From c013892ca22fe6bface8dc580571b2f0527cd1db Mon Sep 17 00:00:00 2001 From: kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com> Date: Wed, 8 Jan 2025 10:29:23 +0000 Subject: [PATCH] [chore] replace statuses.updated_at column with statuses.edited_at (#3636) * update statuses table to replace updated_at column with edited_at * code comment * better code comments, fix setting of status + edit + mention + poll database times * fix log to logf call * fix status.EditIDs not being carried over in dereferencer.encrichStatus() * move status.EditID setting into handleStatusEdit() --- internal/cache/size.go | 2 +- ...eplace_statuses_updatedat_with_editedat.go | 104 ++++++++++++++++ .../interactionpolicy.go | 99 +++++++++++++++ .../status.go | 114 ++++++++++++++++++ internal/db/bundb/poll.go | 12 -- internal/db/bundb/timeline_test.go | 1 - internal/db/test/conversation.go | 1 - internal/federation/dereferencing/status.go | 45 ++++--- .../federation/dereferencing/status_test.go | 2 +- .../filter/visibility/home_timeline_test.go | 7 -- internal/gtsmodel/status.go | 11 +- internal/processing/status/create.go | 1 - internal/processing/status/edit.go | 6 +- internal/processing/status/edit_test.go | 36 +++--- .../processing/workers/fromfediapi_test.go | 1 - internal/typeutils/astointernal.go | 12 +- internal/typeutils/internaltoas.go | 4 +- internal/typeutils/internaltoas_test.go | 4 - internal/typeutils/internaltofrontend.go | 12 +- internal/typeutils/internaltorss.go | 2 +- internal/typeutils/internaltorss_test.go | 2 - internal/typeutils/wrap_test.go | 1 - testrig/testmodels.go | 56 ++++----- 23 files changed, 417 insertions(+), 118 deletions(-) create mode 100644 internal/db/bundb/migrations/20250106114512_replace_statuses_updatedat_with_editedat.go create mode 100644 internal/db/bundb/migrations/20250106114512_replace_statuses_updatedat_with_editedat/interactionpolicy.go create mode 100644 internal/db/bundb/migrations/20250106114512_replace_statuses_updatedat_with_editedat/status.go diff --git a/internal/cache/size.go b/internal/cache/size.go index 2c8772f96..4cca91666 100644 --- a/internal/cache/size.go +++ b/internal/cache/size.go @@ -657,7 +657,7 @@ func sizeofStatus() uintptr { MentionIDs: []string{}, EmojiIDs: []string{exampleID, exampleID, exampleID}, CreatedAt: exampleTime, - UpdatedAt: exampleTime, + EditedAt: exampleTime, FetchedAt: exampleTime, Local: func() *bool { ok := false; return &ok }(), AccountURI: exampleURI, diff --git a/internal/db/bundb/migrations/20250106114512_replace_statuses_updatedat_with_editedat.go b/internal/db/bundb/migrations/20250106114512_replace_statuses_updatedat_with_editedat.go new file mode 100644 index 000000000..fa28c7ce3 --- /dev/null +++ b/internal/db/bundb/migrations/20250106114512_replace_statuses_updatedat_with_editedat.go @@ -0,0 +1,104 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// 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 migrations + +import ( + "context" + "fmt" + "reflect" + + oldmodel "github.com/superseriousbusiness/gotosocial/internal/db/bundb/migrations/20250106114512_replace_statuses_updatedat_with_editedat" + newmodel "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/log" + "github.com/uptrace/bun" + "github.com/uptrace/bun/dialect" +) + +func init() { + up := func(ctx context.Context, db *bun.DB) error { + return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + var newStatus *newmodel.Status + newStatusType := reflect.TypeOf(newStatus) + + // Generate new Status.EditedAt column definition from bun. + colDef, err := getBunColumnDef(tx, newStatusType, "EditedAt") + if err != nil { + return fmt.Errorf("error making column def: %w", err) + } + + log.Info(ctx, "adding statuses.edited_at column...") + _, err = tx.NewAddColumn().Model(newStatus). + ColumnExpr(colDef). + Exec(ctx) + if err != nil { + return fmt.Errorf("error adding column: %w", err) + } + + var whereSQL string + var whereArg []any + + // Check for an empty length + // EditIDs JSON array, with different + // SQL depending on connected database. + switch tx.Dialect().Name() { + case dialect.SQLite: + whereSQL = "NOT (json_array_length(?) = 0 OR ? IS NULL)" + whereArg = []any{bun.Ident("edits"), bun.Ident("edits")} + case dialect.PG: + whereSQL = "NOT (CARDINALITY(?) = 0 OR ? IS NULL)" + whereArg = []any{bun.Ident("edits"), bun.Ident("edits")} + default: + panic("unsupported db type") + } + + log.Info(ctx, "setting edited_at = updated_at where not empty(edits)...") + res, err := tx.NewUpdate().Model(newStatus).Where(whereSQL, whereArg...). + Set("? = ?", + bun.Ident("edited_at"), + bun.Ident("updated_at"), + ). + Exec(ctx) + if err != nil { + return fmt.Errorf("error updating columns: %w", err) + } + + count, _ := res.RowsAffected() + log.Infof(ctx, "updated %d statuses", count) + + log.Info(ctx, "removing statuses.updated_at column...") + _, err = tx.NewDropColumn().Model((*oldmodel.Status)(nil)). + Column("updated_at"). + Exec(ctx) + if err != nil { + return fmt.Errorf("error dropping column: %w", err) + } + + return nil + }) + } + + down := func(ctx context.Context, db *bun.DB) error { + return db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { + return nil + }) + } + + if err := Migrations.Register(up, down); err != nil { + panic(err) + } +} diff --git a/internal/db/bundb/migrations/20250106114512_replace_statuses_updatedat_with_editedat/interactionpolicy.go b/internal/db/bundb/migrations/20250106114512_replace_statuses_updatedat_with_editedat/interactionpolicy.go new file mode 100644 index 000000000..9895acc22 --- /dev/null +++ b/internal/db/bundb/migrations/20250106114512_replace_statuses_updatedat_with_editedat/interactionpolicy.go @@ -0,0 +1,99 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// 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 gtsmodel + +// A policy URI is GoToSocial's internal representation of +// one ActivityPub URI for an Actor or a Collection of Actors, +// specific to the domain of enforcing interaction policies. +// +// A PolicyValue can be stored in the database either as one +// of the Value constants defined below (to save space), OR as +// a full-fledged ActivityPub URI. +// +// A PolicyValue should be translated to the canonical string +// value of the represented URI when federating an item, or +// from the canonical string value of the URI when receiving +// or retrieving an item. +// +// For example, if the PolicyValue `followers` was being +// federated outwards in an interaction policy attached to an +// item created by the actor `https://example.org/users/someone`, +// then it should be translated to their followers URI when sent, +// eg., `https://example.org/users/someone/followers`. +// +// Likewise, if GoToSocial receives an item with an interaction +// policy containing `https://example.org/users/someone/followers`, +// and the item was created by `https://example.org/users/someone`, +// then the followers URI would be converted to `followers` +// for internal storage. +type PolicyValue string + +const ( + // Stand-in for ActivityPub magic public URI, + // which encompasses every possible Actor URI. + PolicyValuePublic PolicyValue = "public" + // Stand-in for the Followers Collection of + // the item owner's Actor. + PolicyValueFollowers PolicyValue = "followers" + // Stand-in for the Following Collection of + // the item owner's Actor. + PolicyValueFollowing PolicyValue = "following" + // Stand-in for the Mutuals Collection of + // the item owner's Actor. + // + // (TODO: Reserved, currently unused). + PolicyValueMutuals PolicyValue = "mutuals" + // Stand-in for Actor URIs tagged in the item. + PolicyValueMentioned PolicyValue = "mentioned" + // Stand-in for the Actor URI of the item owner. + PolicyValueAuthor PolicyValue = "author" +) + +type PolicyValues []PolicyValue + +// An InteractionPolicy determines which +// interactions will be accepted for an +// item, and according to what rules. +type InteractionPolicy struct { + // Conditions in which a Like + // interaction will be accepted + // for an item with this policy. + CanLike PolicyRules + // Conditions in which a Reply + // interaction will be accepted + // for an item with this policy. + CanReply PolicyRules + // Conditions in which an Announce + // interaction will be accepted + // for an item with this policy. + CanAnnounce PolicyRules +} + +// PolicyRules represents the rules according +// to which a certain interaction is permitted +// to various Actor and Actor Collection URIs. +type PolicyRules struct { + // Always is for PolicyValues who are + // permitted to do an interaction + // without requiring approval. + Always PolicyValues + // WithApproval is for PolicyValues who + // are conditionally permitted to do + // an interaction, pending approval. + WithApproval PolicyValues +} diff --git a/internal/db/bundb/migrations/20250106114512_replace_statuses_updatedat_with_editedat/status.go b/internal/db/bundb/migrations/20250106114512_replace_statuses_updatedat_with_editedat/status.go new file mode 100644 index 000000000..27f3e5046 --- /dev/null +++ b/internal/db/bundb/migrations/20250106114512_replace_statuses_updatedat_with_editedat/status.go @@ -0,0 +1,114 @@ +// GoToSocial +// Copyright (C) GoToSocial Authors admin@gotosocial.org +// SPDX-License-Identifier: AGPL-3.0-or-later +// +// 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 gtsmodel + +import ( + "time" +) + +// Status represents a user-created 'post' or +// 'status' in the database, either remote or local +type Status struct { + ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database + CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created + UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated + FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // when was item (remote) last fetched. + PinnedAt time.Time `bun:"type:timestamptz,nullzero"` // Status was pinned by owning account at this time. + URI string `bun:",unique,nullzero,notnull"` // activitypub URI of this status + URL string `bun:",nullzero"` // web url for viewing this status + Content string `bun:""` // content of this status; likely html-formatted but not guaranteed + AttachmentIDs []string `bun:"attachments,array"` // Database IDs of any media attachments associated with this status + TagIDs []string `bun:"tags,array"` // Database IDs of any tags used in this status + MentionIDs []string `bun:"mentions,array"` // Database IDs of any mentions in this status + EmojiIDs []string `bun:"emojis,array"` // Database IDs of any emojis used in this status + Local *bool `bun:",nullzero,notnull,default:false"` // is this status from a local account? + AccountID string `bun:"type:CHAR(26),nullzero,notnull"` // which account posted this status? + AccountURI string `bun:",nullzero,notnull"` // activitypub uri of the owner of this status + InReplyToID string `bun:"type:CHAR(26),nullzero"` // id of the status this status replies to + InReplyToURI string `bun:",nullzero"` // activitypub uri of the status this status is a reply to + InReplyToAccountID string `bun:"type:CHAR(26),nullzero"` // id of the account that this status replies to + InReplyTo *Status `bun:"-"` // status corresponding to inReplyToID + BoostOfID string `bun:"type:CHAR(26),nullzero"` // id of the status this status is a boost of + BoostOfURI string `bun:"-"` // URI of the status this status is a boost of; field not inserted in the db, just for dereferencing purposes. + BoostOfAccountID string `bun:"type:CHAR(26),nullzero"` // id of the account that owns the boosted status + BoostOf *Status `bun:"-"` // status that corresponds to boostOfID + ThreadID string `bun:"type:CHAR(26),nullzero"` // id of the thread to which this status belongs; only set for remote statuses if a local account is involved at some point in the thread, otherwise null + EditIDs []string `bun:"edits,array"` // + PollID string `bun:"type:CHAR(26),nullzero"` // + ContentWarning string `bun:",nullzero"` // cw string for this status + Visibility Visibility `bun:",nullzero,notnull"` // visibility entry for this status + Sensitive *bool `bun:",nullzero,notnull,default:false"` // mark the status as sensitive? + Language string `bun:",nullzero"` // what language is this status written in? + CreatedWithApplicationID string `bun:"type:CHAR(26),nullzero"` // Which application was used to create this status? + ActivityStreamsType string `bun:",nullzero,notnull"` // What is the activitystreams type of this status? See: https://www.w3.org/TR/activitystreams-vocabulary/#object-types. Will probably almost always be Note but who knows!. + Text string `bun:""` // Original text of the status without formatting + Federated *bool `bun:",notnull"` // This status will be federated beyond the local timeline(s) + InteractionPolicy *InteractionPolicy `bun:""` // InteractionPolicy for this status. If null then the default InteractionPolicy should be assumed for this status's Visibility. Always null for boost wrappers. + PendingApproval *bool `bun:",nullzero,notnull,default:false"` // If true then status is a reply or boost wrapper that must be Approved by the reply-ee or boost-ee before being fully distributed. + PreApproved bool `bun:"-"` // If true, then status is a reply to or boost wrapper of a status on our instance, has permission to do the interaction, and an Accept should be sent out for it immediately. Field not stored in the DB. + ApprovedByURI string `bun:",nullzero"` // URI of an Accept Activity that approves the Announce or Create Activity that this status was/will be attached to. +} + +// GetID implements timeline.Timelineable{}. +func (s *Status) GetID() string { + return s.ID +} + +// IsLocal returns true if this is a local +// status (ie., originating from this instance). +func (s *Status) IsLocal() bool { + return s.Local != nil && *s.Local +} + +// enumType is the type we (at least, should) use +// for database enum types. it is the largest size +// supported by a PostgreSQL SMALLINT, since an +// SQLite SMALLINT is actually variable in size. +type enumType int16 + +// Visibility represents the +// visibility granularity of a status. +type Visibility enumType + +const ( + // VisibilityNone means nobody can see this. + // It's only used for web status visibility. + VisibilityNone Visibility = 1 + + // VisibilityPublic means this status will + // be visible to everyone on all timelines. + VisibilityPublic Visibility = 2 + + // VisibilityUnlocked means this status will be visible to everyone, + // but will only show on home timeline to followers, and in lists. + VisibilityUnlocked Visibility = 3 + + // VisibilityFollowersOnly means this status is viewable to followers only. + VisibilityFollowersOnly Visibility = 4 + + // VisibilityMutualsOnly means this status + // is visible to mutual followers only. + VisibilityMutualsOnly Visibility = 5 + + // VisibilityDirect means this status is + // visible only to mentioned recipients. + VisibilityDirect Visibility = 6 + + // VisibilityDefault is used when no other setting can be found. + VisibilityDefault Visibility = VisibilityUnlocked +) diff --git a/internal/db/bundb/poll.go b/internal/db/bundb/poll.go index e8c3e7e54..5da9832f0 100644 --- a/internal/db/bundb/poll.go +++ b/internal/db/bundb/poll.go @@ -21,7 +21,6 @@ "context" "errors" "slices" - "time" "github.com/superseriousbusiness/gotosocial/internal/db" "github.com/superseriousbusiness/gotosocial/internal/gtscontext" @@ -158,17 +157,6 @@ func (p *pollDB) UpdatePoll(ctx context.Context, poll *gtsmodel.Poll, cols ...st return p.state.Caches.DB.Poll.Store(poll, func() error { return p.db.RunInTx(ctx, nil, func(ctx context.Context, tx bun.Tx) error { - // Update the status' "updated_at" field. - if _, err := tx.NewUpdate(). - Table("statuses"). - Where("? = ?", bun.Ident("id"), poll.StatusID). - SetColumn("updated_at", "?", time.Now()). - Exec(ctx); err != nil { - return err - } - - // Finally, update poll - // columns in database. _, err := tx.NewUpdate(). Model(poll). Column(cols...). diff --git a/internal/db/bundb/timeline_test.go b/internal/db/bundb/timeline_test.go index 75a335512..5b3268d36 100644 --- a/internal/db/bundb/timeline_test.go +++ b/internal/db/bundb/timeline_test.go @@ -50,7 +50,6 @@ func getFutureStatus() *gtsmodel.Status { MentionIDs: []string{}, EmojiIDs: []string{}, CreatedAt: theDistantFuture, - UpdatedAt: theDistantFuture, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/admin", AccountID: "01F8MH17FWEB39HZJ76B6VXSKF", diff --git a/internal/db/test/conversation.go b/internal/db/test/conversation.go index 95713927e..50bca5308 100644 --- a/internal/db/test/conversation.go +++ b/internal/db/test/conversation.go @@ -72,7 +72,6 @@ func (f *ConversationFactory) NewTestStatus(localAccount *gtsmodel.Account, thre status := >smodel.Status{ ID: statusID, CreatedAt: createdAt, - UpdatedAt: createdAt, URI: "http://localhost:8080/users/" + localAccount.Username + "/statuses/" + statusID, AccountID: localAccount.ID, AccountURI: localAccount.URI, diff --git a/internal/federation/dereferencing/status.go b/internal/federation/dereferencing/status.go index 223389ad7..8ebfe6431 100644 --- a/internal/federation/dereferencing/status.go +++ b/internal/federation/dereferencing/status.go @@ -656,6 +656,10 @@ func (d *Dereferencer) fetchStatusMentions( err error, ) { + // Get most-recent modified time + // for use in new mention ULIDs. + updatedAt := status.UpdatedAt() + // Allocate new slice to take the yet-to-be created mention IDs. status.MentionIDs = make([]string, len(status.Mentions)) @@ -691,10 +695,10 @@ func (d *Dereferencer) fetchStatusMentions( // This mention didn't exist yet. // Generate new ID according to latest update. - mention.ID = id.NewULIDFromTime(status.UpdatedAt) + mention.ID = id.NewULIDFromTime(updatedAt) - // Set known further mention details. - mention.CreatedAt = status.UpdatedAt + // Set further mention details. + mention.CreatedAt = updatedAt mention.OriginAccount = status.Account mention.OriginAccountID = status.AccountID mention.OriginAccountURI = status.AccountURI @@ -1096,8 +1100,12 @@ func (d *Dereferencer) handleStatusPoll( func (d *Dereferencer) insertStatusPoll(ctx context.Context, status *gtsmodel.Status) error { var err error - // Generate new ID for poll from latest updated time. - status.Poll.ID = id.NewULIDFromTime(status.UpdatedAt) + // Get most-recent modified time + // which will be poll creation time. + createdAt := status.UpdatedAt() + + // Generate new ID for poll from createdAt. + status.Poll.ID = id.NewULIDFromTime(createdAt) // Update the status<->poll links. status.PollID = status.Poll.ID @@ -1133,6 +1141,10 @@ func (d *Dereferencer) handleStatusEdit( ) { var edited bool + // Copy previous status edit columns. + status.EditIDs = existing.EditIDs + status.Edits = existing.Edits + // Preallocate max slice length. cols = make([]string, 1, 13) @@ -1216,25 +1228,21 @@ func (d *Dereferencer) handleStatusEdit( } if edited { - // ensure that updated_at hasn't remained the same - // but an edit was received. manually intervene here. - if status.UpdatedAt.Equal(existing.UpdatedAt) || - status.CreatedAt.Equal(status.UpdatedAt) { - - // Simply use current fetching time. - status.UpdatedAt = status.FetchedAt - } + // Get previous-most-recent modified time, + // which will be this edit's creation time. + createdAt := existing.UpdatedAt() // Status has been editted since last // we saw it, take snapshot of existing. var edit gtsmodel.StatusEdit - edit.ID = id.NewULIDFromTime(status.UpdatedAt) + edit.ID = id.NewULIDFromTime(createdAt) edit.Content = existing.Content edit.ContentWarning = existing.ContentWarning edit.Text = existing.Text edit.Language = existing.Language edit.Sensitive = existing.Sensitive edit.StatusID = status.ID + edit.CreatedAt = createdAt // Copy existing attachments and descriptions. edit.AttachmentIDs = existing.AttachmentIDs @@ -1246,9 +1254,6 @@ func (d *Dereferencer) handleStatusEdit( } } - // Edit creation is last update time. - edit.CreatedAt = existing.UpdatedAt - if existing.Poll != nil { // Poll only set if existing contained them. edit.PollOptions = existing.Poll.Options @@ -1275,10 +1280,10 @@ func (d *Dereferencer) handleStatusEdit( cols = append(cols, "edits") } - if !existing.UpdatedAt.Equal(status.UpdatedAt) { + if !existing.EditedAt.Equal(status.EditedAt) { // Whether status edited or not, - // updated_at column has changed. - cols = append(cols, "updated_at") + // edited_at column has changed. + cols = append(cols, "edited_at") } return cols, nil diff --git a/internal/federation/dereferencing/status_test.go b/internal/federation/dereferencing/status_test.go index 4b3bd6d67..445676ba4 100644 --- a/internal/federation/dereferencing/status_test.go +++ b/internal/federation/dereferencing/status_test.go @@ -337,7 +337,7 @@ func (suite *StatusTestSuite) TestDereferencerRefreshStatusUpdated() { AttachmentIDs: testStatus.AttachmentIDs, PollOptions: getPollOptions(testStatus), PollVotes: getPollVotes(testStatus), - CreatedAt: testStatus.UpdatedAt, + CreatedAt: testStatus.UpdatedAt(), }, ) } diff --git a/internal/filter/visibility/home_timeline_test.go b/internal/filter/visibility/home_timeline_test.go index 9b7ce8c51..f33ad3999 100644 --- a/internal/filter/visibility/home_timeline_test.go +++ b/internal/filter/visibility/home_timeline_test.go @@ -147,7 +147,6 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestThread() { URL: "http://localhost:8080/@the_mighty_zork/statuses/01G395ESAYPK9161QSQEZKATJN", Content: "nbnbdy expects dog", CreatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"), - UpdatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"), Local: util.Ptr(false), AccountURI: "http://localhost:8080/users/the_mighty_zork", AccountID: threadParentAccount.ID, @@ -197,7 +196,6 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly( URL: "http://fossbros-anonymous.io/@foss_satan/statuses/01G3957TS7XE2CMDKFG3MZPWAF", Content: "didn't expect dog", CreatedAt: testrig.TimeMustParse("2021-09-20T12:40:37+02:00"), - UpdatedAt: testrig.TimeMustParse("2021-09-20T12:40:37+02:00"), Local: util.Ptr(false), AccountURI: "http://fossbros-anonymous.io/users/foss_satan", AccountID: originalStatusParent.ID, @@ -228,7 +226,6 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly( URL: "http://localhost:8080/@the_mighty_zork/statuses/01G395ESAYPK9161QSQEZKATJN", Content: "nbnbdy expects dog", CreatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"), - UpdatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"), Local: util.Ptr(false), AccountURI: "http://localhost:8080/users/the_mighty_zork", AccountID: replyingAccount.ID, @@ -259,7 +256,6 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyFollowersOnly( URL: "http://localhost:8080/@the_mighty_zork/statuses/01G395NZQZGJYRBAES57KYZ7XP", Content: "*nobody", CreatedAt: testrig.TimeMustParse("2021-09-20T12:42:37+02:00"), - UpdatedAt: testrig.TimeMustParse("2021-09-20T12:42:37+02:00"), Local: util.Ptr(false), AccountURI: "http://localhost:8080/users/the_mighty_zork", AccountID: replyingAccount.ID, @@ -301,7 +297,6 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc URL: "http://fossbros-anonymous.io/@foss_satan/statuses/01G3957TS7XE2CMDKFG3MZPWAF", Content: "didn't expect dog", CreatedAt: testrig.TimeMustParse("2021-09-20T12:40:37+02:00"), - UpdatedAt: testrig.TimeMustParse("2021-09-20T12:40:37+02:00"), Local: util.Ptr(false), AccountURI: "http://fossbros-anonymous.io/users/foss_satan", AccountID: originalStatusParent.ID, @@ -332,7 +327,6 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc URL: "http://localhost:8080/@the_mighty_zork/statuses/01G395ESAYPK9161QSQEZKATJN", Content: "nbnbdy expects dog", CreatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"), - UpdatedAt: testrig.TimeMustParse("2021-09-20T12:41:37+02:00"), Local: util.Ptr(false), AccountURI: "http://localhost:8080/users/the_mighty_zork", AccountID: replyingAccount.ID, @@ -363,7 +357,6 @@ func (suite *StatusStatusHomeTimelineableTestSuite) TestChainReplyPublicAndUnloc URL: "http://localhost:8080/@the_mighty_zork/statuses/01G395NZQZGJYRBAES57KYZ7XP", Content: "*nobody", CreatedAt: testrig.TimeMustParse("2021-09-20T12:42:37+02:00"), - UpdatedAt: testrig.TimeMustParse("2021-09-20T12:42:37+02:00"), Local: util.Ptr(false), AccountURI: "http://localhost:8080/users/the_mighty_zork", AccountID: replyingAccount.ID, diff --git a/internal/gtsmodel/status.go b/internal/gtsmodel/status.go index 3a348bba4..d28898ed1 100644 --- a/internal/gtsmodel/status.go +++ b/internal/gtsmodel/status.go @@ -28,7 +28,7 @@ type Status struct { ID string `bun:"type:CHAR(26),pk,nullzero,notnull,unique"` // id of this item in the database CreatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item created - UpdatedAt time.Time `bun:"type:timestamptz,nullzero,notnull,default:current_timestamp"` // when was item last updated + EditedAt time.Time `bun:"type:timestamptz,nullzero"` // when this status was last edited (if set) FetchedAt time.Time `bun:"type:timestamptz,nullzero"` // when was item (remote) last fetched. PinnedAt time.Time `bun:"type:timestamptz,nullzero"` // Status was pinned by owning account at this time. URI string `bun:",unique,nullzero,notnull"` // activitypub URI of this status @@ -299,6 +299,15 @@ func (s *Status) AllAttachmentIDs() []string { return xslices.Deduplicate(attachmentIDs) } +// UpdatedAt returns latest time this status +// was updated, either EditedAt or CreatedAt. +func (s *Status) UpdatedAt() time.Time { + if s.EditedAt.IsZero() { + return s.CreatedAt + } + return s.EditedAt +} + // StatusToTag is an intermediate struct to facilitate the many2many relationship between a status and one or more tags. type StatusToTag struct { StatusID string `bun:"type:CHAR(26),unique:statustag,nullzero,notnull"` diff --git a/internal/processing/status/create.go b/internal/processing/status/create.go index af9831b9c..b77d0af9c 100644 --- a/internal/processing/status/create.go +++ b/internal/processing/status/create.go @@ -97,7 +97,6 @@ func (p *Processor) Create( URI: accountURIs.StatusesURI + "/" + statusID, URL: accountURIs.StatusesURL + "/" + statusID, CreatedAt: now, - UpdatedAt: now, Local: util.Ptr(true), Account: requester, AccountID: requester.ID, diff --git a/internal/processing/status/edit.go b/internal/processing/status/edit.go index d16092a57..95665074e 100644 --- a/internal/processing/status/edit.go +++ b/internal/processing/status/edit.go @@ -147,7 +147,7 @@ func (p *Processor) Edit( // Track status columns we // need to update in database. cols := make([]string, 2, 13) - cols[0] = "updated_at" + cols[0] = "edited_at" cols[1] = "edits" if contentChanged { @@ -259,7 +259,7 @@ func (p *Processor) Edit( edit.Language = status.Language edit.Sensitive = status.Sensitive edit.StatusID = status.ID - edit.CreatedAt = status.UpdatedAt + edit.CreatedAt = status.UpdatedAt() // Copy existing media and descriptions. edit.AttachmentIDs = status.AttachmentIDs @@ -302,7 +302,7 @@ func (p *Processor) Edit( status.Sensitive = &form.Sensitive status.AttachmentIDs = form.MediaIDs status.Attachments = media - status.UpdatedAt = now + status.EditedAt = now if poll != nil { // Set relevent fields for latest with poll. diff --git a/internal/processing/status/edit_test.go b/internal/processing/status/edit_test.go index 393c3efc2..36ebf2765 100644 --- a/internal/processing/status/edit_test.go +++ b/internal/processing/status/edit_test.go @@ -68,7 +68,7 @@ func (suite *StatusEditTestSuite) TestSimpleEdit() { suite.Equal(form.SpoilerText, apiStatus.SpoilerText) suite.Equal(form.Sensitive, apiStatus.Sensitive) suite.Equal(form.Language, *apiStatus.Language) - suite.NotEqual(util.FormatISO8601(status.UpdatedAt), *apiStatus.EditedAt) + suite.NotEqual(util.FormatISO8601(status.EditedAt), *apiStatus.EditedAt) // Fetched the latest version of edited status from the database. latestStatus, err := suite.state.DB.GetStatusByID(ctx, status.ID) @@ -80,7 +80,7 @@ func (suite *StatusEditTestSuite) TestSimpleEdit() { suite.Equal(form.Sensitive, *latestStatus.Sensitive) suite.Equal(form.Language, latestStatus.Language) suite.Equal(len(status.EditIDs)+1, len(latestStatus.EditIDs)) - suite.NotEqual(status.UpdatedAt, latestStatus.UpdatedAt) + suite.NotEqual(status.UpdatedAt(), latestStatus.UpdatedAt()) // Populate all historical edits for this status. err = suite.state.DB.PopulateStatusEdits(ctx, latestStatus) @@ -93,7 +93,7 @@ func (suite *StatusEditTestSuite) TestSimpleEdit() { suite.Equal(status.ContentWarning, previousEdit.ContentWarning) suite.Equal(*status.Sensitive, *previousEdit.Sensitive) suite.Equal(status.Language, previousEdit.Language) - suite.Equal(status.UpdatedAt, previousEdit.CreatedAt) + suite.Equal(status.UpdatedAt(), previousEdit.CreatedAt) } func (suite *StatusEditTestSuite) TestEditAddPoll() { @@ -135,7 +135,7 @@ func (suite *StatusEditTestSuite) TestEditAddPoll() { suite.Equal(form.SpoilerText, apiStatus.SpoilerText) suite.Equal(form.Sensitive, apiStatus.Sensitive) suite.Equal(form.Language, *apiStatus.Language) - suite.NotEqual(util.FormatISO8601(status.UpdatedAt), *apiStatus.EditedAt) + suite.NotEqual(util.FormatISO8601(status.EditedAt), *apiStatus.EditedAt) suite.NotNil(apiStatus.Poll) suite.Equal(form.Poll.Options, xslices.Gather(nil, apiStatus.Poll.Options, func(opt apimodel.PollOption) string { return opt.Title @@ -151,7 +151,7 @@ func (suite *StatusEditTestSuite) TestEditAddPoll() { suite.Equal(form.Sensitive, *latestStatus.Sensitive) suite.Equal(form.Language, latestStatus.Language) suite.Equal(len(status.EditIDs)+1, len(latestStatus.EditIDs)) - suite.NotEqual(status.UpdatedAt, latestStatus.UpdatedAt) + suite.NotEqual(status.UpdatedAt(), latestStatus.UpdatedAt()) suite.NotNil(latestStatus.Poll) suite.Equal(form.Poll.Options, latestStatus.Poll.Options) @@ -170,7 +170,7 @@ func (suite *StatusEditTestSuite) TestEditAddPoll() { suite.Equal(status.ContentWarning, previousEdit.ContentWarning) suite.Equal(*status.Sensitive, *previousEdit.Sensitive) suite.Equal(status.Language, previousEdit.Language) - suite.Equal(status.UpdatedAt, previousEdit.CreatedAt) + suite.Equal(status.UpdatedAt(), previousEdit.CreatedAt) suite.Equal(status.Poll != nil, len(previousEdit.PollOptions) > 0) } @@ -213,7 +213,7 @@ func (suite *StatusEditTestSuite) TestEditAddPollNoExpiry() { suite.Equal(form.SpoilerText, apiStatus.SpoilerText) suite.Equal(form.Sensitive, apiStatus.Sensitive) suite.Equal(form.Language, *apiStatus.Language) - suite.NotEqual(util.FormatISO8601(status.UpdatedAt), *apiStatus.EditedAt) + suite.NotEqual(util.FormatISO8601(status.EditedAt), *apiStatus.EditedAt) suite.NotNil(apiStatus.Poll) suite.Equal(form.Poll.Options, xslices.Gather(nil, apiStatus.Poll.Options, func(opt apimodel.PollOption) string { return opt.Title @@ -229,7 +229,7 @@ func (suite *StatusEditTestSuite) TestEditAddPollNoExpiry() { suite.Equal(form.Sensitive, *latestStatus.Sensitive) suite.Equal(form.Language, latestStatus.Language) suite.Equal(len(status.EditIDs)+1, len(latestStatus.EditIDs)) - suite.NotEqual(status.UpdatedAt, latestStatus.UpdatedAt) + suite.NotEqual(status.UpdatedAt(), latestStatus.UpdatedAt()) suite.NotNil(latestStatus.Poll) suite.Equal(form.Poll.Options, latestStatus.Poll.Options) @@ -248,7 +248,7 @@ func (suite *StatusEditTestSuite) TestEditAddPollNoExpiry() { suite.Equal(status.ContentWarning, previousEdit.ContentWarning) suite.Equal(*status.Sensitive, *previousEdit.Sensitive) suite.Equal(status.Language, previousEdit.Language) - suite.Equal(status.UpdatedAt, previousEdit.CreatedAt) + suite.Equal(status.UpdatedAt(), previousEdit.CreatedAt) suite.Equal(status.Poll != nil, len(previousEdit.PollOptions) > 0) } @@ -287,7 +287,7 @@ func (suite *StatusEditTestSuite) TestEditMediaDescription() { suite.Equal(form.SpoilerText, apiStatus.SpoilerText) suite.Equal(form.Sensitive, apiStatus.Sensitive) suite.Equal(form.Language, *apiStatus.Language) - suite.NotEqual(util.FormatISO8601(status.UpdatedAt), *apiStatus.EditedAt) + suite.NotEqual(util.FormatISO8601(status.EditedAt), *apiStatus.EditedAt) suite.Equal(form.MediaIDs, xslices.Gather(nil, apiStatus.MediaAttachments, func(media *apimodel.Attachment) string { return media.ID })) @@ -310,7 +310,7 @@ func (suite *StatusEditTestSuite) TestEditMediaDescription() { suite.Equal(form.Sensitive, *latestStatus.Sensitive) suite.Equal(form.Language, latestStatus.Language) suite.Equal(len(status.EditIDs)+1, len(latestStatus.EditIDs)) - suite.NotEqual(status.UpdatedAt, latestStatus.UpdatedAt) + suite.NotEqual(status.UpdatedAt(), latestStatus.UpdatedAt()) suite.Equal(form.MediaIDs, latestStatus.AttachmentIDs) suite.Equal( xslices.Gather(nil, form.MediaAttributes, func(attr apimodel.AttachmentAttributesRequest) string { @@ -338,7 +338,7 @@ func (suite *StatusEditTestSuite) TestEditMediaDescription() { suite.Equal(status.ContentWarning, previousEdit.ContentWarning) suite.Equal(*status.Sensitive, *previousEdit.Sensitive) suite.Equal(status.Language, previousEdit.Language) - suite.Equal(status.UpdatedAt, previousEdit.CreatedAt) + suite.Equal(status.UpdatedAt(), previousEdit.CreatedAt) suite.Equal(status.AttachmentIDs, previousEdit.AttachmentIDs) suite.Equal( xslices.Gather(nil, status.Attachments, func(media *gtsmodel.MediaAttachment) string { @@ -390,7 +390,7 @@ func (suite *StatusEditTestSuite) TestEditAddMedia() { suite.Equal(form.SpoilerText, apiStatus.SpoilerText) suite.Equal(form.Sensitive, apiStatus.Sensitive) suite.Equal(form.Language, *apiStatus.Language) - suite.NotEqual(util.FormatISO8601(status.UpdatedAt), *apiStatus.EditedAt) + suite.NotEqual(util.FormatISO8601(status.EditedAt), *apiStatus.EditedAt) suite.Equal(form.MediaIDs, xslices.Gather(nil, apiStatus.MediaAttachments, func(media *apimodel.Attachment) string { return media.ID })) @@ -405,7 +405,7 @@ func (suite *StatusEditTestSuite) TestEditAddMedia() { suite.Equal(form.Sensitive, *latestStatus.Sensitive) suite.Equal(form.Language, latestStatus.Language) suite.Equal(len(status.EditIDs)+1, len(latestStatus.EditIDs)) - suite.NotEqual(status.UpdatedAt, latestStatus.UpdatedAt) + suite.NotEqual(status.UpdatedAt(), latestStatus.UpdatedAt()) suite.Equal(form.MediaIDs, latestStatus.AttachmentIDs) // Populate all historical edits for this status. @@ -419,7 +419,7 @@ func (suite *StatusEditTestSuite) TestEditAddMedia() { suite.Equal(status.ContentWarning, previousEdit.ContentWarning) suite.Equal(*status.Sensitive, *previousEdit.Sensitive) suite.Equal(status.Language, previousEdit.Language) - suite.Equal(status.UpdatedAt, previousEdit.CreatedAt) + suite.Equal(status.UpdatedAt(), previousEdit.CreatedAt) suite.Equal(status.AttachmentIDs, previousEdit.AttachmentIDs) } @@ -456,7 +456,7 @@ func (suite *StatusEditTestSuite) TestEditRemoveMedia() { suite.Equal(form.SpoilerText, apiStatus.SpoilerText) suite.Equal(form.Sensitive, apiStatus.Sensitive) suite.Equal(form.Language, *apiStatus.Language) - suite.NotEqual(util.FormatISO8601(status.UpdatedAt), *apiStatus.EditedAt) + suite.NotEqual(util.FormatISO8601(status.EditedAt), *apiStatus.EditedAt) suite.Equal(form.MediaIDs, xslices.Gather(nil, apiStatus.MediaAttachments, func(media *apimodel.Attachment) string { return media.ID })) @@ -471,7 +471,7 @@ func (suite *StatusEditTestSuite) TestEditRemoveMedia() { suite.Equal(form.Sensitive, *latestStatus.Sensitive) suite.Equal(form.Language, latestStatus.Language) suite.Equal(len(status.EditIDs)+1, len(latestStatus.EditIDs)) - suite.NotEqual(status.UpdatedAt, latestStatus.UpdatedAt) + suite.NotEqual(status.UpdatedAt(), latestStatus.UpdatedAt()) suite.Equal(form.MediaIDs, latestStatus.AttachmentIDs) // Populate all historical edits for this status. @@ -485,7 +485,7 @@ func (suite *StatusEditTestSuite) TestEditRemoveMedia() { suite.Equal(status.ContentWarning, previousEdit.ContentWarning) suite.Equal(*status.Sensitive, *previousEdit.Sensitive) suite.Equal(status.Language, previousEdit.Language) - suite.Equal(status.UpdatedAt, previousEdit.CreatedAt) + suite.Equal(status.UpdatedAt(), previousEdit.CreatedAt) suite.Equal(status.AttachmentIDs, previousEdit.AttachmentIDs) } diff --git a/internal/processing/workers/fromfediapi_test.go b/internal/processing/workers/fromfediapi_test.go index d7d7454e7..88d0e6071 100644 --- a/internal/processing/workers/fromfediapi_test.go +++ b/internal/processing/workers/fromfediapi_test.go @@ -55,7 +55,6 @@ func (suite *FromFediAPITestSuite) TestProcessFederationAnnounce() { announceStatus.URI = "https://example.org/some-announce-uri" announceStatus.BoostOfURI = boostedStatus.URI announceStatus.CreatedAt = time.Now() - announceStatus.UpdatedAt = time.Now() announceStatus.AccountID = boostingAccount.ID announceStatus.AccountURI = boostingAccount.URI announceStatus.Account = boostingAccount diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go index a473317ff..0ad9a6ff7 100644 --- a/internal/typeutils/astointernal.go +++ b/internal/typeutils/astointernal.go @@ -361,14 +361,12 @@ func (c *Converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab status.CreatedAt = time.Now() } - // status.Updated + // status.Edited // - // Extract and validate update time for status. Defaults to published. + // Extract and validate update time for status. Defaults to none. if upd := ap.GetUpdated(statusable); !upd.Before(status.CreatedAt) { - status.UpdatedAt = upd - } else if upd.IsZero() { - status.UpdatedAt = status.CreatedAt - } else { + status.EditedAt = upd + } else if !upd.IsZero() { // This is a malformed status that will likely break our systems. err := gtserror.Newf("status %s 'updated' predates 'published'", uri) @@ -649,9 +647,9 @@ func (c *Converter) ASAnnounceToStatus( // zero-time will fall back to db defaults. if pub := ap.GetPublished(announceable); !pub.IsZero() { boost.CreatedAt = pub - boost.UpdatedAt = pub } else { log.Warnf(ctx, "unusable published property on %s", uri) + boost.CreatedAt = time.Now() } // Extract and load the boost actor account, diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go index 7d0c483dd..644d832f5 100644 --- a/internal/typeutils/internaltoas.go +++ b/internal/typeutils/internaltoas.go @@ -486,7 +486,9 @@ func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (ap.Stat // Set created / updated at properties. ap.SetPublished(status, s.CreatedAt) - ap.SetUpdated(status, s.UpdatedAt) + if at := s.EditedAt; !at.IsZero() { + ap.SetUpdated(status, at) + } // url if s.URL != "" { diff --git a/internal/typeutils/internaltoas_test.go b/internal/typeutils/internaltoas_test.go index 9870c760a..c847cfc93 100644 --- a/internal/typeutils/internaltoas_test.go +++ b/internal/typeutils/internaltoas_test.go @@ -499,7 +499,6 @@ func (suite *InternalToASTestSuite) TestStatusToAS() { "tag": [], "to": "https://www.w3.org/ns/activitystreams#Public", "type": "Note", - "updated": "2021-10-20T12:40:37+02:00", "url": "http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY" }`, string(bytes)) } @@ -599,7 +598,6 @@ func (suite *InternalToASTestSuite) TestStatusWithTagsToASWithIDs() { ], "to": "https://www.w3.org/ns/activitystreams#Public", "type": "Note", - "updated": "2021-10-20T11:36:45Z", "url": "http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R" }`, string(bytes)) } @@ -700,7 +698,6 @@ func (suite *InternalToASTestSuite) TestStatusWithTagsToASFromDB() { ], "to": "https://www.w3.org/ns/activitystreams#Public", "type": "Note", - "updated": "2021-10-20T11:36:45Z", "url": "http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R" }`, string(bytes)) } @@ -781,7 +778,6 @@ func (suite *InternalToASTestSuite) TestStatusToASWithMentions() { }, "to": "https://www.w3.org/ns/activitystreams#Public", "type": "Note", - "updated": "2021-11-20T13:32:16Z", "url": "http://localhost:8080/@admin/statuses/01FF25D5Q0DH7CHD57CTRS6WK0" }`, string(bytes)) } diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go index 9fb69b438..a90e88a70 100644 --- a/internal/typeutils/internaltofrontend.go +++ b/internal/typeutils/internaltofrontend.go @@ -997,7 +997,7 @@ func (c *Converter) statusToAPIFilterResults( // Key this status based on ID + last updated time, // to ensure we always filter on latest version. - statusKey := s.ID + strconv.FormatInt(s.UpdatedAt.Unix(), 10) + statusKey := s.ID + strconv.FormatInt(s.UpdatedAt().Unix(), 10) // Check if we have filterable fields cached for this status. cache := c.state.Caches.StatusesFilterableFields @@ -1384,10 +1384,8 @@ func (c *Converter) baseStatusToFrontend( InteractionPolicy: *apiInteractionPolicy, } - // Only set edited_at if this is a non-boost-wrapper - // with an updated_at date different to creation date. - if !s.UpdatedAt.Equal(s.CreatedAt) && s.BoostOfID == "" { - timestamp := util.FormatISO8601(s.UpdatedAt) + if at := s.EditedAt; !at.IsZero() { + timestamp := util.FormatISO8601(at) apiStatus.EditedAt = util.Ptr(timestamp) } @@ -1522,8 +1520,8 @@ func (c *Converter) StatusToAPIEdits(ctx context.Context, status *gtsmodel.Statu PollOptions: options, PollVotes: votes, AttachmentIDs: status.AttachmentIDs, - AttachmentDescriptions: nil, // no change from current - CreatedAt: status.UpdatedAt, + AttachmentDescriptions: nil, // no change from current + CreatedAt: status.UpdatedAt(), // falls back to creation }) // Iterate through status edits, starting at newest. diff --git a/internal/typeutils/internaltorss.go b/internal/typeutils/internaltorss.go index 4f4b2b93a..43ca7ba48 100644 --- a/internal/typeutils/internaltorss.go +++ b/internal/typeutils/internaltorss.go @@ -161,7 +161,7 @@ func (c *Converter) StatusToRSSItem(ctx context.Context, s *gtsmodel.Status) (*f Description: description, Id: id, IsPermaLink: "true", - Updated: s.UpdatedAt, + Updated: s.EditedAt, Created: s.CreatedAt, Enclosure: enclosure, Content: content, diff --git a/internal/typeutils/internaltorss_test.go b/internal/typeutils/internaltorss_test.go index 5c4d27208..188f63762 100644 --- a/internal/typeutils/internaltorss_test.go +++ b/internal/typeutils/internaltorss_test.go @@ -50,7 +50,6 @@ func (suite *InternalToRSSTestSuite) TestStatusToRSSItem1() { suite.Equal("@the_mighty_zork@localhost:8080", item.Author.Name) suite.Equal("@the_mighty_zork@localhost:8080 made a new post: \"hello everyone!\"", item.Description) suite.Equal("http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY", item.Id) - suite.EqualValues(1634726437, item.Updated.Unix()) suite.EqualValues(1634726437, item.Created.Unix()) suite.Equal("", item.Enclosure.Length) suite.Equal("", item.Enclosure.Type) @@ -76,7 +75,6 @@ func (suite *InternalToRSSTestSuite) TestStatusToRSSItem2() { suite.Equal("@admin@localhost:8080", item.Author.Name) suite.Equal("@admin@localhost:8080 posted 1 attachment: \"hello world! #welcome ! first post on the instance :rainbow: !\"", item.Description) suite.Equal("http://localhost:8080/@admin/statuses/01F8MH75CBF9JFX4ZAD54N0W0R", item.Id) - suite.EqualValues(1634729805, item.Updated.Unix()) suite.EqualValues(1634729805, item.Created.Unix()) suite.Equal("62529", item.Enclosure.Length) suite.Equal("image/jpeg", item.Enclosure.Type) diff --git a/internal/typeutils/wrap_test.go b/internal/typeutils/wrap_test.go index c2c9c9464..1085c8c66 100644 --- a/internal/typeutils/wrap_test.go +++ b/internal/typeutils/wrap_test.go @@ -131,7 +131,6 @@ func (suite *WrapTestSuite) TestWrapNoteInCreate() { "tag": [], "to": "https://www.w3.org/ns/activitystreams#Public", "type": "Note", - "updated": "2021-10-20T12:40:37+02:00", "url": "http://localhost:8080/@the_mighty_zork/statuses/01F8MHAMCHF6Y650WCRSCP4WMY" }, "published": "2021-10-20T12:40:37+02:00", diff --git a/testrig/testmodels.go b/testrig/testmodels.go index 2b83c2102..81c3a85c5 100644 --- a/testrig/testmodels.go +++ b/testrig/testmodels.go @@ -1432,7 +1432,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { MentionIDs: []string{}, EmojiIDs: []string{"01F8MH9H8E4VG3KDYJR9EGPXCQ"}, CreatedAt: TimeMustParse("2021-10-20T11:36:45Z"), - UpdatedAt: TimeMustParse("2021-10-20T11:36:45Z"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/admin", AccountID: "01F8MH17FWEB39HZJ76B6VXSKF", @@ -1456,7 +1456,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: "🐕🐕🐕🐕🐕", Text: "🐕🐕🐕🐕🐕", CreatedAt: TimeMustParse("2021-10-20T12:36:45Z"), - UpdatedAt: TimeMustParse("2021-10-20T12:36:45Z"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/admin", AccountID: "01F8MH17FWEB39HZJ76B6VXSKF", @@ -1479,7 +1479,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: "hi @the_mighty_zork welcome to the instance!", Text: "hi @the_mighty_zork welcome to the instance!", CreatedAt: TimeMustParse("2021-11-20T13:32:16Z"), - UpdatedAt: TimeMustParse("2021-11-20T13:32:16Z"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/admin", MentionIDs: []string{"01FF26A6BGEKCZFWNEHXB2ZZ6M"}, @@ -1502,7 +1502,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { URI: "http://localhost:8080/users/admin/statuses/01G36SF3V6Y6V5BF9P4R7PQG7G", URL: "http://localhost:8080/@admin/statuses/01G36SF3V6Y6V5BF9P4R7PQG7G", CreatedAt: TimeMustParse("2021-10-20T12:41:37+02:00"), - UpdatedAt: TimeMustParse("2021-10-20T12:41:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/admin", AccountID: "01F8MH17FWEB39HZJ76B6VXSKF", @@ -1526,7 +1526,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: `

Hi @1happyturtle, can I reply?

`, Text: "Hi @1happyturtle, can I reply?", CreatedAt: TimeMustParse("2024-02-20T12:41:37+02:00"), - UpdatedAt: TimeMustParse("2024-02-20T12:41:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/admin", MentionIDs: []string{"01J5QVP69ANF1K4WHES6GA4WXP"}, @@ -1551,7 +1551,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: "hello everyone!", Text: "hello everyone!", CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), - UpdatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/the_mighty_zork", AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF", @@ -1574,7 +1574,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: "this is a Public local-only post that shouldn't federate, but it's still boostable, replyable, and likeable", Text: "this is a Public local-only post that shouldn't federate, but it's still boostable, replyable, and likeable", CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), - UpdatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/the_mighty_zork", AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF", @@ -1597,7 +1597,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: "this is a very personal post that I don't want anyone to interact with at all, and i only want mutuals to see it", Text: "this is a very personal post that I don't want anyone to interact with at all, and i only want mutuals to see it", CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), - UpdatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/the_mighty_zork", AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF", @@ -1632,7 +1632,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Text: "here's a little gif of trent.... and also a cow", AttachmentIDs: []string{"01F8MH7TDVANYKWVE8VVKFPJTJ", "01CDR64G398ADCHXK08WWTHEZ5"}, CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), - UpdatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/the_mighty_zork", AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF", @@ -1656,7 +1656,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Text: "hi!", AttachmentIDs: []string{}, CreatedAt: TimeMustParse("2022-05-20T11:37:55Z"), - UpdatedAt: TimeMustParse("2022-05-20T11:37:55Z"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/the_mighty_zork", AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF", @@ -1680,7 +1680,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Text: "what do you think of sloths?", AttachmentIDs: nil, CreatedAt: TimeMustParse("2022-05-20T11:41:10Z"), - UpdatedAt: TimeMustParse("2022-05-20T11:41:10Z"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/the_mighty_zork", AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF", @@ -1704,7 +1704,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: "

Here's a bunch of HTML, read it and weep, weep then!

<section class="about-user">\n    <div class="col-header">\n        <h2>About</h2>\n    </div>            \n    <div class="fields">\n        <h3 class="sr-only">Fields</h3>\n        <dl>\n            <div class="field">\n                <dt>should you follow me?</dt>\n                <dd>maybe!</dd>\n            </div>\n            <div class="field">\n                <dt>age</dt>\n                <dd>120</dd>\n            </div>\n        </dl>\n    </div>\n    <div class="bio">\n        <h3 class="sr-only">Bio</h3>\n        <p>i post about things that concern me</p>\n    </div>\n    <div class="sr-only" role="group">\n        <h3 class="sr-only">Stats</h3>\n        <span>Joined in Jun, 2022.</span>\n        <span>8 posts.</span>\n        <span>Followed by 1.</span>\n        <span>Following 1.</span>\n    </div>\n    <div class="accountstats" aria-hidden="true">\n        <b>Joined</b><time datetime="2022-06-04T13:12:00.000Z">Jun, 2022</time>\n        <b>Posts</b><span>8</span>\n        <b>Followed by</b><span>1</span>\n        <b>Following</b><span>1</span>\n    </div>\n</section>\n

There, hope you liked that!

", Text: "Here's a bunch of HTML, read it and weep, weep then!\n\n```html\n
\n
\n

About

\n
\n
\n

Fields

\n
\n
\n
should you follow me?
\n
maybe!
\n
\n
\n
age
\n
120
\n

Stats

\n Joined in Jun, 2022.\n 8 posts.\n Followed by 1.\n Following 1.\n
\n
\n Joined\n Posts8\n Followed by1\n Following1\n
\n
\n```\n\nThere, hope you liked that!", CreatedAt: TimeMustParse("2023-12-10T11:24:00+02:00"), - UpdatedAt: TimeMustParse("2023-12-10T11:24:00+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/the_mighty_zork", AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF", @@ -1728,7 +1728,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Text: "Thanks! Here's a NIN track", AttachmentIDs: []string{"01J2M20K6K9XQC4WSB961YJHV6"}, CreatedAt: TimeMustParse("2024-01-10T11:24:00+02:00"), - UpdatedAt: TimeMustParse("2024-01-10T11:24:00+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/the_mighty_zork", AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF", @@ -1754,7 +1754,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { ContentWarning: "edited status", AttachmentIDs: nil, CreatedAt: TimeMustParse("2024-11-01T11:00:00+02:00"), - UpdatedAt: TimeMustParse("2024-11-01T11:02:00+02:00"), + EditedAt: TimeMustParse("2024-11-01T11:02:00+02:00"), Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/the_mighty_zork", AccountID: "01F8MH1H7YV1Z7D2C8K2730QBF", @@ -1778,7 +1778,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: "🐢 hi everyone i post about turtles 🐢", Text: "🐢 hi everyone i post about turtles 🐢", CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), - UpdatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/1happyturtle", AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF", @@ -1801,7 +1801,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: "🐢 this one is federated, likeable, and boostable but not replyable 🐢", Text: "🐢 this one is federated, likeable, and boostable but not replyable 🐢", CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), - UpdatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/1happyturtle", AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF", @@ -1835,7 +1835,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: "🐢 i don't mind people sharing and liking this one but I want to moderate replies to it 🐢", Text: "🐢 i don't mind people sharing and liking this one but I want to moderate replies to it 🐢", CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), - UpdatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/1happyturtle", AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF", @@ -1870,7 +1870,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: "🐢 this is a public status but I want it local only and not boostable 🐢", Text: "🐢 this is a public status but I want it local only and not boostable 🐢", CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), - UpdatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/1happyturtle", AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF", @@ -1904,7 +1904,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: "🐢 @the_mighty_zork hi zork! 🐢", Text: "🐢 @the_mighty_zork hi zork! 🐢", CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), - UpdatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/1happyturtle", MentionIDs: []string{"01FDF2HM2NF6FSRZCDEDV451CN"}, @@ -1930,7 +1930,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: "🐢 @the_mighty_zork hi zork, this is a direct message, shhhhhh! 🐢", Text: "🐢 @the_mighty_zork hi zork, this is a direct message, shhhhhh! 🐢", CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), - UpdatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/1happyturtle", MentionIDs: []string{"01FDF2HM2NF6FSRZCDEDV451CN"}, @@ -1958,7 +1958,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Text: "🐢 hi followers! did u know i'm a turtle? 🐢", AttachmentIDs: []string{}, CreatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), - UpdatedAt: TimeMustParse("2021-10-20T12:40:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/1happyturtle", AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF", @@ -1982,7 +1982,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Text: "hey everyone i got stuck in a shed. any ideas for how to get out?", AttachmentIDs: nil, CreatedAt: TimeMustParse("2021-07-28T10:40:37+02:00"), - UpdatedAt: TimeMustParse("2021-07-28T10:40:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/1happyturtle", AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF", @@ -2008,7 +2008,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { ContentWarning: "edit with media attachments", AttachmentIDs: []string{"01JDQ164HM08SGJ7ZEK9003Z4B"}, CreatedAt: TimeMustParse("2024-11-01T10:00:00+02:00"), - UpdatedAt: TimeMustParse("2024-11-01T10:03:00+02:00"), + EditedAt: TimeMustParse("2024-11-01T10:03:00+02:00"), Local: util.Ptr(true), AccountURI: "http://localhost:8080/users/the_mighty_zork", AccountID: "01F8MH5NBDF2MV7CTC4Q5128HF", @@ -2032,7 +2032,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: "dark souls status bot: \"thoughts of dog\"", AttachmentIDs: []string{"01FVW7RXPQ8YJHTEXYPE7Q8ZY0"}, CreatedAt: TimeMustParse("2021-09-20T12:40:37+02:00"), - UpdatedAt: TimeMustParse("2021-09-20T12:40:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(false), AccountURI: "http://fossbros-anonymous.io/users/foss_satan", MentionIDs: []string{}, @@ -2057,7 +2057,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: "what products should i buy at the grocery store?", AttachmentIDs: []string{"01FVW7RXPQ8YJHTEXYPE7Q8ZY0"}, CreatedAt: TimeMustParse("2021-09-11T11:40:37+02:00"), - UpdatedAt: TimeMustParse("2021-09-11T11:40:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(false), AccountURI: "http://fossbros-anonymous.io/users/foss_satan", AccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX", @@ -2082,7 +2082,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: "what products should i buy at the grocery store? (now an endless poll!)", AttachmentIDs: []string{"01FVW7RXPQ8YJHTEXYPE7Q8ZY0"}, CreatedAt: TimeMustParse("2021-09-11T11:40:37+02:00"), - UpdatedAt: TimeMustParse("2021-09-11T11:40:37+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(false), AccountURI: "http://fossbros-anonymous.io/users/foss_satan", AccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX", @@ -2109,7 +2109,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { ContentWarning: "", AttachmentIDs: nil, CreatedAt: TimeMustParse("2024-11-01T09:00:00+02:00"), - UpdatedAt: TimeMustParse("2024-11-01T09:02:00+02:00"), + EditedAt: TimeMustParse("2024-11-01T09:02:00+02:00"), Local: util.Ptr(false), AccountURI: "http://fossbros-anonymous.io/users/foss_satan", AccountID: "01F8MH5ZK5VRH73AKHQM6Y9VNX", @@ -2134,7 +2134,7 @@ func NewTestStatuses() map[string]*gtsmodel.Status { Content: `

hi @admin here's some media for ya

`, AttachmentIDs: []string{"01HE7Y3C432WRSNS10EZM86SA5", "01HE7ZFX9GKA5ZZVD4FACABSS9", "01HE88YG74PVAB81PX2XA9F3FG"}, CreatedAt: TimeMustParse("2023-11-02T12:44:25+02:00"), - UpdatedAt: TimeMustParse("2023-11-02T12:44:25+02:00"), + EditedAt: time.Time{}, Local: util.Ptr(false), AccountURI: "http://example.org/users/Some_User", MentionIDs: []string{"01HE7XQNMKTVC8MNPCE1JGK4J3"},