diff options
Diffstat (limited to 'internal/typeutils')
-rw-r--r-- | internal/typeutils/astointernal.go | 74 | ||||
-rw-r--r-- | internal/typeutils/converter.go | 210 | ||||
-rw-r--r-- | internal/typeutils/converter_test.go | 6 | ||||
-rw-r--r-- | internal/typeutils/defaulticons.go | 4 | ||||
-rw-r--r-- | internal/typeutils/internal.go | 6 | ||||
-rw-r--r-- | internal/typeutils/internaltoas.go | 297 | ||||
-rw-r--r-- | internal/typeutils/internaltofrontend.go | 174 | ||||
-rw-r--r-- | internal/typeutils/internaltorss.go | 8 | ||||
-rw-r--r-- | internal/typeutils/util.go | 10 | ||||
-rw-r--r-- | internal/typeutils/wrap.go | 10 |
10 files changed, 340 insertions, 459 deletions
diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go index 7b1ba0396..81dbc6f40 100644 --- a/internal/typeutils/astointernal.go +++ b/internal/typeutils/astointernal.go @@ -34,7 +34,10 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/util" ) -func (c *converter) ASRepresentationToAccount(ctx context.Context, accountable ap.Accountable, accountDomain string) (*gtsmodel.Account, error) { +// ASRepresentationToAccount converts a remote account/person/application representation into a gts model account. +// +// If accountDomain is provided then this value will be used as the account's Domain, else the AP ID host. +func (c *Converter) ASRepresentationToAccount(ctx context.Context, accountable ap.Accountable, accountDomain string) (*gtsmodel.Account, error) { // first check if we actually already know this account uriProp := accountable.GetJSONLDId() if uriProp == nil || !uriProp.IsIRI() { @@ -213,7 +216,7 @@ func (c *converter) ASRepresentationToAccount(ctx context.Context, accountable a return acct, nil } -func (c *converter) extractAttachments(i ap.WithAttachment) []*gtsmodel.MediaAttachment { +func (c *Converter) extractAttachments(i ap.WithAttachment) []*gtsmodel.MediaAttachment { attachmentProp := i.GetActivityStreamsAttachment() if attachmentProp == nil { return nil @@ -245,7 +248,8 @@ func (c *converter) extractAttachments(i ap.WithAttachment) []*gtsmodel.MediaAtt return attachments } -func (c *converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusable) (*gtsmodel.Status, error) { +// ASStatus converts a remote activitystreams 'status' representation into a gts model status. +func (c *Converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusable) (*gtsmodel.Status, error) { status := new(gtsmodel.Status) // status.URI @@ -341,7 +345,7 @@ func (c *converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab } accountURI := attributedTo.String() - account, err := c.db.GetAccountByURI(ctx, accountURI) + account, err := c.state.DB.GetAccountByURI(ctx, accountURI) if err != nil { err = gtserror.Newf("db error getting status author account %s: %w", accountURI, err) return nil, err @@ -364,7 +368,7 @@ func (c *converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab status.InReplyToURI = inReplyToURI // Check if we already have the replied-to status. - inReplyTo, err := c.db.GetStatusByURI(ctx, inReplyToURI) + inReplyTo, err := c.state.DB.GetStatusByURI(ctx, inReplyToURI) if err != nil && !errors.Is(err, db.ErrNoEntries) { // Real database error. err = gtserror.Newf("db error getting replied-to status %s: %w", inReplyToURI, err) @@ -417,7 +421,8 @@ func (c *converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab return status, nil } -func (c *converter) ASFollowToFollowRequest(ctx context.Context, followable ap.Followable) (*gtsmodel.FollowRequest, error) { +// ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow request. +func (c *Converter) ASFollowToFollowRequest(ctx context.Context, followable ap.Followable) (*gtsmodel.FollowRequest, error) { idProp := followable.GetJSONLDId() if idProp == nil || !idProp.IsIRI() { return nil, errors.New("no id property set on follow, or was not an iri") @@ -428,7 +433,7 @@ func (c *converter) ASFollowToFollowRequest(ctx context.Context, followable ap.F if err != nil { return nil, errors.New("error extracting actor property from follow") } - originAccount, err := c.db.GetAccountByURI(ctx, origin.String()) + originAccount, err := c.state.DB.GetAccountByURI(ctx, origin.String()) if err != nil { return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } @@ -437,7 +442,7 @@ func (c *converter) ASFollowToFollowRequest(ctx context.Context, followable ap.F if err != nil { return nil, errors.New("error extracting object property from follow") } - targetAccount, err := c.db.GetAccountByURI(ctx, target.String()) + targetAccount, err := c.state.DB.GetAccountByURI(ctx, target.String()) if err != nil { return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } @@ -451,7 +456,8 @@ func (c *converter) ASFollowToFollowRequest(ctx context.Context, followable ap.F return followRequest, nil } -func (c *converter) ASFollowToFollow(ctx context.Context, followable ap.Followable) (*gtsmodel.Follow, error) { +// ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow. +func (c *Converter) ASFollowToFollow(ctx context.Context, followable ap.Followable) (*gtsmodel.Follow, error) { idProp := followable.GetJSONLDId() if idProp == nil || !idProp.IsIRI() { return nil, errors.New("no id property set on follow, or was not an iri") @@ -462,7 +468,7 @@ func (c *converter) ASFollowToFollow(ctx context.Context, followable ap.Followab if err != nil { return nil, errors.New("error extracting actor property from follow") } - originAccount, err := c.db.GetAccountByURI(ctx, origin.String()) + originAccount, err := c.state.DB.GetAccountByURI(ctx, origin.String()) if err != nil { return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } @@ -471,7 +477,7 @@ func (c *converter) ASFollowToFollow(ctx context.Context, followable ap.Followab if err != nil { return nil, errors.New("error extracting object property from follow") } - targetAccount, err := c.db.GetAccountByURI(ctx, target.String()) + targetAccount, err := c.state.DB.GetAccountByURI(ctx, target.String()) if err != nil { return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } @@ -485,7 +491,8 @@ func (c *converter) ASFollowToFollow(ctx context.Context, followable ap.Followab return follow, nil } -func (c *converter) ASLikeToFave(ctx context.Context, likeable ap.Likeable) (*gtsmodel.StatusFave, error) { +// ASLikeToFave converts a remote activitystreams 'like' representation into a gts model status fave. +func (c *Converter) ASLikeToFave(ctx context.Context, likeable ap.Likeable) (*gtsmodel.StatusFave, error) { idProp := likeable.GetJSONLDId() if idProp == nil || !idProp.IsIRI() { return nil, errors.New("no id property set on like, or was not an iri") @@ -496,7 +503,7 @@ func (c *converter) ASLikeToFave(ctx context.Context, likeable ap.Likeable) (*gt if err != nil { return nil, errors.New("error extracting actor property from like") } - originAccount, err := c.db.GetAccountByURI(ctx, origin.String()) + originAccount, err := c.state.DB.GetAccountByURI(ctx, origin.String()) if err != nil { return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } @@ -506,7 +513,7 @@ func (c *converter) ASLikeToFave(ctx context.Context, likeable ap.Likeable) (*gt return nil, errors.New("error extracting object property from like") } - targetStatus, err := c.db.GetStatusByURI(ctx, target.String()) + targetStatus, err := c.state.DB.GetStatusByURI(ctx, target.String()) if err != nil { return nil, fmt.Errorf("error extracting status with uri %s from the database: %s", target.String(), err) } @@ -515,7 +522,7 @@ func (c *converter) ASLikeToFave(ctx context.Context, likeable ap.Likeable) (*gt if targetStatus.Account != nil { targetAccount = targetStatus.Account } else { - a, err := c.db.GetAccountByID(ctx, targetStatus.AccountID) + a, err := c.state.DB.GetAccountByID(ctx, targetStatus.AccountID) if err != nil { return nil, fmt.Errorf("error extracting account with id %s from the database: %s", targetStatus.AccountID, err) } @@ -533,7 +540,8 @@ func (c *converter) ASLikeToFave(ctx context.Context, likeable ap.Likeable) (*gt }, nil } -func (c *converter) ASBlockToBlock(ctx context.Context, blockable ap.Blockable) (*gtsmodel.Block, error) { +// ASBlockToBlock converts a remote activity streams 'block' representation into a gts model block. +func (c *Converter) ASBlockToBlock(ctx context.Context, blockable ap.Blockable) (*gtsmodel.Block, error) { idProp := blockable.GetJSONLDId() if idProp == nil || !idProp.IsIRI() { return nil, errors.New("ASBlockToBlock: no id property set on block, or was not an iri") @@ -544,7 +552,7 @@ func (c *converter) ASBlockToBlock(ctx context.Context, blockable ap.Blockable) if err != nil { return nil, errors.New("ASBlockToBlock: error extracting actor property from block") } - originAccount, err := c.db.GetAccountByURI(ctx, origin.String()) + originAccount, err := c.state.DB.GetAccountByURI(ctx, origin.String()) if err != nil { return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } @@ -554,7 +562,7 @@ func (c *converter) ASBlockToBlock(ctx context.Context, blockable ap.Blockable) return nil, errors.New("ASBlockToBlock: error extracting object property from block") } - targetAccount, err := c.db.GetAccountByURI(ctx, target.String()) + targetAccount, err := c.state.DB.GetAccountByURI(ctx, target.String()) if err != nil { return nil, fmt.Errorf("error extracting account with uri %s from the database: %s", origin.String(), err) } @@ -568,6 +576,19 @@ func (c *converter) ASBlockToBlock(ctx context.Context, blockable ap.Blockable) }, nil } +// ASAnnounceToStatus converts an activitystreams 'announce' into a status. +// +// The returned bool indicates whether this status is new (true) or not new (false). +// +// In other words, if the status is already in the database with the ID set on the announceable, then that will be returned, +// the returned bool will be false, and no further processing is necessary. If the returned bool is true, indicating +// that this is a new announce, then further processing will be necessary, because the returned status will be bareboned and +// require further dereferencing. +// +// This is useful when multiple users on an instance might receive the same boost, and we only want to process the boost once. +// +// NOTE -- this is different from one status being boosted multiple times! In this case, new boosts should indeed be created. +// // Implementation note: this function creates and returns a boost WRAPPER // status which references the boosted status in its BoostOf field. No // dereferencing is done on the boosted status by this function. Callers @@ -577,7 +598,7 @@ func (c *converter) ASBlockToBlock(ctx context.Context, blockable ap.Blockable) // The returned boolean indicates whether or not the boost has already been // seen before by this instance. If it was, then status.BoostOf should be a // fully filled-out status. If not, then only status.BoostOf.URI will be set. -func (c *converter) ASAnnounceToStatus(ctx context.Context, announceable ap.Announceable) (*gtsmodel.Status, bool, error) { +func (c *Converter) ASAnnounceToStatus(ctx context.Context, announceable ap.Announceable) (*gtsmodel.Status, bool, error) { // Ensure item has an ID URI set. _, statusURIStr, err := getURI(announceable) if err != nil { @@ -591,7 +612,7 @@ func (c *converter) ASAnnounceToStatus(ctx context.Context, announceable ap.Anno ) // Check if we already have this boost in the database. - status, err = c.db.GetStatusByURI(ctx, statusURIStr) + status, err = c.state.DB.GetStatusByURI(ctx, statusURIStr) if err != nil && !errors.Is(err, db.ErrNoEntries) { // Real database error. err = gtserror.Newf("db error trying to get status with uri %s: %w", statusURIStr, err) @@ -648,7 +669,7 @@ func (c *converter) ASAnnounceToStatus(ctx context.Context, announceable ap.Anno // This should have been dereferenced already before // we hit this point so we can confidently error out // if we don't have it. - account, err := c.db.GetAccountByURI(ctx, accountURIStr) + account, err := c.state.DB.GetAccountByURI(ctx, accountURIStr) if err != nil { err = gtserror.Newf("db error trying to get account with uri %s: %w", accountURIStr, err) return nil, isNew, err @@ -678,7 +699,8 @@ func (c *converter) ASAnnounceToStatus(ctx context.Context, announceable ap.Anno return status, isNew, nil } -func (c *converter) ASFlagToReport(ctx context.Context, flaggable ap.Flaggable) (*gtsmodel.Report, error) { +// ASFlagToReport converts a remote activitystreams 'flag' representation into a gts model report. +func (c *Converter) ASFlagToReport(ctx context.Context, flaggable ap.Flaggable) (*gtsmodel.Report, error) { // Extract flag uri. idProp := flaggable.GetJSONLDId() if idProp == nil || !idProp.IsIRI() { @@ -692,7 +714,7 @@ func (c *converter) ASFlagToReport(ctx context.Context, flaggable ap.Flaggable) if err != nil { return nil, fmt.Errorf("ASFlagToReport: error extracting actor: %w", err) } - account, err := c.db.GetAccountByURI(ctx, actor.String()) + account, err := c.state.DB.GetAccountByURI(ctx, actor.String()) if err != nil { return nil, fmt.Errorf("ASFlagToReport: error in db fetching account with uri %s: %w", actor.String(), err) } @@ -744,7 +766,7 @@ func (c *converter) ASFlagToReport(ctx context.Context, flaggable ap.Flaggable) if targetAccountURI == nil { return nil, errors.New("ASFlagToReport: flaggable objects contained no recognizable target account uri") } - targetAccount, err := c.db.GetAccountByURI(ctx, targetAccountURI.String()) + targetAccount, err := c.state.DB.GetAccountByURI(ctx, targetAccountURI.String()) if err != nil { if errors.Is(err, db.ErrNoEntries) { return nil, fmt.Errorf("ASFlagToReport: account with uri %s could not be found in the db", targetAccountURI.String()) @@ -761,13 +783,13 @@ func (c *converter) ASFlagToReport(ctx context.Context, flaggable ap.Flaggable) statusURIString := statusURI.String() // try getting this status by URI first, then URL - status, err := c.db.GetStatusByURI(ctx, statusURIString) + status, err := c.state.DB.GetStatusByURI(ctx, statusURIString) if err != nil { if !errors.Is(err, db.ErrNoEntries) { return nil, fmt.Errorf("ASFlagToReport: db error getting status with uri %s: %w", statusURIString, err) } - status, err = c.db.GetStatusByURL(ctx, statusURIString) + status, err = c.state.DB.GetStatusByURL(ctx, statusURIString) if err != nil { if !errors.Is(err, db.ErrNoEntries) { return nil, fmt.Errorf("ASFlagToReport: db error getting status with url %s: %w", statusURIString, err) diff --git a/internal/typeutils/converter.go b/internal/typeutils/converter.go index af77734cc..5f849c39d 100644 --- a/internal/typeutils/converter.go +++ b/internal/typeutils/converter.go @@ -18,218 +18,20 @@ package typeutils import ( - "context" - "net/url" "sync" - "github.com/gorilla/feeds" - "github.com/superseriousbusiness/activity/streams/vocab" - "github.com/superseriousbusiness/gotosocial/internal/ap" - apimodel "github.com/superseriousbusiness/gotosocial/internal/api/model" - "github.com/superseriousbusiness/gotosocial/internal/db" - "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/state" ) -// TypeConverter is an interface for the common action of converting between apimodule (frontend, serializable) models, -// internal gts models used in the database, and activitypub models used in federation. -// -// It requires access to the database because many of the conversions require pulling out database entries and counting them etc. -// That said, it *absolutely should not* manipulate database entries in any way, only examine them. -type TypeConverter interface { - /* - INTERNAL (gts) MODEL TO FRONTEND (api) MODEL - */ - - // AccountToAPIAccountSensitive takes a db model account as a param, and returns a populated apitype account, or an error - // if something goes wrong. The returned account should be ready to serialize on an API level, and may have sensitive fields, - // so serve it only to an authorized user who should have permission to see it. - AccountToAPIAccountSensitive(ctx context.Context, account *gtsmodel.Account) (*apimodel.Account, error) - // AccountToAPIAccountPublic takes a db model account as a param, and returns a populated apitype account, or an error - // if something goes wrong. The returned account should be ready to serialize on an API level, and may NOT have sensitive fields. - // In other words, this is the public record that the server has of an account. - AccountToAPIAccountPublic(ctx context.Context, account *gtsmodel.Account) (*apimodel.Account, error) - // AccountToAPIAccountBlocked takes a db model account as a param, and returns a apitype account, or an error if - // something goes wrong. The returned account will be a bare minimum representation of the account. This function should be used - // when someone wants to view an account they've blocked. - AccountToAPIAccountBlocked(ctx context.Context, account *gtsmodel.Account) (*apimodel.Account, error) - // AppToAPIAppSensitive takes a db model application as a param, and returns a populated apitype application, or an error - // if something goes wrong. The returned application should be ready to serialize on an API level, and may have sensitive fields - // (such as client id and client secret), so serve it only to an authorized user who should have permission to see it. - AppToAPIAppSensitive(ctx context.Context, application *gtsmodel.Application) (*apimodel.Application, error) - // AppToAPIAppPublic takes a db model application as a param, and returns a populated apitype application, or an error - // if something goes wrong. The returned application should be ready to serialize on an API level, and has sensitive - // fields sanitized so that it can be served to non-authorized accounts without revealing any private information. - AppToAPIAppPublic(ctx context.Context, application *gtsmodel.Application) (*apimodel.Application, error) - // AttachmentToAPIAttachment converts a gts model media attacahment into its api representation for serialization on the API. - AttachmentToAPIAttachment(ctx context.Context, attachment *gtsmodel.MediaAttachment) (apimodel.Attachment, error) - // MentionToAPIMention converts a gts model mention into its api (frontend) representation for serialization on the API. - MentionToAPIMention(ctx context.Context, m *gtsmodel.Mention) (apimodel.Mention, error) - // EmojiToAPIEmoji converts a gts model emoji into its api (frontend) representation for serialization on the API. - EmojiToAPIEmoji(ctx context.Context, e *gtsmodel.Emoji) (apimodel.Emoji, error) - // EmojiToAdminAPIEmoji converts a gts model emoji into an API representation with extra admin information. - EmojiToAdminAPIEmoji(ctx context.Context, e *gtsmodel.Emoji) (*apimodel.AdminEmoji, error) - // EmojiCategoryToAPIEmojiCategory converts a gts model emoji category into its api (frontend) representation. - EmojiCategoryToAPIEmojiCategory(ctx context.Context, category *gtsmodel.EmojiCategory) (*apimodel.EmojiCategory, error) - // TagToAPITag converts a gts model tag into its api (frontend) representation for serialization on the API. - // If stubHistory is set to 'true', then the 'history' field of the tag will be populated with a pointer to an empty slice, for API compatibility reasons. - TagToAPITag(ctx context.Context, t *gtsmodel.Tag, stubHistory bool) (apimodel.Tag, error) - // StatusToAPIStatus converts a gts model status into its api (frontend) representation for serialization on the API. - // - // Requesting account can be nil. - StatusToAPIStatus(ctx context.Context, s *gtsmodel.Status, requestingAccount *gtsmodel.Account) (*apimodel.Status, error) - // VisToAPIVis converts a gts visibility into its api equivalent - VisToAPIVis(ctx context.Context, m gtsmodel.Visibility) apimodel.Visibility - // InstanceToAPIV1Instance converts a gts instance into its api equivalent for serving at /api/v1/instance - InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Instance) (*apimodel.InstanceV1, error) - // InstanceToAPIV2Instance converts a gts instance into its api equivalent for serving at /api/v2/instance - InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Instance) (*apimodel.InstanceV2, error) - // InstanceRulesToAPIRules converts all local instance rules into their api equivalent for serving at /api/v1/instance/rules - InstanceRulesToAPIRules(r []gtsmodel.Rule) []apimodel.InstanceRule - // InstanceRuleToAdminAPIRule converts a local instance rule into its api equivalent for serving at /api/v1/admin/instance/rules/:id - InstanceRuleToAdminAPIRule(r *gtsmodel.Rule) *apimodel.AdminInstanceRule - // RelationshipToAPIRelationship converts a gts relationship into its api equivalent for serving in various places - RelationshipToAPIRelationship(ctx context.Context, r *gtsmodel.Relationship) (*apimodel.Relationship, error) - // NotificationToAPINotification converts a gts notification into a api notification - NotificationToAPINotification(ctx context.Context, n *gtsmodel.Notification) (*apimodel.Notification, error) - // DomainPermToAPIDomainPerm converts a gts model domin block or allow into an api domain permission. - DomainPermToAPIDomainPerm(ctx context.Context, d gtsmodel.DomainPermission, export bool) (*apimodel.DomainPermission, error) - // ReportToAPIReport converts a gts model report into an api model report, for serving at /api/v1/reports - ReportToAPIReport(ctx context.Context, r *gtsmodel.Report) (*apimodel.Report, error) - // ReportToAdminAPIReport converts a gts model report into an admin view report, for serving at /api/v1/admin/reports - ReportToAdminAPIReport(ctx context.Context, r *gtsmodel.Report, requestingAccount *gtsmodel.Account) (*apimodel.AdminReport, error) - // ListToAPIList converts one gts model list into an api model list, for serving at /api/v1/lists/{id} - ListToAPIList(ctx context.Context, l *gtsmodel.List) (*apimodel.List, error) - // MarkersToAPIMarker converts several gts model markers into an api marker, for serving at /api/v1/markers - MarkersToAPIMarker(ctx context.Context, markers []*gtsmodel.Marker) (*apimodel.Marker, error) - - /* - INTERNAL (gts) MODEL TO FRONTEND (rss) MODEL - */ - - StatusToRSSItem(ctx context.Context, s *gtsmodel.Status) (*feeds.Item, error) - - /* - ACTIVITYSTREAMS MODEL TO INTERNAL (gts) MODEL - */ - - // ASRepresentationToAccount converts a remote account/person/application representation into a gts model account. - // - // If accountDomain is provided then this value will be used as the account's Domain, else the AP ID host. - ASRepresentationToAccount(ctx context.Context, accountable ap.Accountable, accountDomain string) (*gtsmodel.Account, error) - // ASStatus converts a remote activitystreams 'status' representation into a gts model status. - ASStatusToStatus(ctx context.Context, statusable ap.Statusable) (*gtsmodel.Status, error) - // ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow request. - ASFollowToFollowRequest(ctx context.Context, followable ap.Followable) (*gtsmodel.FollowRequest, error) - // ASFollowToFollowRequest converts a remote activitystreams `follow` representation into gts model follow. - ASFollowToFollow(ctx context.Context, followable ap.Followable) (*gtsmodel.Follow, error) - // ASLikeToFave converts a remote activitystreams 'like' representation into a gts model status fave. - ASLikeToFave(ctx context.Context, likeable ap.Likeable) (*gtsmodel.StatusFave, error) - // ASBlockToBlock converts a remote activity streams 'block' representation into a gts model block. - ASBlockToBlock(ctx context.Context, blockable ap.Blockable) (*gtsmodel.Block, error) - // ASAnnounceToStatus converts an activitystreams 'announce' into a status. - // - // The returned bool indicates whether this status is new (true) or not new (false). - // - // In other words, if the status is already in the database with the ID set on the announceable, then that will be returned, - // the returned bool will be false, and no further processing is necessary. If the returned bool is true, indicating - // that this is a new announce, then further processing will be necessary, because the returned status will be bareboned and - // require further dereferencing. - // - // This is useful when multiple users on an instance might receive the same boost, and we only want to process the boost once. - // - // NOTE -- this is different from one status being boosted multiple times! In this case, new boosts should indeed be created. - ASAnnounceToStatus(ctx context.Context, announceable ap.Announceable) (status *gtsmodel.Status, new bool, err error) - // ASFlagToReport converts a remote activitystreams 'flag' representation into a gts model report. - ASFlagToReport(ctx context.Context, flaggable ap.Flaggable) (report *gtsmodel.Report, err error) - - /* - INTERNAL (gts) MODEL TO ACTIVITYSTREAMS MODEL - */ - - // AccountToAS converts a gts model account into an activity streams person, suitable for federation - AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) - // AccountToASMinimal converts a gts model account into an activity streams person, suitable for federation. - // - // The returned account will just have the Type, Username, PublicKey, and ID properties set. This is - // suitable for serving to requesters to whom we want to give as little information as possible because - // we don't trust them (yet). - AccountToASMinimal(ctx context.Context, a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) - // StatusToAS converts a gts model status into an activity streams note, suitable for federation - StatusToAS(ctx context.Context, s *gtsmodel.Status) (vocab.ActivityStreamsNote, error) - // StatusToASDelete converts a gts model status into a Delete of that status, using just the - // URI of the status as object, and addressing the Delete appropriately. - StatusToASDelete(ctx context.Context, status *gtsmodel.Status) (vocab.ActivityStreamsDelete, error) - // FollowToASFollow converts a gts model Follow into an activity streams Follow, suitable for federation - FollowToAS(ctx context.Context, f *gtsmodel.Follow) (vocab.ActivityStreamsFollow, error) - // MentionToAS converts a gts model mention into an activity streams Mention, suitable for federation - MentionToAS(ctx context.Context, m *gtsmodel.Mention) (vocab.ActivityStreamsMention, error) - // EmojiToAS converts a gts emoji into a mastodon ns Emoji, suitable for federation - EmojiToAS(ctx context.Context, e *gtsmodel.Emoji) (vocab.TootEmoji, error) - // TagToAS converts a gts model tag into a toot Hashtag, suitable for federation. - TagToAS(ctx context.Context, t *gtsmodel.Tag) (vocab.TootHashtag, error) - // AttachmentToAS converts a gts model media attachment into an activity streams Attachment, suitable for federation - AttachmentToAS(ctx context.Context, a *gtsmodel.MediaAttachment) (vocab.ActivityStreamsDocument, error) - // FaveToAS converts a gts model status fave into an activityStreams LIKE, suitable for federation. - FaveToAS(ctx context.Context, f *gtsmodel.StatusFave) (vocab.ActivityStreamsLike, error) - // BoostToAS converts a gts model boost into an activityStreams ANNOUNCE, suitable for federation - BoostToAS(ctx context.Context, boostWrapperStatus *gtsmodel.Status, boostingAccount *gtsmodel.Account, boostedAccount *gtsmodel.Account) (vocab.ActivityStreamsAnnounce, error) - // BlockToAS converts a gts model block into an activityStreams BLOCK, suitable for federation. - BlockToAS(ctx context.Context, block *gtsmodel.Block) (vocab.ActivityStreamsBlock, error) - // StatusToASRepliesCollection converts a gts model status into an activityStreams REPLIES collection. - StatusToASRepliesCollection(ctx context.Context, status *gtsmodel.Status, onlyOtherAccounts bool) (vocab.ActivityStreamsCollection, error) - // StatusURIsToASRepliesPage returns a collection page with appropriate next/part of pagination. - StatusURIsToASRepliesPage(ctx context.Context, status *gtsmodel.Status, onlyOtherAccounts bool, minID string, replies map[string]*url.URL) (vocab.ActivityStreamsCollectionPage, error) - // OutboxToASCollection returns an ordered collection with appropriate id, next, and last fields. - // The returned collection won't have any actual entries; just links to where entries can be obtained. - OutboxToASCollection(ctx context.Context, outboxID string) (vocab.ActivityStreamsOrderedCollection, error) - // StatusesToASOutboxPage returns an ordered collection page using the given statuses and parameters as contents. - // - // The maxID and minID should be the parameters that were passed to the database to obtain the given statuses. - // These will be used to create the 'id' field of the collection. - // - // OutboxID is used to create the 'partOf' field in the collection. - // - // Appropriate 'next' and 'prev' fields will be created based on the highest and lowest IDs present in the statuses slice. - StatusesToASOutboxPage(ctx context.Context, outboxID string, maxID string, minID string, statuses []*gtsmodel.Status) (vocab.ActivityStreamsOrderedCollectionPage, error) - // StatusesToASFeaturedCollection converts a slice of statuses into an ordered collection - // of URIs, suitable for serializing and serving via the activitypub API. - StatusesToASFeaturedCollection(ctx context.Context, featuredCollectionID string, statuses []*gtsmodel.Status) (vocab.ActivityStreamsOrderedCollection, error) - // ReportToASFlag converts a gts model report into an activitystreams FLAG, suitable for federation. - ReportToASFlag(ctx context.Context, r *gtsmodel.Report) (vocab.ActivityStreamsFlag, error) - - /* - INTERNAL (gts) MODEL TO INTERNAL MODEL - */ - - // FollowRequestToFollow just converts a follow request into a follow, that's it! No bells and whistles. - FollowRequestToFollow(ctx context.Context, f *gtsmodel.FollowRequest) *gtsmodel.Follow - // StatusToBoost wraps the given status into a boosting status. - StatusToBoost(ctx context.Context, s *gtsmodel.Status, boostingAccount *gtsmodel.Account) (*gtsmodel.Status, error) - - /* - WRAPPER CONVENIENCE FUNCTIONS - */ - - // WrapPersonInUpdate - WrapPersonInUpdate(person vocab.ActivityStreamsPerson, originAccount *gtsmodel.Account) (vocab.ActivityStreamsUpdate, error) - // WrapNoteInCreate wraps a Note with a Create activity. - // - // If objectIRIOnly is set to true, then the function won't put the *entire* note in the Object field of the Create, - // but just the AP URI of the note. This is useful in cases where you want to give a remote server something to dereference, - // and still have control over whether or not they're allowed to actually see the contents. - WrapNoteInCreate(note vocab.ActivityStreamsNote, objectIRIOnly bool) (vocab.ActivityStreamsCreate, error) -} - -type converter struct { - db db.DB +type Converter struct { + state *state.State defaultAvatars []string randAvatars sync.Map } -// NewConverter returns a new Converter -func NewConverter(db db.DB) TypeConverter { - return &converter{ - db: db, +func NewConverter(state *state.State) *Converter { + return &Converter{ + state: state, defaultAvatars: populateDefaultAvatars(), } } diff --git a/internal/typeutils/converter_test.go b/internal/typeutils/converter_test.go index a91e6a157..74b213431 100644 --- a/internal/typeutils/converter_test.go +++ b/internal/typeutils/converter_test.go @@ -481,7 +481,7 @@ type TypeUtilsTestSuite struct { testReports map[string]*gtsmodel.Report testMentions map[string]*gtsmodel.Mention - typeconverter typeutils.TypeConverter + typeconverter *typeutils.Converter } func (suite *TypeUtilsTestSuite) SetupTest() { @@ -502,7 +502,7 @@ func (suite *TypeUtilsTestSuite) SetupTest() { suite.testEmojis = testrig.NewTestEmojis() suite.testReports = testrig.NewTestReports() suite.testMentions = testrig.NewTestMentions() - suite.typeconverter = typeutils.NewConverter(suite.db) + suite.typeconverter = typeutils.NewConverter(&suite.state) testrig.StandardDBSetup(suite.db, nil) } @@ -519,7 +519,7 @@ func (suite *TypeUtilsTestSuite) GetProcessor() *processing.Processor { testrig.StartTimelines( &suite.state, visibility.NewFilter(&suite.state), - testrig.NewTestTypeConverter(suite.db), + suite.typeconverter, ) httpClient := testrig.NewMockHTTPClient(nil, "../../testrig/media") diff --git a/internal/typeutils/defaulticons.go b/internal/typeutils/defaulticons.go index eb7cca88c..bcb7b8c10 100644 --- a/internal/typeutils/defaulticons.go +++ b/internal/typeutils/defaulticons.go @@ -96,7 +96,7 @@ func populateDefaultAvatars() (defaultAvatars []string) { // // If there are no default avatars available, this function is a // no-op. -func (c *converter) ensureAvatar(account *apimodel.Account) { +func (c *Converter) ensureAvatar(account *apimodel.Account) { if (account.Avatar != "" && account.AvatarStatic != "") || len(c.defaultAvatars) == 0 { return } @@ -126,7 +126,7 @@ func (c *converter) ensureAvatar(account *apimodel.Account) { // // If a value for the header URL is already set, this function is // a no-op. -func (c *converter) ensureHeader(account *apimodel.Account) { +func (c *Converter) ensureHeader(account *apimodel.Account) { if account.Header != "" && account.HeaderStatic != "" { return } diff --git a/internal/typeutils/internal.go b/internal/typeutils/internal.go index 831aad63e..1824a4421 100644 --- a/internal/typeutils/internal.go +++ b/internal/typeutils/internal.go @@ -26,7 +26,8 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/uris" ) -func (c *converter) FollowRequestToFollow(ctx context.Context, f *gtsmodel.FollowRequest) *gtsmodel.Follow { +// FollowRequestToFollow just converts a follow request into a follow, that's it! No bells and whistles. +func (c *Converter) FollowRequestToFollow(ctx context.Context, f *gtsmodel.FollowRequest) *gtsmodel.Follow { showReblogs := *f.ShowReblogs notify := *f.Notify return >smodel.Follow{ @@ -41,7 +42,8 @@ func (c *converter) FollowRequestToFollow(ctx context.Context, f *gtsmodel.Follo } } -func (c *converter) StatusToBoost(ctx context.Context, s *gtsmodel.Status, boostingAccount *gtsmodel.Account) (*gtsmodel.Status, error) { +// StatusToBoost wraps the given status into a boosting status. +func (c *Converter) StatusToBoost(ctx context.Context, s *gtsmodel.Status, boostingAccount *gtsmodel.Account) (*gtsmodel.Status, error) { // the wrapper won't use the same ID as the boosted status so we generate some new UUIDs accountURIs := uris.GenerateURIsForAccount(boostingAccount.Username) boostWrapperStatusID := id.NewULID() diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go index 9404e2ec7..d82fe8e04 100644 --- a/internal/typeutils/internaltoas.go +++ b/internal/typeutils/internaltoas.go @@ -37,8 +37,8 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/uris" ) -// Converts a gts model account into an Activity Streams person type. -func (c *converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) { +// AccountToAS converts a gts model account into an activity streams person, suitable for federation +func (c *Converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) { person := streams.NewActivityStreamsPerson() // id should be the activitypub URI of this user @@ -220,7 +220,7 @@ func (c *converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab if len(a.EmojiIDs) > len(emojis) { emojis = []*gtsmodel.Emoji{} for _, emojiID := range a.EmojiIDs { - emoji, err := c.db.GetEmojiByID(ctx, emojiID) + emoji, err := c.state.DB.GetEmojiByID(ctx, emojiID) if err != nil { return nil, fmt.Errorf("AccountToAS: error getting emoji %s from database: %s", emojiID, err) } @@ -269,7 +269,7 @@ func (c *converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab // Used as profile avatar. if a.AvatarMediaAttachmentID != "" { if a.AvatarMediaAttachment == nil { - avatar, err := c.db.GetAttachmentByID(ctx, a.AvatarMediaAttachmentID) + avatar, err := c.state.DB.GetAttachmentByID(ctx, a.AvatarMediaAttachmentID) if err == nil { a.AvatarMediaAttachment = avatar } else { @@ -303,7 +303,7 @@ func (c *converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab // Used as profile header. if a.HeaderMediaAttachmentID != "" { if a.HeaderMediaAttachment == nil { - header, err := c.db.GetAttachmentByID(ctx, a.HeaderMediaAttachmentID) + header, err := c.state.DB.GetAttachmentByID(ctx, a.HeaderMediaAttachmentID) if err == nil { a.HeaderMediaAttachment = header } else { @@ -336,10 +336,12 @@ func (c *converter) AccountToAS(ctx context.Context, a *gtsmodel.Account) (vocab return person, nil } -// Converts a gts model account into a VERY MINIMAL Activity Streams person type. +// AccountToASMinimal converts a gts model account into an activity streams person, suitable for federation. // -// The returned account will just have the Type, Username, PublicKey, and ID properties set. -func (c *converter) AccountToASMinimal(ctx context.Context, a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) { +// The returned account will just have the Type, Username, PublicKey, and ID properties set. This is +// suitable for serving to requesters to whom we want to give as little information as possible because +// we don't trust them (yet). +func (c *Converter) AccountToASMinimal(ctx context.Context, a *gtsmodel.Account) (vocab.ActivityStreamsPerson, error) { person := streams.NewActivityStreamsPerson() // id should be the activitypub URI of this user @@ -401,13 +403,14 @@ func (c *converter) AccountToASMinimal(ctx context.Context, a *gtsmodel.Account) return person, nil } -func (c *converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (vocab.ActivityStreamsNote, error) { +// StatusToAS converts a gts model status into an activity streams note, suitable for federation +func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (vocab.ActivityStreamsNote, error) { // ensure prerequisites here before we get stuck in // check if author account is already attached to status and attach it if not // if we can't retrieve this, bail here already because we can't attribute the status to anyone if s.Account == nil { - a, err := c.db.GetAccountByID(ctx, s.AccountID) + a, err := c.state.DB.GetAccountByID(ctx, s.AccountID) if err != nil { return nil, gtserror.Newf("error retrieving author account from db: %w", err) } @@ -478,7 +481,7 @@ func (c *converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (vocab.A // tag -- mentions mentions := s.Mentions if len(s.MentionIDs) > len(mentions) { - mentions, err = c.db.GetMentions(ctx, s.MentionIDs) + mentions, err = c.state.DB.GetMentions(ctx, s.MentionIDs) if err != nil { return nil, gtserror.Newf("error getting mentions: %w", err) } @@ -496,7 +499,7 @@ func (c *converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (vocab.A if len(s.EmojiIDs) > len(emojis) { emojis = []*gtsmodel.Emoji{} for _, emojiID := range s.EmojiIDs { - emoji, err := c.db.GetEmojiByID(ctx, emojiID) + emoji, err := c.state.DB.GetEmojiByID(ctx, emojiID) if err != nil { return nil, gtserror.Newf("error getting emoji %s from database: %w", emojiID, err) } @@ -514,7 +517,7 @@ func (c *converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (vocab.A // tag -- hashtags hashtags := s.Tags if len(s.TagIDs) > len(hashtags) { - hashtags, err = c.db.GetTags(ctx, s.TagIDs) + hashtags, err = c.state.DB.GetTags(ctx, s.TagIDs) if err != nil { return nil, gtserror.Newf("error getting tags: %w", err) } @@ -605,7 +608,7 @@ func (c *converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (vocab.A if len(s.AttachmentIDs) > len(attachments) { attachments = []*gtsmodel.MediaAttachment{} for _, attachmentID := range s.AttachmentIDs { - attachment, err := c.db.GetAttachmentByID(ctx, attachmentID) + attachment, err := c.state.DB.GetAttachmentByID(ctx, attachmentID) if err != nil { return nil, gtserror.Newf("error getting attachment %s from database: %w", attachmentID, err) } @@ -639,13 +642,15 @@ func (c *converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (vocab.A return status, nil } -func (c *converter) StatusToASDelete(ctx context.Context, s *gtsmodel.Status) (vocab.ActivityStreamsDelete, error) { +// StatusToASDelete converts a gts model status into a Delete of that status, using just the +// URI of the status as object, and addressing the Delete appropriately. +func (c *Converter) StatusToASDelete(ctx context.Context, s *gtsmodel.Status) (vocab.ActivityStreamsDelete, error) { // Parse / fetch some information // we need to create the Delete. if s.Account == nil { var err error - s.Account, err = c.db.GetAccountByID(ctx, s.AccountID) + s.Account, err = c.state.DB.GetAccountByID(ctx, s.AccountID) if err != nil { return nil, fmt.Errorf("StatusToASDelete: error retrieving author account from db: %w", err) } @@ -717,7 +722,7 @@ func (c *converter) StatusToASDelete(ctx context.Context, s *gtsmodel.Status) (v // Ensure mentions are populated. mentions := s.Mentions if len(s.MentionIDs) > len(mentions) { - mentions, err = c.db.GetMentions(ctx, s.MentionIDs) + mentions, err = c.state.DB.GetMentions(ctx, s.MentionIDs) if err != nil { return nil, fmt.Errorf("StatusToASDelete: error getting mentions: %w", err) } @@ -752,7 +757,7 @@ func (c *converter) StatusToASDelete(ctx context.Context, s *gtsmodel.Status) (v // Only address to this account if it // wasn't already included as a mention. if s.InReplyToAccount == nil { - s.InReplyToAccount, err = c.db.GetAccountByID(ctx, s.InReplyToAccountID) + s.InReplyToAccount, err = c.state.DB.GetAccountByID(ctx, s.InReplyToAccountID) if err != nil && !errors.Is(err, db.ErrNoEntries) { return nil, fmt.Errorf("StatusToASDelete: db error getting account %s: %w", s.InReplyToAccountID, err) } @@ -774,8 +779,9 @@ func (c *converter) StatusToASDelete(ctx context.Context, s *gtsmodel.Status) (v return delete, nil } -func (c *converter) FollowToAS(ctx context.Context, f *gtsmodel.Follow) (vocab.ActivityStreamsFollow, error) { - if err := c.db.PopulateFollow(ctx, f); err != nil { +// FollowToASFollow converts a gts model Follow into an activity streams Follow, suitable for federation +func (c *Converter) FollowToAS(ctx context.Context, f *gtsmodel.Follow) (vocab.ActivityStreamsFollow, error) { + if err := c.state.DB.PopulateFollow(ctx, f); err != nil { return nil, gtserror.Newf("error populating follow: %w", err) } @@ -824,9 +830,10 @@ func (c *converter) FollowToAS(ctx context.Context, f *gtsmodel.Follow) (vocab.A return follow, nil } -func (c *converter) MentionToAS(ctx context.Context, m *gtsmodel.Mention) (vocab.ActivityStreamsMention, error) { +// MentionToAS converts a gts model mention into an activity streams Mention, suitable for federation +func (c *Converter) MentionToAS(ctx context.Context, m *gtsmodel.Mention) (vocab.ActivityStreamsMention, error) { if m.TargetAccount == nil { - a, err := c.db.GetAccountByID(ctx, m.TargetAccountID) + a, err := c.state.DB.GetAccountByID(ctx, m.TargetAccountID) if err != nil { return nil, fmt.Errorf("MentionToAS: error getting target account from db: %s", err) } @@ -865,7 +872,8 @@ func (c *converter) MentionToAS(ctx context.Context, m *gtsmodel.Mention) (vocab return mention, nil } -func (c *converter) TagToAS(ctx context.Context, t *gtsmodel.Tag) (vocab.TootHashtag, error) { +// TagToAS converts a gts model tag into a toot Hashtag, suitable for federation. +func (c *Converter) TagToAS(ctx context.Context, t *gtsmodel.Tag) (vocab.TootHashtag, error) { // This is probably already lowercase, // but let's err on the safe side. nameLower := strings.ToLower(t.Name) @@ -891,20 +899,20 @@ func (c *converter) TagToAS(ctx context.Context, t *gtsmodel.Tag) (vocab.TootHas return tag, nil } -/* - we're making something like this: - { - "id": "https://example.com/emoji/123", - "type": "Emoji", - "name": ":kappa:", - "icon": { - "type": "Image", - "mediaType": "image/png", - "url": "https://example.com/files/kappa.png" - } - } -*/ -func (c *converter) EmojiToAS(ctx context.Context, e *gtsmodel.Emoji) (vocab.TootEmoji, error) { +// EmojiToAS converts a gts emoji into a mastodon ns Emoji, suitable for federation. +// we're making something like this: +// +// { +// "id": "https://example.com/emoji/123", +// "type": "Emoji", +// "name": ":kappa:", +// "icon": { +// "type": "Image", +// "mediaType": "image/png", +// "url": "https://example.com/files/kappa.png" +// } +// } +func (c *Converter) EmojiToAS(ctx context.Context, e *gtsmodel.Emoji) (vocab.TootEmoji, error) { // create the emoji emoji := streams.NewTootEmoji() @@ -947,7 +955,8 @@ func (c *converter) EmojiToAS(ctx context.Context, e *gtsmodel.Emoji) (vocab.Too return emoji, nil } -func (c *converter) AttachmentToAS(ctx context.Context, a *gtsmodel.MediaAttachment) (vocab.ActivityStreamsDocument, error) { +// AttachmentToAS converts a gts model media attachment into an activity streams Attachment, suitable for federation +func (c *Converter) AttachmentToAS(ctx context.Context, a *gtsmodel.MediaAttachment) (vocab.ActivityStreamsDocument, error) { // type -- Document doc := streams.NewActivityStreamsDocument() @@ -981,21 +990,20 @@ func (c *converter) AttachmentToAS(ctx context.Context, a *gtsmodel.MediaAttachm return doc, nil } -/* -We want to end up with something like this: - -{ -"@context": "https://www.w3.org/ns/activitystreams", -"actor": "https://ondergrond.org/users/dumpsterqueer", -"id": "https://ondergrond.org/users/dumpsterqueer#likes/44584", -"object": "https://testingtesting123.xyz/users/gotosocial_test_account/statuses/771aea80-a33d-4d6d-8dfd-57d4d2bfcbd4", -"type": "Like" -} -*/ -func (c *converter) FaveToAS(ctx context.Context, f *gtsmodel.StatusFave) (vocab.ActivityStreamsLike, error) { +// FaveToAS converts a gts model status fave into an activityStreams LIKE, suitable for federation. +// We want to end up with something like this: +// +// { +// "@context": "https://www.w3.org/ns/activitystreams", +// "actor": "https://ondergrond.org/users/dumpsterqueer", +// "id": "https://ondergrond.org/users/dumpsterqueer#likes/44584", +// "object": "https://testingtesting123.xyz/users/gotosocial_test_account/statuses/771aea80-a33d-4d6d-8dfd-57d4d2bfcbd4", +// "type": "Like" +// } +func (c *Converter) FaveToAS(ctx context.Context, f *gtsmodel.StatusFave) (vocab.ActivityStreamsLike, error) { // check if targetStatus is already pinned to this fave, and fetch it if not if f.Status == nil { - s, err := c.db.GetStatusByID(ctx, f.StatusID) + s, err := c.state.DB.GetStatusByID(ctx, f.StatusID) if err != nil { return nil, fmt.Errorf("FaveToAS: error fetching target status from database: %s", err) } @@ -1004,7 +1012,7 @@ func (c *converter) FaveToAS(ctx context.Context, f *gtsmodel.StatusFave) (vocab // check if the targetAccount is already pinned to this fave, and fetch it if not if f.TargetAccount == nil { - a, err := c.db.GetAccountByID(ctx, f.TargetAccountID) + a, err := c.state.DB.GetAccountByID(ctx, f.TargetAccountID) if err != nil { return nil, fmt.Errorf("FaveToAS: error fetching target account from database: %s", err) } @@ -1013,7 +1021,7 @@ func (c *converter) FaveToAS(ctx context.Context, f *gtsmodel.StatusFave) (vocab // check if the faving account is already pinned to this fave, and fetch it if not if f.Account == nil { - a, err := c.db.GetAccountByID(ctx, f.AccountID) + a, err := c.state.DB.GetAccountByID(ctx, f.AccountID) if err != nil { return nil, fmt.Errorf("FaveToAS: error fetching faving account from database: %s", err) } @@ -1062,10 +1070,11 @@ func (c *converter) FaveToAS(ctx context.Context, f *gtsmodel.StatusFave) (vocab return like, nil } -func (c *converter) BoostToAS(ctx context.Context, boostWrapperStatus *gtsmodel.Status, boostingAccount *gtsmodel.Account, boostedAccount *gtsmodel.Account) (vocab.ActivityStreamsAnnounce, error) { +// BoostToAS converts a gts model boost into an activityStreams ANNOUNCE, suitable for federation +func (c *Converter) BoostToAS(ctx context.Context, boostWrapperStatus *gtsmodel.Status, boostingAccount *gtsmodel.Account, boostedAccount *gtsmodel.Account) (vocab.ActivityStreamsAnnounce, error) { // the boosted status is probably pinned to the boostWrapperStatus but double check to make sure if boostWrapperStatus.BoostOf == nil { - b, err := c.db.GetStatusByID(ctx, boostWrapperStatus.BoostOfID) + b, err := c.state.DB.GetStatusByID(ctx, boostWrapperStatus.BoostOfID) if err != nil { return nil, fmt.Errorf("BoostToAS: error getting status with ID %s from the db: %s", boostWrapperStatus.BoostOfID, err) } @@ -1139,20 +1148,19 @@ func (c *converter) BoostToAS(ctx context.Context, boostWrapperStatus *gtsmodel. return announce, nil } -/* -we want to end up with something like this: - - { - "@context": "https://www.w3.org/ns/activitystreams", - "actor": "https://example.org/users/some_user", - "id":"https://example.org/users/some_user/blocks/SOME_ULID_OF_A_BLOCK", - "object":"https://some_other.instance/users/some_other_user", - "type":"Block" - } -*/ -func (c *converter) BlockToAS(ctx context.Context, b *gtsmodel.Block) (vocab.ActivityStreamsBlock, error) { +// BlockToAS converts a gts model block into an activityStreams BLOCK, suitable for federation. +// we want to end up with something like this: +// +// { +// "@context": "https://www.w3.org/ns/activitystreams", +// "actor": "https://example.org/users/some_user", +// "id":"https://example.org/users/some_user/blocks/SOME_ULID_OF_A_BLOCK", +// "object":"https://some_other.instance/users/some_other_user", +// "type":"Block" +// } +func (c *Converter) BlockToAS(ctx context.Context, b *gtsmodel.Block) (vocab.ActivityStreamsBlock, error) { if b.Account == nil { - a, err := c.db.GetAccountByID(ctx, b.AccountID) + a, err := c.state.DB.GetAccountByID(ctx, b.AccountID) if err != nil { return nil, fmt.Errorf("BlockToAS: error getting block owner account from database: %s", err) } @@ -1160,7 +1168,7 @@ func (c *converter) BlockToAS(ctx context.Context, b *gtsmodel.Block) (vocab.Act } if b.TargetAccount == nil { - a, err := c.db.GetAccountByID(ctx, b.TargetAccountID) + a, err := c.state.DB.GetAccountByID(ctx, b.TargetAccountID) if err != nil { return nil, fmt.Errorf("BlockToAS: error getting block target account from database: %s", err) } @@ -1209,23 +1217,22 @@ func (c *converter) BlockToAS(ctx context.Context, b *gtsmodel.Block) (vocab.Act return block, nil } -/* -the goal is to end up with something like this: - - { - "@context": "https://www.w3.org/ns/activitystreams", - "id": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies", - "type": "Collection", - "first": { - "id": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?page=true", - "type": "CollectionPage", - "next": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?only_other_accounts=true&page=true", - "partOf": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies", - "items": [] - } - } -*/ -func (c *converter) StatusToASRepliesCollection(ctx context.Context, status *gtsmodel.Status, onlyOtherAccounts bool) (vocab.ActivityStreamsCollection, error) { +// StatusToASRepliesCollection converts a gts model status into an activityStreams REPLIES collection. +// the goal is to end up with something like this: +// +// { +// "@context": "https://www.w3.org/ns/activitystreams", +// "id": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies", +// "type": "Collection", +// "first": { +// "id": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?page=true", +// "type": "CollectionPage", +// "next": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?only_other_accounts=true&page=true", +// "partOf": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies", +// "items": [] +// } +// } +func (c *Converter) StatusToASRepliesCollection(ctx context.Context, status *gtsmodel.Status, onlyOtherAccounts bool) (vocab.ActivityStreamsCollection, error) { collectionID := fmt.Sprintf("%s/replies", status.URI) collectionIDURI, err := url.Parse(collectionID) if err != nil { @@ -1274,22 +1281,21 @@ func (c *converter) StatusToASRepliesCollection(ctx context.Context, status *gts return collection, nil } -/* -the goal is to end up with something like this: - - { - "@context": "https://www.w3.org/ns/activitystreams", - "id": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?only_other_accounts=true&page=true", - "type": "CollectionPage", - "next": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?min_id=106720870266901180&only_other_accounts=true&page=true", - "partOf": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies", - "items": [ - "https://example.com/users/someone/statuses/106720752853216226", - "https://somewhere.online/users/eeeeeeeeeep/statuses/106720870163727231" - ] - } -*/ -func (c *converter) StatusURIsToASRepliesPage(ctx context.Context, status *gtsmodel.Status, onlyOtherAccounts bool, minID string, replies map[string]*url.URL) (vocab.ActivityStreamsCollectionPage, error) { +// StatusURIsToASRepliesPage returns a collection page with appropriate next/part of pagination. +// the goal is to end up with something like this: +// +// { +// "@context": "https://www.w3.org/ns/activitystreams", +// "id": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?only_other_accounts=true&page=true", +// "type": "CollectionPage", +// "next": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies?min_id=106720870266901180&only_other_accounts=true&page=true", +// "partOf": "https://example.org/users/whatever/statuses/01FCNEXAGAKPEX1J7VJRPJP490/replies", +// "items": [ +// "https://example.com/users/someone/statuses/106720752853216226", +// "https://somewhere.online/users/eeeeeeeeeep/statuses/106720870163727231" +// ] +// } +func (c *Converter) StatusURIsToASRepliesPage(ctx context.Context, status *gtsmodel.Status, onlyOtherAccounts bool, minID string, replies map[string]*url.URL) (vocab.ActivityStreamsCollectionPage, error) { collectionID := fmt.Sprintf("%s/replies", status.URI) page := streams.NewActivityStreamsCollectionPage() @@ -1345,31 +1351,37 @@ func (c *converter) StatusURIsToASRepliesPage(ctx context.Context, status *gtsmo return page, nil } -/* -the goal is to end up with something like this: - - { - "id": "https://example.org/users/whatever/outbox?page=true", - "type": "OrderedCollectionPage", - "next": "https://example.org/users/whatever/outbox?max_id=01FJC1Q0E3SSQR59TD2M1KP4V8&page=true", - "prev": "https://example.org/users/whatever/outbox?min_id=01FJC1Q0E3SSQR59TD2M1KP4V8&page=true", - "partOf": "https://example.org/users/whatever/outbox", - "orderedItems": [ - "id": "https://example.org/users/whatever/statuses/01FJC1MKPVX2VMWP2ST93Q90K7/activity", - "type": "Create", - "actor": "https://example.org/users/whatever", - "published": "2021-10-18T20:06:18Z", - "to": [ - "https://www.w3.org/ns/activitystreams#Public" - ], - "cc": [ - "https://example.org/users/whatever/followers" - ], - "object": "https://example.org/users/whatever/statuses/01FJC1MKPVX2VMWP2ST93Q90K7" - ] - } -*/ -func (c *converter) StatusesToASOutboxPage(ctx context.Context, outboxID string, maxID string, minID string, statuses []*gtsmodel.Status) (vocab.ActivityStreamsOrderedCollectionPage, error) { +// StatusesToASOutboxPage returns an ordered collection page using the given statuses and parameters as contents. +// +// The maxID and minID should be the parameters that were passed to the database to obtain the given statuses. +// These will be used to create the 'id' field of the collection. +// +// OutboxID is used to create the 'partOf' field in the collection. +// +// Appropriate 'next' and 'prev' fields will be created based on the highest and lowest IDs present in the statuses slice. +// the goal is to end up with something like this: +// +// { +// "id": "https://example.org/users/whatever/outbox?page=true", +// "type": "OrderedCollectionPage", +// "next": "https://example.org/users/whatever/outbox?max_id=01FJC1Q0E3SSQR59TD2M1KP4V8&page=true", +// "prev": "https://example.org/users/whatever/outbox?min_id=01FJC1Q0E3SSQR59TD2M1KP4V8&page=true", +// "partOf": "https://example.org/users/whatever/outbox", +// "orderedItems": [ +// "id": "https://example.org/users/whatever/statuses/01FJC1MKPVX2VMWP2ST93Q90K7/activity", +// "type": "Create", +// "actor": "https://example.org/users/whatever", +// "published": "2021-10-18T20:06:18Z", +// "to": [ +// "https://www.w3.org/ns/activitystreams#Public" +// ], +// "cc": [ +// "https://example.org/users/whatever/followers" +// ], +// "object": "https://example.org/users/whatever/statuses/01FJC1MKPVX2VMWP2ST93Q90K7" +// ] +// } +func (c *Converter) StatusesToASOutboxPage(ctx context.Context, outboxID string, maxID string, minID string, statuses []*gtsmodel.Status) (vocab.ActivityStreamsOrderedCollectionPage, error) { page := streams.NewActivityStreamsOrderedCollectionPage() // .id @@ -1450,17 +1462,17 @@ func (c *converter) StatusesToASOutboxPage(ctx context.Context, outboxID string, return page, nil } -/* -we want something that looks like this: - - { - "@context": "https://www.w3.org/ns/activitystreams", - "id": "https://example.org/users/whatever/outbox", - "type": "OrderedCollection", - "first": "https://example.org/users/whatever/outbox?page=true" - } -*/ -func (c *converter) OutboxToASCollection(ctx context.Context, outboxID string) (vocab.ActivityStreamsOrderedCollection, error) { +// OutboxToASCollection returns an ordered collection with appropriate id, next, and last fields. +// The returned collection won't have any actual entries; just links to where entries can be obtained. +// we want something that looks like this: +// +// { +// "@context": "https://www.w3.org/ns/activitystreams", +// "id": "https://example.org/users/whatever/outbox", +// "type": "OrderedCollection", +// "first": "https://example.org/users/whatever/outbox?page=true" +// } +func (c *Converter) OutboxToASCollection(ctx context.Context, outboxID string) (vocab.ActivityStreamsOrderedCollection, error) { collection := streams.NewActivityStreamsOrderedCollection() collectionIDProp := streams.NewJSONLDIdProperty() @@ -1483,7 +1495,9 @@ func (c *converter) OutboxToASCollection(ctx context.Context, outboxID string) ( return collection, nil } -func (c *converter) StatusesToASFeaturedCollection(ctx context.Context, featuredCollectionID string, statuses []*gtsmodel.Status) (vocab.ActivityStreamsOrderedCollection, error) { +// StatusesToASFeaturedCollection converts a slice of statuses into an ordered collection +// of URIs, suitable for serializing and serving via the activitypub API. +func (c *Converter) StatusesToASFeaturedCollection(ctx context.Context, featuredCollectionID string, statuses []*gtsmodel.Status) (vocab.ActivityStreamsOrderedCollection, error) { collection := streams.NewActivityStreamsOrderedCollection() collectionIDProp := streams.NewJSONLDIdProperty() @@ -1511,7 +1525,8 @@ func (c *converter) StatusesToASFeaturedCollection(ctx context.Context, featured return collection, nil } -func (c *converter) ReportToASFlag(ctx context.Context, r *gtsmodel.Report) (vocab.ActivityStreamsFlag, error) { +// ReportToASFlag converts a gts model report into an activitystreams FLAG, suitable for federation. +func (c *Converter) ReportToASFlag(ctx context.Context, r *gtsmodel.Report) (vocab.ActivityStreamsFlag, error) { flag := streams.NewActivityStreamsFlag() flagIDProp := streams.NewJSONLDIdProperty() @@ -1524,7 +1539,7 @@ func (c *converter) ReportToASFlag(ctx context.Context, r *gtsmodel.Report) (voc // for privacy, set the actor as the INSTANCE ACTOR, // not as the actor who created the report - instanceAccount, err := c.db.GetInstanceAccount(ctx, "") + instanceAccount, err := c.state.DB.GetInstanceAccount(ctx, "") if err != nil { return nil, fmt.Errorf("error getting instance account: %w", err) } diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go index 11838e2bd..155863e34 100644 --- a/internal/typeutils/internaltofrontend.go +++ b/internal/typeutils/internaltofrontend.go @@ -58,7 +58,10 @@ func toMastodonVersion(in string) string { return instanceMastodonVersion + "+" + strings.ReplaceAll(in, " ", "-") } -func (c *converter) AccountToAPIAccountSensitive(ctx context.Context, a *gtsmodel.Account) (*apimodel.Account, error) { +// AppToAPIAppSensitive takes a db model application as a param, and returns a populated apitype application, or an error +// if something goes wrong. The returned application should be ready to serialize on an API level, and may have sensitive fields +// (such as client id and client secret), so serve it only to an authorized user who should have permission to see it. +func (c *Converter) AccountToAPIAccountSensitive(ctx context.Context, a *gtsmodel.Account) (*apimodel.Account, error) { // we can build this sensitive account easily by first getting the public account.... apiAccount, err := c.AccountToAPIAccountPublic(ctx, a) if err != nil { @@ -68,7 +71,7 @@ func (c *converter) AccountToAPIAccountSensitive(ctx context.Context, a *gtsmode // then adding the Source object to it... // check pending follow requests aimed at this account - frc, err := c.db.CountAccountFollowRequests(ctx, a.ID) + frc, err := c.state.DB.CountAccountFollowRequests(ctx, a.ID) if err != nil { return nil, fmt.Errorf("error counting follow requests: %s", err) } @@ -91,8 +94,11 @@ func (c *converter) AccountToAPIAccountSensitive(ctx context.Context, a *gtsmode return apiAccount, nil } -func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.Account) (*apimodel.Account, error) { - if err := c.db.PopulateAccount(ctx, a); err != nil { +// AccountToAPIAccountPublic takes a db model account as a param, and returns a populated apitype account, or an error +// if something goes wrong. The returned account should be ready to serialize on an API level, and may NOT have sensitive fields. +// In other words, this is the public record that the server has of an account. +func (c *Converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.Account) (*apimodel.Account, error) { + if err := c.state.DB.PopulateAccount(ctx, a); err != nil { log.Errorf(ctx, "error(s) populating account, will continue: %s", err) } @@ -102,23 +108,23 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A // - Statuses count // - Last status time - followersCount, err := c.db.CountAccountFollowers(ctx, a.ID) + followersCount, err := c.state.DB.CountAccountFollowers(ctx, a.ID) if err != nil && !errors.Is(err, db.ErrNoEntries) { return nil, fmt.Errorf("AccountToAPIAccountPublic: error counting followers: %w", err) } - followingCount, err := c.db.CountAccountFollows(ctx, a.ID) + followingCount, err := c.state.DB.CountAccountFollows(ctx, a.ID) if err != nil && !errors.Is(err, db.ErrNoEntries) { return nil, fmt.Errorf("AccountToAPIAccountPublic: error counting following: %w", err) } - statusesCount, err := c.db.CountAccountStatuses(ctx, a.ID) + statusesCount, err := c.state.DB.CountAccountStatuses(ctx, a.ID) if err != nil && !errors.Is(err, db.ErrNoEntries) { return nil, fmt.Errorf("AccountToAPIAccountPublic: error counting statuses: %w", err) } var lastStatusAt *string - lastPosted, err := c.db.GetAccountLastPosted(ctx, a.ID, false) + lastPosted, err := c.state.DB.GetAccountLastPosted(ctx, a.ID, false) if err != nil && !errors.Is(err, db.ErrNoEntries) { return nil, fmt.Errorf("AccountToAPIAccountPublic: error counting statuses: %w", err) } @@ -182,7 +188,7 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A // fetch more info. Skip for instance // accounts since they have no user. if !a.IsInstance() { - user, err := c.db.GetUserByAccountID(ctx, a.ID) + user, err := c.state.DB.GetUserByAccountID(ctx, a.ID) if err != nil { return nil, fmt.Errorf("AccountToAPIAccountPublic: error getting user from database for account id %s: %w", a.ID, err) } @@ -238,7 +244,7 @@ func (c *converter) AccountToAPIAccountPublic(ctx context.Context, a *gtsmodel.A return accountFrontend, nil } -func (c *converter) fieldsToAPIFields(f []*gtsmodel.Field) []apimodel.Field { +func (c *Converter) fieldsToAPIFields(f []*gtsmodel.Field) []apimodel.Field { fields := make([]apimodel.Field, len(f)) for i, field := range f { @@ -257,7 +263,10 @@ func (c *converter) fieldsToAPIFields(f []*gtsmodel.Field) []apimodel.Field { return fields } -func (c *converter) AccountToAPIAccountBlocked(ctx context.Context, a *gtsmodel.Account) (*apimodel.Account, error) { +// AccountToAPIAccountBlocked takes a db model account as a param, and returns a apitype account, or an error if +// something goes wrong. The returned account will be a bare minimum representation of the account. This function should be used +// when someone wants to view an account they've blocked. +func (c *Converter) AccountToAPIAccountBlocked(ctx context.Context, a *gtsmodel.Account) (*apimodel.Account, error) { var ( acct string role *apimodel.AccountRole @@ -277,7 +286,7 @@ func (c *converter) AccountToAPIAccountBlocked(ctx context.Context, a *gtsmodel. // fetch more info. Skip for instance // accounts since they have no user. if !a.IsInstance() { - user, err := c.db.GetUserByAccountID(ctx, a.ID) + user, err := c.state.DB.GetUserByAccountID(ctx, a.ID) if err != nil { return nil, fmt.Errorf("AccountToAPIAccountPublic: error getting user from database for account id %s: %w", a.ID, err) } @@ -308,7 +317,7 @@ func (c *converter) AccountToAPIAccountBlocked(ctx context.Context, a *gtsmodel. }, nil } -func (c *converter) AccountToAdminAPIAccount(ctx context.Context, a *gtsmodel.Account) (*apimodel.AdminAccountInfo, error) { +func (c *Converter) AccountToAdminAPIAccount(ctx context.Context, a *gtsmodel.Account) (*apimodel.AdminAccountInfo, error) { var ( email string ip *string @@ -334,7 +343,7 @@ func (c *converter) AccountToAdminAPIAccount(ctx context.Context, a *gtsmodel.Ac } else if !a.IsInstance() { // This is a local, non-instance // acct; we can fetch more info. - user, err := c.db.GetUserByAccountID(ctx, a.ID) + user, err := c.state.DB.GetUserByAccountID(ctx, a.ID) if err != nil { return nil, fmt.Errorf("AccountToAdminAPIAccount: error getting user from database for account id %s: %w", a.ID, err) } @@ -393,7 +402,7 @@ func (c *converter) AccountToAdminAPIAccount(ctx context.Context, a *gtsmodel.Ac }, nil } -func (c *converter) AppToAPIAppSensitive(ctx context.Context, a *gtsmodel.Application) (*apimodel.Application, error) { +func (c *Converter) AppToAPIAppSensitive(ctx context.Context, a *gtsmodel.Application) (*apimodel.Application, error) { return &apimodel.Application{ ID: a.ID, Name: a.Name, @@ -404,14 +413,18 @@ func (c *converter) AppToAPIAppSensitive(ctx context.Context, a *gtsmodel.Applic }, nil } -func (c *converter) AppToAPIAppPublic(ctx context.Context, a *gtsmodel.Application) (*apimodel.Application, error) { +// AppToAPIAppPublic takes a db model application as a param, and returns a populated apitype application, or an error +// if something goes wrong. The returned application should be ready to serialize on an API level, and has sensitive +// fields sanitized so that it can be served to non-authorized accounts without revealing any private information. +func (c *Converter) AppToAPIAppPublic(ctx context.Context, a *gtsmodel.Application) (*apimodel.Application, error) { return &apimodel.Application{ Name: a.Name, Website: a.Website, }, nil } -func (c *converter) AttachmentToAPIAttachment(ctx context.Context, a *gtsmodel.MediaAttachment) (apimodel.Attachment, error) { +// AttachmentToAPIAttachment converts a gts model media attacahment into its api representation for serialization on the API. +func (c *Converter) AttachmentToAPIAttachment(ctx context.Context, a *gtsmodel.MediaAttachment) (apimodel.Attachment, error) { apiAttachment := apimodel.Attachment{ ID: a.ID, Type: strings.ToLower(string(a.Type)), @@ -479,9 +492,10 @@ func (c *converter) AttachmentToAPIAttachment(ctx context.Context, a *gtsmodel.M return apiAttachment, nil } -func (c *converter) MentionToAPIMention(ctx context.Context, m *gtsmodel.Mention) (apimodel.Mention, error) { +// MentionToAPIMention converts a gts model mention into its api (frontend) representation for serialization on the API. +func (c *Converter) MentionToAPIMention(ctx context.Context, m *gtsmodel.Mention) (apimodel.Mention, error) { if m.TargetAccount == nil { - targetAccount, err := c.db.GetAccountByID(ctx, m.TargetAccountID) + targetAccount, err := c.state.DB.GetAccountByID(ctx, m.TargetAccountID) if err != nil { return apimodel.Mention{}, err } @@ -511,12 +525,13 @@ func (c *converter) MentionToAPIMention(ctx context.Context, m *gtsmodel.Mention }, nil } -func (c *converter) EmojiToAPIEmoji(ctx context.Context, e *gtsmodel.Emoji) (apimodel.Emoji, error) { +// EmojiToAPIEmoji converts a gts model emoji into its api (frontend) representation for serialization on the API. +func (c *Converter) EmojiToAPIEmoji(ctx context.Context, e *gtsmodel.Emoji) (apimodel.Emoji, error) { var category string if e.CategoryID != "" { if e.Category == nil { var err error - e.Category, err = c.db.GetEmojiCategory(ctx, e.CategoryID) + e.Category, err = c.state.DB.GetEmojiCategory(ctx, e.CategoryID) if err != nil { return apimodel.Emoji{}, err } @@ -533,7 +548,8 @@ func (c *converter) EmojiToAPIEmoji(ctx context.Context, e *gtsmodel.Emoji) (api }, nil } -func (c *converter) EmojiToAdminAPIEmoji(ctx context.Context, e *gtsmodel.Emoji) (*apimodel.AdminEmoji, error) { +// EmojiToAdminAPIEmoji converts a gts model emoji into an API representation with extra admin information. +func (c *Converter) EmojiToAdminAPIEmoji(ctx context.Context, e *gtsmodel.Emoji) (*apimodel.AdminEmoji, error) { emoji, err := c.EmojiToAPIEmoji(ctx, e) if err != nil { return nil, err @@ -562,14 +578,17 @@ func (c *converter) EmojiToAdminAPIEmoji(ctx context.Context, e *gtsmodel.Emoji) }, nil } -func (c *converter) EmojiCategoryToAPIEmojiCategory(ctx context.Context, category *gtsmodel.EmojiCategory) (*apimodel.EmojiCategory, error) { +// EmojiCategoryToAPIEmojiCategory converts a gts model emoji category into its api (frontend) representation. +func (c *Converter) EmojiCategoryToAPIEmojiCategory(ctx context.Context, category *gtsmodel.EmojiCategory) (*apimodel.EmojiCategory, error) { return &apimodel.EmojiCategory{ ID: category.ID, Name: category.Name, }, nil } -func (c *converter) TagToAPITag(ctx context.Context, t *gtsmodel.Tag, stubHistory bool) (apimodel.Tag, error) { +// TagToAPITag converts a gts model tag into its api (frontend) representation for serialization on the API. +// If stubHistory is set to 'true', then the 'history' field of the tag will be populated with a pointer to an empty slice, for API compatibility reasons. +func (c *Converter) TagToAPITag(ctx context.Context, t *gtsmodel.Tag, stubHistory bool) (apimodel.Tag, error) { return apimodel.Tag{ Name: strings.ToLower(t.Name), URL: uris.GenerateURIForTag(t.Name), @@ -584,8 +603,11 @@ func (c *converter) TagToAPITag(ctx context.Context, t *gtsmodel.Tag, stubHistor }, nil } -func (c *converter) StatusToAPIStatus(ctx context.Context, s *gtsmodel.Status, requestingAccount *gtsmodel.Account) (*apimodel.Status, error) { - if err := c.db.PopulateStatus(ctx, s); err != nil { +// StatusToAPIStatus converts a gts model status into its api (frontend) representation for serialization on the API. +// +// Requesting account can be nil. +func (c *Converter) StatusToAPIStatus(ctx context.Context, s *gtsmodel.Status, requestingAccount *gtsmodel.Account) (*apimodel.Status, error) { + if err := c.state.DB.PopulateStatus(ctx, s); err != nil { // Ensure author account present + correct; // can't really go further without this! if s.Account == nil { @@ -600,17 +622,17 @@ func (c *converter) StatusToAPIStatus(ctx context.Context, s *gtsmodel.Status, r return nil, fmt.Errorf("error converting status author: %w", err) } - repliesCount, err := c.db.CountStatusReplies(ctx, s.ID) + repliesCount, err := c.state.DB.CountStatusReplies(ctx, s.ID) if err != nil { return nil, fmt.Errorf("error counting replies: %w", err) } - reblogsCount, err := c.db.CountStatusBoosts(ctx, s.ID) + reblogsCount, err := c.state.DB.CountStatusBoosts(ctx, s.ID) if err != nil { return nil, fmt.Errorf("error counting reblogs: %w", err) } - favesCount, err := c.db.CountStatusFaves(ctx, s.ID) + favesCount, err := c.state.DB.CountStatusFaves(ctx, s.ID) if err != nil { return nil, fmt.Errorf("error counting faves: %w", err) } @@ -699,7 +721,7 @@ func (c *converter) StatusToAPIStatus(ctx context.Context, s *gtsmodel.Status, r } if appID := s.CreatedWithApplicationID; appID != "" { - app, err := c.db.GetApplicationByID(ctx, appID) + app, err := c.state.DB.GetApplicationByID(ctx, appID) if err != nil { return nil, fmt.Errorf("error getting application %s: %w", appID, err) } @@ -723,8 +745,8 @@ func (c *converter) StatusToAPIStatus(ctx context.Context, s *gtsmodel.Status, r return apiStatus, nil } -// VisToapi converts a gts visibility into its api equivalent -func (c *converter) VisToAPIVis(ctx context.Context, m gtsmodel.Visibility) apimodel.Visibility { +// VisToAPIVis converts a gts visibility into its api equivalent +func (c *Converter) VisToAPIVis(ctx context.Context, m gtsmodel.Visibility) apimodel.Visibility { switch m { case gtsmodel.VisibilityPublic: return apimodel.VisibilityPublic @@ -738,14 +760,16 @@ func (c *converter) VisToAPIVis(ctx context.Context, m gtsmodel.Visibility) apim return "" } -func (c *converter) InstanceRuleToAPIRule(r gtsmodel.Rule) apimodel.InstanceRule { +// InstanceRuleToAdminAPIRule converts a local instance rule into its api equivalent for serving at /api/v1/admin/instance/rules/:id +func (c *Converter) InstanceRuleToAPIRule(r gtsmodel.Rule) apimodel.InstanceRule { return apimodel.InstanceRule{ ID: r.ID, Text: r.Text, } } -func (c *converter) InstanceRulesToAPIRules(r []gtsmodel.Rule) []apimodel.InstanceRule { +// InstanceRulesToAPIRules converts all local instance rules into their api equivalent for serving at /api/v1/instance/rules +func (c *Converter) InstanceRulesToAPIRules(r []gtsmodel.Rule) []apimodel.InstanceRule { rules := make([]apimodel.InstanceRule, len(r)) for i, v := range r { @@ -755,7 +779,8 @@ func (c *converter) InstanceRulesToAPIRules(r []gtsmodel.Rule) []apimodel.Instan return rules } -func (c *converter) InstanceRuleToAdminAPIRule(r *gtsmodel.Rule) *apimodel.AdminInstanceRule { +// InstanceRuleToAdminAPIRule converts a local instance rule into its api equivalent for serving at /api/v1/admin/instance/rules/:id +func (c *Converter) InstanceRuleToAdminAPIRule(r *gtsmodel.Rule) *apimodel.AdminInstanceRule { return &apimodel.AdminInstanceRule{ ID: r.ID, CreatedAt: util.FormatISO8601(r.CreatedAt), @@ -764,7 +789,8 @@ func (c *converter) InstanceRuleToAdminAPIRule(r *gtsmodel.Rule) *apimodel.Admin } } -func (c *converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Instance) (*apimodel.InstanceV1, error) { +// InstanceToAPIV1Instance converts a gts instance into its api equivalent for serving at /api/v1/instance +func (c *Converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Instance) (*apimodel.InstanceV1, error) { instance := &apimodel.InstanceV1{ URI: i.URI, AccountDomain: config.GetAccountDomain(), @@ -810,19 +836,19 @@ func (c *converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins // statistics stats := make(map[string]int, 3) - userCount, err := c.db.CountInstanceUsers(ctx, i.Domain) + userCount, err := c.state.DB.CountInstanceUsers(ctx, i.Domain) if err != nil { return nil, fmt.Errorf("InstanceToAPIV1Instance: db error getting counting instance users: %w", err) } stats["user_count"] = userCount - statusCount, err := c.db.CountInstanceStatuses(ctx, i.Domain) + statusCount, err := c.state.DB.CountInstanceStatuses(ctx, i.Domain) if err != nil { return nil, fmt.Errorf("InstanceToAPIV1Instance: db error getting counting instance statuses: %w", err) } stats["status_count"] = statusCount - domainCount, err := c.db.CountInstanceDomains(ctx, i.Domain) + domainCount, err := c.state.DB.CountInstanceDomains(ctx, i.Domain) if err != nil { return nil, fmt.Errorf("InstanceToAPIV1Instance: db error getting counting instance domains: %w", err) } @@ -830,14 +856,14 @@ func (c *converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins instance.Stats = stats // thumbnail - iAccount, err := c.db.GetInstanceAccount(ctx, "") + iAccount, err := c.state.DB.GetInstanceAccount(ctx, "") if err != nil { return nil, fmt.Errorf("InstanceToAPIV1Instance: db error getting instance account: %w", err) } if iAccount.AvatarMediaAttachmentID != "" { if iAccount.AvatarMediaAttachment == nil { - avi, err := c.db.GetAttachmentByID(ctx, iAccount.AvatarMediaAttachmentID) + avi, err := c.state.DB.GetAttachmentByID(ctx, iAccount.AvatarMediaAttachmentID) if err != nil { return nil, fmt.Errorf("InstanceToAPIInstance: error getting instance avatar attachment with id %s: %w", iAccount.AvatarMediaAttachmentID, err) } @@ -854,7 +880,7 @@ func (c *converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins // contact account if i.ContactAccountID != "" { if i.ContactAccount == nil { - contactAccount, err := c.db.GetAccountByID(ctx, i.ContactAccountID) + contactAccount, err := c.state.DB.GetAccountByID(ctx, i.ContactAccountID) if err != nil { return nil, fmt.Errorf("InstanceToAPIV1Instance: db error getting instance contact account %s: %w", i.ContactAccountID, err) } @@ -871,7 +897,8 @@ func (c *converter) InstanceToAPIV1Instance(ctx context.Context, i *gtsmodel.Ins return instance, nil } -func (c *converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Instance) (*apimodel.InstanceV2, error) { +// InstanceToAPIV2Instance converts a gts instance into its api equivalent for serving at /api/v2/instance +func (c *Converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Instance) (*apimodel.InstanceV2, error) { instance := &apimodel.InstanceV2{ Domain: i.Domain, AccountDomain: config.GetAccountDomain(), @@ -891,14 +918,14 @@ func (c *converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins // thumbnail thumbnail := apimodel.InstanceV2Thumbnail{} - iAccount, err := c.db.GetInstanceAccount(ctx, "") + iAccount, err := c.state.DB.GetInstanceAccount(ctx, "") if err != nil { return nil, fmt.Errorf("InstanceToAPIV2Instance: db error getting instance account: %w", err) } if iAccount.AvatarMediaAttachmentID != "" { if iAccount.AvatarMediaAttachment == nil { - avi, err := c.db.GetAttachmentByID(ctx, iAccount.AvatarMediaAttachmentID) + avi, err := c.state.DB.GetAttachmentByID(ctx, iAccount.AvatarMediaAttachmentID) if err != nil { return nil, fmt.Errorf("InstanceToAPIV2Instance: error getting instance avatar attachment with id %s: %w", iAccount.AvatarMediaAttachmentID, err) } @@ -945,7 +972,7 @@ func (c *converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins instance.Contact.Email = i.ContactEmail if i.ContactAccountID != "" { if i.ContactAccount == nil { - contactAccount, err := c.db.GetAccountByID(ctx, i.ContactAccountID) + contactAccount, err := c.state.DB.GetAccountByID(ctx, i.ContactAccountID) if err != nil { return nil, fmt.Errorf("InstanceToAPIV2Instance: db error getting instance contact account %s: %w", i.ContactAccountID, err) } @@ -962,7 +989,8 @@ func (c *converter) InstanceToAPIV2Instance(ctx context.Context, i *gtsmodel.Ins return instance, nil } -func (c *converter) RelationshipToAPIRelationship(ctx context.Context, r *gtsmodel.Relationship) (*apimodel.Relationship, error) { +// RelationshipToAPIRelationship converts a gts relationship into its api equivalent for serving in various places +func (c *Converter) RelationshipToAPIRelationship(ctx context.Context, r *gtsmodel.Relationship) (*apimodel.Relationship, error) { return &apimodel.Relationship{ ID: r.ID, Following: r.Following, @@ -980,9 +1008,10 @@ func (c *converter) RelationshipToAPIRelationship(ctx context.Context, r *gtsmod }, nil } -func (c *converter) NotificationToAPINotification(ctx context.Context, n *gtsmodel.Notification) (*apimodel.Notification, error) { +// NotificationToAPINotification converts a gts notification into a api notification +func (c *Converter) NotificationToAPINotification(ctx context.Context, n *gtsmodel.Notification) (*apimodel.Notification, error) { if n.TargetAccount == nil { - tAccount, err := c.db.GetAccountByID(ctx, n.TargetAccountID) + tAccount, err := c.state.DB.GetAccountByID(ctx, n.TargetAccountID) if err != nil { return nil, fmt.Errorf("NotificationToapi: error getting target account with id %s from the db: %s", n.TargetAccountID, err) } @@ -990,7 +1019,7 @@ func (c *converter) NotificationToAPINotification(ctx context.Context, n *gtsmod } if n.OriginAccount == nil { - ogAccount, err := c.db.GetAccountByID(ctx, n.OriginAccountID) + ogAccount, err := c.state.DB.GetAccountByID(ctx, n.OriginAccountID) if err != nil { return nil, fmt.Errorf("NotificationToapi: error getting origin account with id %s from the db: %s", n.OriginAccountID, err) } @@ -1005,7 +1034,7 @@ func (c *converter) NotificationToAPINotification(ctx context.Context, n *gtsmod var apiStatus *apimodel.Status if n.StatusID != "" { if n.Status == nil { - status, err := c.db.GetStatusByID(ctx, n.StatusID) + status, err := c.state.DB.GetStatusByID(ctx, n.StatusID) if err != nil { return nil, fmt.Errorf("NotificationToapi: error getting status with id %s from the db: %s", n.StatusID, err) } @@ -1041,7 +1070,8 @@ func (c *converter) NotificationToAPINotification(ctx context.Context, n *gtsmod }, nil } -func (c *converter) DomainPermToAPIDomainPerm( +// DomainPermToAPIDomainPerm converts a gts model domin block or allow into an api domain permission. +func (c *Converter) DomainPermToAPIDomainPerm( ctx context.Context, d gtsmodel.DomainPermission, export bool, @@ -1076,7 +1106,8 @@ func (c *converter) DomainPermToAPIDomainPerm( return domainPerm, nil } -func (c *converter) ReportToAPIReport(ctx context.Context, r *gtsmodel.Report) (*apimodel.Report, error) { +// ReportToAPIReport converts a gts model report into an api model report, for serving at /api/v1/reports +func (c *Converter) ReportToAPIReport(ctx context.Context, r *gtsmodel.Report) (*apimodel.Report, error) { report := &apimodel.Report{ ID: r.ID, CreatedAt: util.FormatISO8601(r.CreatedAt), @@ -1098,7 +1129,7 @@ func (c *converter) ReportToAPIReport(ctx context.Context, r *gtsmodel.Report) ( } if r.TargetAccount == nil { - tAccount, err := c.db.GetAccountByID(ctx, r.TargetAccountID) + tAccount, err := c.state.DB.GetAccountByID(ctx, r.TargetAccountID) if err != nil { return nil, fmt.Errorf("ReportToAPIReport: error getting target account with id %s from the db: %s", r.TargetAccountID, err) } @@ -1114,7 +1145,8 @@ func (c *converter) ReportToAPIReport(ctx context.Context, r *gtsmodel.Report) ( return report, nil } -func (c *converter) ReportToAdminAPIReport(ctx context.Context, r *gtsmodel.Report, requestingAccount *gtsmodel.Account) (*apimodel.AdminReport, error) { +// ReportToAdminAPIReport converts a gts model report into an admin view report, for serving at /api/v1/admin/reports +func (c *Converter) ReportToAdminAPIReport(ctx context.Context, r *gtsmodel.Report, requestingAccount *gtsmodel.Account) (*apimodel.AdminReport, error) { var ( err error actionTakenAt *string @@ -1128,7 +1160,7 @@ func (c *converter) ReportToAdminAPIReport(ctx context.Context, r *gtsmodel.Repo } if r.Account == nil { - r.Account, err = c.db.GetAccountByID(ctx, r.AccountID) + r.Account, err = c.state.DB.GetAccountByID(ctx, r.AccountID) if err != nil { return nil, fmt.Errorf("ReportToAdminAPIReport: error getting account with id %s from the db: %w", r.AccountID, err) } @@ -1139,7 +1171,7 @@ func (c *converter) ReportToAdminAPIReport(ctx context.Context, r *gtsmodel.Repo } if r.TargetAccount == nil { - r.TargetAccount, err = c.db.GetAccountByID(ctx, r.TargetAccountID) + r.TargetAccount, err = c.state.DB.GetAccountByID(ctx, r.TargetAccountID) if err != nil { return nil, fmt.Errorf("ReportToAdminAPIReport: error getting target account with id %s from the db: %w", r.TargetAccountID, err) } @@ -1151,7 +1183,7 @@ func (c *converter) ReportToAdminAPIReport(ctx context.Context, r *gtsmodel.Repo if r.ActionTakenByAccountID != "" { if r.ActionTakenByAccount == nil { - r.ActionTakenByAccount, err = c.db.GetAccountByID(ctx, r.ActionTakenByAccountID) + r.ActionTakenByAccount, err = c.state.DB.GetAccountByID(ctx, r.ActionTakenByAccountID) if err != nil { return nil, fmt.Errorf("ReportToAdminAPIReport: error getting action taken by account with id %s from the db: %w", r.ActionTakenByAccountID, err) } @@ -1165,7 +1197,7 @@ func (c *converter) ReportToAdminAPIReport(ctx context.Context, r *gtsmodel.Repo statuses := make([]*apimodel.Status, 0, len(r.StatusIDs)) if len(r.StatusIDs) != 0 && len(r.Statuses) == 0 { - r.Statuses, err = c.db.GetStatusesByIDs(ctx, r.StatusIDs) + r.Statuses, err = c.state.DB.GetStatusesByIDs(ctx, r.StatusIDs) if err != nil { return nil, fmt.Errorf("ReportToAdminAPIReport: error getting statuses from the db: %w", err) } @@ -1180,7 +1212,7 @@ func (c *converter) ReportToAdminAPIReport(ctx context.Context, r *gtsmodel.Repo rules := make([]*apimodel.InstanceRule, 0, len(r.RuleIDs)) if len(r.RuleIDs) != 0 && len(r.Rules) == 0 { - r.Rules, err = c.db.GetRulesByIDs(ctx, r.RuleIDs) + r.Rules, err = c.state.DB.GetRulesByIDs(ctx, r.RuleIDs) if err != nil { return nil, fmt.Errorf("ReportToAdminAPIReport: error getting rules from the db: %w", err) } @@ -1215,7 +1247,8 @@ func (c *converter) ReportToAdminAPIReport(ctx context.Context, r *gtsmodel.Repo }, nil } -func (c *converter) ListToAPIList(ctx context.Context, l *gtsmodel.List) (*apimodel.List, error) { +// ListToAPIList converts one gts model list into an api model list, for serving at /api/v1/lists/{id} +func (c *Converter) ListToAPIList(ctx context.Context, l *gtsmodel.List) (*apimodel.List, error) { return &apimodel.List{ ID: l.ID, Title: l.Title, @@ -1223,7 +1256,8 @@ func (c *converter) ListToAPIList(ctx context.Context, l *gtsmodel.List) (*apimo }, nil } -func (c *converter) MarkersToAPIMarker(ctx context.Context, markers []*gtsmodel.Marker) (*apimodel.Marker, error) { +// MarkersToAPIMarker converts several gts model markers into an api marker, for serving at /api/v1/markers +func (c *Converter) MarkersToAPIMarker(ctx context.Context, markers []*gtsmodel.Marker) (*apimodel.Marker, error) { apiMarker := &apimodel.Marker{} for _, marker := range markers { apiTimelineMarker := &apimodel.TimelineMarker{ @@ -1244,7 +1278,7 @@ func (c *converter) MarkersToAPIMarker(ctx context.Context, markers []*gtsmodel. } // convertAttachmentsToAPIAttachments will convert a slice of GTS model attachments to frontend API model attachments, falling back to IDs if no GTS models supplied. -func (c *converter) convertAttachmentsToAPIAttachments(ctx context.Context, attachments []*gtsmodel.MediaAttachment, attachmentIDs []string) ([]apimodel.Attachment, error) { +func (c *Converter) convertAttachmentsToAPIAttachments(ctx context.Context, attachments []*gtsmodel.MediaAttachment, attachmentIDs []string) ([]apimodel.Attachment, error) { var errs gtserror.MultiError if len(attachments) == 0 { @@ -1255,7 +1289,7 @@ func (c *converter) convertAttachmentsToAPIAttachments(ctx context.Context, atta // Fetch GTS models for attachment IDs for _, id := range attachmentIDs { - attachment, err := c.db.GetAttachmentByID(ctx, id) + attachment, err := c.state.DB.GetAttachmentByID(ctx, id) if err != nil { errs.Appendf("error fetching attachment %s from database: %v", id, err) continue @@ -1281,7 +1315,7 @@ func (c *converter) convertAttachmentsToAPIAttachments(ctx context.Context, atta } // convertEmojisToAPIEmojis will convert a slice of GTS model emojis to frontend API model emojis, falling back to IDs if no GTS models supplied. -func (c *converter) convertEmojisToAPIEmojis(ctx context.Context, emojis []*gtsmodel.Emoji, emojiIDs []string) ([]apimodel.Emoji, error) { +func (c *Converter) convertEmojisToAPIEmojis(ctx context.Context, emojis []*gtsmodel.Emoji, emojiIDs []string) ([]apimodel.Emoji, error) { var errs gtserror.MultiError if len(emojis) == 0 { @@ -1292,7 +1326,7 @@ func (c *converter) convertEmojisToAPIEmojis(ctx context.Context, emojis []*gtsm // Fetch GTS models for emoji IDs for _, id := range emojiIDs { - emoji, err := c.db.GetEmojiByID(ctx, id) + emoji, err := c.state.DB.GetEmojiByID(ctx, id) if err != nil { errs.Appendf("error fetching emoji %s from database: %v", id, err) continue @@ -1318,7 +1352,7 @@ func (c *converter) convertEmojisToAPIEmojis(ctx context.Context, emojis []*gtsm } // convertMentionsToAPIMentions will convert a slice of GTS model mentions to frontend API model mentions, falling back to IDs if no GTS models supplied. -func (c *converter) convertMentionsToAPIMentions(ctx context.Context, mentions []*gtsmodel.Mention, mentionIDs []string) ([]apimodel.Mention, error) { +func (c *Converter) convertMentionsToAPIMentions(ctx context.Context, mentions []*gtsmodel.Mention, mentionIDs []string) ([]apimodel.Mention, error) { var errs gtserror.MultiError if len(mentions) == 0 { @@ -1327,7 +1361,7 @@ func (c *converter) convertMentionsToAPIMentions(ctx context.Context, mentions [ // GTS model mentions were not populated // // Fetch GTS models for mention IDs - mentions, err = c.db.GetMentions(ctx, mentionIDs) + mentions, err = c.state.DB.GetMentions(ctx, mentionIDs) if err != nil { errs.Appendf("error fetching mentions from database: %v", err) } @@ -1350,13 +1384,13 @@ func (c *converter) convertMentionsToAPIMentions(ctx context.Context, mentions [ } // convertTagsToAPITags will convert a slice of GTS model tags to frontend API model tags, falling back to IDs if no GTS models supplied. -func (c *converter) convertTagsToAPITags(ctx context.Context, tags []*gtsmodel.Tag, tagIDs []string) ([]apimodel.Tag, error) { +func (c *Converter) convertTagsToAPITags(ctx context.Context, tags []*gtsmodel.Tag, tagIDs []string) ([]apimodel.Tag, error) { var errs gtserror.MultiError if len(tags) == 0 { var err error - tags, err = c.db.GetTags(ctx, tagIDs) + tags, err = c.state.DB.GetTags(ctx, tagIDs) if err != nil { errs.Appendf("error fetching tags from database: %v", err) } diff --git a/internal/typeutils/internaltorss.go b/internal/typeutils/internaltorss.go index adec26a30..e70b11aae 100644 --- a/internal/typeutils/internaltorss.go +++ b/internal/typeutils/internaltorss.go @@ -36,7 +36,7 @@ const ( rssDescriptionMaxRunes = 256 ) -func (c *converter) StatusToRSSItem(ctx context.Context, s *gtsmodel.Status) (*feeds.Item, error) { +func (c *Converter) StatusToRSSItem(ctx context.Context, s *gtsmodel.Status) (*feeds.Item, error) { // see https://cyber.harvard.edu/rss/rss.html // Title -- The title of the item. @@ -57,7 +57,7 @@ func (c *converter) StatusToRSSItem(ctx context.Context, s *gtsmodel.Status) (*f // Author -- Email address of the author of the item. // example: oprah\@oxygen.net if s.Account == nil { - a, err := c.db.GetAccountByID(ctx, s.AccountID) + a, err := c.state.DB.GetAccountByID(ctx, s.AccountID) if err != nil { return nil, fmt.Errorf("error getting status author: %s", err) } @@ -110,7 +110,7 @@ func (c *converter) StatusToRSSItem(ctx context.Context, s *gtsmodel.Status) (*f if len(s.Attachments) > 0 { attachment = s.Attachments[0] } else if len(s.AttachmentIDs) > 0 { - a, err := c.db.GetAttachmentByID(ctx, s.AttachmentIDs[0]) + a, err := c.state.DB.GetAttachmentByID(ctx, s.AttachmentIDs[0]) if err == nil { attachment = a } @@ -139,7 +139,7 @@ func (c *converter) StatusToRSSItem(ctx context.Context, s *gtsmodel.Status) (*f } else { for _, e := range s.EmojiIDs { gtsEmoji := >smodel.Emoji{} - if err := c.db.GetByID(ctx, e, gtsEmoji); err != nil { + if err := c.state.DB.GetByID(ctx, e, gtsEmoji); err != nil { log.Errorf(ctx, "error getting emoji with id %s: %s", e, err) continue } diff --git a/internal/typeutils/util.go b/internal/typeutils/util.go index 42387afec..86acf4595 100644 --- a/internal/typeutils/util.go +++ b/internal/typeutils/util.go @@ -36,29 +36,29 @@ type statusInteractions struct { Pinned bool } -func (c *converter) interactionsWithStatusForAccount(ctx context.Context, s *gtsmodel.Status, requestingAccount *gtsmodel.Account) (*statusInteractions, error) { +func (c *Converter) interactionsWithStatusForAccount(ctx context.Context, s *gtsmodel.Status, requestingAccount *gtsmodel.Account) (*statusInteractions, error) { si := &statusInteractions{} if requestingAccount != nil { - faved, err := c.db.IsStatusFavedBy(ctx, s.ID, requestingAccount.ID) + faved, err := c.state.DB.IsStatusFavedBy(ctx, s.ID, requestingAccount.ID) if err != nil { return nil, fmt.Errorf("error checking if requesting account has faved status: %s", err) } si.Faved = faved - reblogged, err := c.db.IsStatusBoostedBy(ctx, s.ID, requestingAccount.ID) + reblogged, err := c.state.DB.IsStatusBoostedBy(ctx, s.ID, requestingAccount.ID) if err != nil { return nil, fmt.Errorf("error checking if requesting account has reblogged status: %s", err) } si.Reblogged = reblogged - muted, err := c.db.IsStatusMutedBy(ctx, s, requestingAccount.ID) + muted, err := c.state.DB.IsStatusMutedBy(ctx, s, requestingAccount.ID) if err != nil { return nil, fmt.Errorf("error checking if requesting account has muted status: %s", err) } si.Muted = muted - bookmarked, err := c.db.IsStatusBookmarkedBy(ctx, s, requestingAccount.ID) + bookmarked, err := c.state.DB.IsStatusBookmarkedBy(ctx, s, requestingAccount.ID) if err != nil { return nil, fmt.Errorf("error checking if requesting account has bookmarked status: %s", err) } diff --git a/internal/typeutils/wrap.go b/internal/typeutils/wrap.go index 71099cbee..67d3c2b5c 100644 --- a/internal/typeutils/wrap.go +++ b/internal/typeutils/wrap.go @@ -30,7 +30,8 @@ import ( "github.com/superseriousbusiness/gotosocial/internal/uris" ) -func (c *converter) WrapPersonInUpdate(person vocab.ActivityStreamsPerson, originAccount *gtsmodel.Account) (vocab.ActivityStreamsUpdate, error) { +// WrapPersonInUpdate ... +func (c *Converter) WrapPersonInUpdate(person vocab.ActivityStreamsPerson, originAccount *gtsmodel.Account) (vocab.ActivityStreamsUpdate, error) { update := streams.NewActivityStreamsUpdate() // set the actor @@ -84,7 +85,12 @@ func (c *converter) WrapPersonInUpdate(person vocab.ActivityStreamsPerson, origi return update, nil } -func (c *converter) WrapNoteInCreate(note vocab.ActivityStreamsNote, objectIRIOnly bool) (vocab.ActivityStreamsCreate, error) { +// WrapNoteInCreate wraps a Note with a Create activity. +// +// If objectIRIOnly is set to true, then the function won't put the *entire* note in the Object field of the Create, +// but just the AP URI of the note. This is useful in cases where you want to give a remote server something to dereference, +// and still have control over whether or not they're allowed to actually see the contents. +func (c *Converter) WrapNoteInCreate(note vocab.ActivityStreamsNote, objectIRIOnly bool) (vocab.ActivityStreamsCreate, error) { create := streams.NewActivityStreamsCreate() // Object property |