summaryrefslogtreecommitdiff
path: root/internal/typeutils
diff options
context:
space:
mode:
Diffstat (limited to 'internal/typeutils')
-rw-r--r--internal/typeutils/astointernal.go4
-rw-r--r--internal/typeutils/internal.go70
-rw-r--r--internal/typeutils/internaltoas.go800
-rw-r--r--internal/typeutils/internaltoas_test.go429
-rw-r--r--internal/typeutils/internaltofrontend.go11
-rw-r--r--internal/typeutils/internaltofrontend_test.go2
-rw-r--r--internal/typeutils/wrap.go2
7 files changed, 923 insertions, 395 deletions
diff --git a/internal/typeutils/astointernal.go b/internal/typeutils/astointernal.go
index ede2b3253..f352632b7 100644
--- a/internal/typeutils/astointernal.go
+++ b/internal/typeutils/astointernal.go
@@ -444,8 +444,8 @@ func (c *Converter) ASStatusToStatus(ctx context.Context, statusable ap.Statusab
// Set approvedByURI if present,
// for later dereferencing.
- if ipa, ok := statusable.(ap.InteractionPolicyAware); ok {
- approvedByURI := ap.GetApprovedBy(ipa)
+ if wab, ok := statusable.(ap.WithApprovedBy); ok {
+ approvedByURI := ap.GetApprovedBy(wab)
if approvedByURI != nil {
status.ApprovedByURI = approvedByURI.String()
}
diff --git a/internal/typeutils/internal.go b/internal/typeutils/internal.go
index 4504168ef..a18b7a287 100644
--- a/internal/typeutils/internal.go
+++ b/internal/typeutils/internal.go
@@ -104,17 +104,18 @@ func (c *Converter) StatusToBoost(
return boost, nil
}
-func StatusToInteractionRequest(status *gtsmodel.Status) *gtsmodel.InteractionRequest {
+func StatusToImpoliteInteractionRequest(status *gtsmodel.Status) *gtsmodel.InteractionRequest {
reqID := id.NewULIDFromTime(status.CreatedAt)
var (
- targetID string
- target *gtsmodel.Status
- targetAccountID string
- targetAccount *gtsmodel.Account
- interactionType gtsmodel.InteractionType
- reply *gtsmodel.Status
- announce *gtsmodel.Status
+ targetID string
+ target *gtsmodel.Status
+ targetAccountID string
+ targetAccount *gtsmodel.Account
+ interactionRequestURI string
+ interactionType gtsmodel.InteractionType
+ reply *gtsmodel.Status
+ announce *gtsmodel.Status
)
if status.InReplyToID != "" {
@@ -123,6 +124,7 @@ func StatusToInteractionRequest(status *gtsmodel.Status) *gtsmodel.InteractionRe
target = status.InReplyTo
targetAccountID = status.InReplyToAccountID
targetAccount = status.InReplyToAccount
+ interactionRequestURI = gtsmodel.ForwardCompatibleInteractionRequestURI(status.URI, gtsmodel.ReplyRequestSuffix)
interactionType = gtsmodel.InteractionReply
reply = status
} else {
@@ -131,41 +133,43 @@ func StatusToInteractionRequest(status *gtsmodel.Status) *gtsmodel.InteractionRe
target = status.BoostOf
targetAccountID = status.BoostOfAccountID
targetAccount = status.BoostOfAccount
+ interactionRequestURI = gtsmodel.ForwardCompatibleInteractionRequestURI(status.URI, gtsmodel.AnnounceRequestSuffix)
interactionType = gtsmodel.InteractionAnnounce
announce = status
}
return &gtsmodel.InteractionRequest{
- ID: reqID,
- CreatedAt: status.CreatedAt,
- StatusID: targetID,
- Status: target,
- TargetAccountID: targetAccountID,
- TargetAccount: targetAccount,
- InteractingAccountID: status.AccountID,
- InteractingAccount: status.Account,
- InteractionURI: status.URI,
- InteractionType: interactionType,
- Reply: reply,
- Announce: announce,
+ ID: reqID,
+ TargetStatusID: targetID,
+ TargetStatus: target,
+ TargetAccountID: targetAccountID,
+ TargetAccount: targetAccount,
+ InteractingAccountID: status.AccountID,
+ InteractingAccount: status.Account,
+ InteractionRequestURI: interactionRequestURI,
+ InteractionURI: status.URI,
+ InteractionType: interactionType,
+ Polite: util.Ptr(false),
+ Reply: reply,
+ Announce: announce,
}
}
-func StatusFaveToInteractionRequest(fave *gtsmodel.StatusFave) *gtsmodel.InteractionRequest {
+func StatusFaveToImpoliteInteractionRequest(fave *gtsmodel.StatusFave) *gtsmodel.InteractionRequest {
reqID := id.NewULIDFromTime(fave.CreatedAt)
-
return &gtsmodel.InteractionRequest{
- ID: reqID,
- CreatedAt: fave.CreatedAt,
- StatusID: fave.StatusID,
- Status: fave.Status,
- TargetAccountID: fave.TargetAccountID,
- TargetAccount: fave.TargetAccount,
- InteractingAccountID: fave.AccountID,
- InteractingAccount: fave.Account,
- InteractionURI: fave.URI,
- InteractionType: gtsmodel.InteractionLike,
- Like: fave,
+ ID: reqID,
+ TargetStatusID: fave.StatusID,
+ TargetStatus: fave.Status,
+ TargetAccountID: fave.TargetAccountID,
+ TargetAccount: fave.TargetAccount,
+ InteractingAccountID: fave.AccountID,
+ InteractingAccount: fave.Account,
+ InteractionRequestURI: gtsmodel.ForwardCompatibleInteractionRequestURI(fave.URI, gtsmodel.LikeRequestSuffix),
+ InteractionURI: fave.URI,
+ InteractionType: gtsmodel.InteractionLike,
+ Polite: util.Ptr(false),
+ Like: fave,
}
}
diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go
index 7cf736993..feb794e51 100644
--- a/internal/typeutils/internaltoas.go
+++ b/internal/typeutils/internaltoas.go
@@ -33,6 +33,7 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/ap"
"code.superseriousbusiness.org/gotosocial/internal/config"
"code.superseriousbusiness.org/gotosocial/internal/db"
+ "code.superseriousbusiness.org/gotosocial/internal/gtscontext"
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
"code.superseriousbusiness.org/gotosocial/internal/log"
@@ -489,8 +490,9 @@ func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (ap.Stat
return nil, gtserror.Newf("error populating status: %w", err)
}
- var status ap.Statusable
-
+ // TODO: in future, allow longer
+ // posts to be federated as Articles.
+ var statusable ap.Statusable
if s.Poll != nil {
// If status has poll available, we convert
// it as an AS Question (similar to a Note).
@@ -502,80 +504,56 @@ func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (ap.Stat
}
// Set poll as status.
- status = poll
+ statusable = poll
} else {
// Else we converter it as an AS Note.
- status = streams.NewActivityStreamsNote()
+ statusable = streams.NewActivityStreamsNote()
}
- // id
- statusURI, err := url.Parse(s.URI)
- if err != nil {
- return nil, gtserror.Newf("error parsing url %s: %w", s.URI, err)
+ // `id` property.
+ if err := ap.SetJSONLDIdStr(statusable, s.URI); err != nil {
+ return nil, gtserror.Newf("error setting id: %w", err)
}
- statusIDProp := streams.NewJSONLDIdProperty()
- statusIDProp.SetIRI(statusURI)
- status.SetJSONLDId(statusIDProp)
- // type
- // will be set automatically by go-fed
+ // `summary` property.
+ ap.AppendSummary(statusable, s.ContentWarning)
- // summary aka cw
- statusSummaryProp := streams.NewActivityStreamsSummaryProperty()
- statusSummaryProp.AppendXMLSchemaString(s.ContentWarning)
- status.SetActivityStreamsSummary(statusSummaryProp)
-
- // inReplyTo
+ // `inReplyTo` property.
if s.InReplyToURI != "" {
rURI, err := url.Parse(s.InReplyToURI)
if err != nil {
- return nil, gtserror.Newf("error parsing url %s: %w", s.InReplyToURI, err)
+ return nil, gtserror.Newf("error parsing inReplyTo: %w", err)
}
-
- inReplyToProp := streams.NewActivityStreamsInReplyToProperty()
- inReplyToProp.AppendIRI(rURI)
- status.SetActivityStreamsInReplyTo(inReplyToProp)
+ ap.AppendInReplyTo(statusable, rURI)
}
- // Set created / updated at properties.
- ap.SetPublished(status, s.CreatedAt)
+ // `published` and `updatedAt` properties.
+ ap.SetPublished(statusable, s.CreatedAt)
if at := s.EditedAt; !at.IsZero() {
- ap.SetUpdated(status, at)
+ ap.SetUpdated(statusable, at)
}
- // url
+ // Web-accessible `url` property.
if s.URL != "" {
sURL, err := url.Parse(s.URL)
if err != nil {
- return nil, gtserror.Newf("error parsing url %s: %w", s.URL, err)
+ return nil, gtserror.Newf("error parsing url: %w", err)
}
-
- urlProp := streams.NewActivityStreamsUrlProperty()
- urlProp.AppendIRI(sURL)
- status.SetActivityStreamsUrl(urlProp)
+ ap.AppendURL(statusable, sURL)
}
- // attributedTo
- authorAccountURI, err := url.Parse(s.Account.URI)
+ // `attributedTo` property.
+ acctURI, err := url.Parse(s.Account.URI)
if err != nil {
- return nil, gtserror.Newf("error parsing url %s: %w", s.Account.URI, err)
+ return nil, gtserror.Newf("error parsing account uri: %w", err)
}
- attributedToProp := streams.NewActivityStreamsAttributedToProperty()
- attributedToProp.AppendIRI(authorAccountURI)
- status.SetActivityStreamsAttributedTo(attributedToProp)
+ ap.AppendAttributedTo(statusable, acctURI)
- // tags
+ // Start building out `tag` property.
tagProp := streams.NewActivityStreamsTagProperty()
- // tag -- mentions
- mentions := s.Mentions
- if len(s.MentionIDs) != len(mentions) {
- mentions, err = c.state.DB.GetMentions(ctx, s.MentionIDs)
- if err != nil {
- return nil, gtserror.Newf("error getting mentions: %w", err)
- }
- }
- for _, m := range mentions {
+ // `tag`: mentions
+ for _, m := range s.Mentions {
asMention, err := c.MentionToAS(ctx, m)
if err != nil {
return nil, gtserror.Newf("error converting mention to AS mention: %w", err)
@@ -583,139 +561,109 @@ func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (ap.Stat
tagProp.AppendActivityStreamsMention(asMention)
}
- // tag -- emojis
- emojis := s.Emojis
- if len(s.EmojiIDs) != len(emojis) {
- emojis, err = c.state.DB.GetEmojisByIDs(ctx, s.EmojiIDs)
- if err != nil {
- return nil, gtserror.Newf("error getting emojis from database: %w", err)
- }
- }
- for _, emoji := range emojis {
- asEmoji, err := c.EmojiToAS(ctx, emoji)
+ // `tag`: emojis
+ for _, e := range s.Emojis {
+ asEmoji, err := c.EmojiToAS(ctx, e)
if err != nil {
return nil, gtserror.Newf("error converting emoji to AS emoji: %w", err)
}
tagProp.AppendTootEmoji(asEmoji)
}
- // tag -- hashtags
- hashtags := s.Tags
- if len(s.TagIDs) != len(hashtags) {
- hashtags, err = c.state.DB.GetTags(ctx, s.TagIDs)
- if err != nil {
- return nil, gtserror.Newf("error getting tags: %w", err)
- }
- }
- for _, ht := range hashtags {
- asHashtag, err := c.TagToAS(ctx, ht)
+ // `tag`: hashtags
+ for _, t := range s.Tags {
+ asHashtag, err := c.TagToAS(ctx, t)
if err != nil {
return nil, gtserror.Newf("error converting tag to AS tag: %w", err)
}
tagProp.AppendTootHashtag(asHashtag)
}
- status.SetActivityStreamsTag(tagProp)
-
- // parse out some URIs we need here
- authorFollowersURI, err := url.Parse(s.Account.FollowersURI)
- if err != nil {
- return nil, gtserror.Newf("error parsing url %s: %w", s.Account.FollowersURI, err)
- }
- publicURI, err := url.Parse(pub.PublicActivityPubIRI)
- if err != nil {
- return nil, gtserror.Newf("error parsing url %s: %w", pub.PublicActivityPubIRI, err)
- }
+ // Append built `tag` property.
+ statusable.SetActivityStreamsTag(tagProp)
- // to and cc
- toProp := streams.NewActivityStreamsToProperty()
- ccProp := streams.NewActivityStreamsCcProperty()
- switch s.Visibility {
- case gtsmodel.VisibilityDirect:
- // if DIRECT, then only mentioned users should be added to TO, and nothing to CC
- for _, m := range mentions {
+ // `to` and `cc` properties
+ // depend on visibility of post.
+ if s.Visibility == gtsmodel.VisibilityDirect {
+ // If DIRECT visibility, then only mentioned
+ // users should be added to TO, nothing in CC.
+ for _, m := range s.Mentions {
iri, err := url.Parse(m.TargetAccount.URI)
if err != nil {
- return nil, gtserror.Newf("error parsing uri %s: %w", m.TargetAccount.URI, err)
+ return nil, gtserror.Newf("error parsing mention target: %w", err)
}
- toProp.AppendIRI(iri)
+ ap.AppendTo(statusable, iri)
}
- case gtsmodel.VisibilityMutualsOnly:
- // TODO
- case gtsmodel.VisibilityFollowersOnly:
- // if FOLLOWERS ONLY then we want to add followers to TO, and mentions to CC
- toProp.AppendIRI(authorFollowersURI)
- for _, m := range mentions {
- iri, err := url.Parse(m.TargetAccount.URI)
- if err != nil {
- return nil, gtserror.Newf("error parsing uri %s: %w", m.TargetAccount.URI, err)
- }
- ccProp.AppendIRI(iri)
+
+ } else {
+ // For all other visibilities
+ // we need the followers URI.
+ followersURI, err := url.Parse(s.Account.FollowersURI)
+ if err != nil {
+ return nil, gtserror.Newf("error parsing followers url: %w", err)
}
- case gtsmodel.VisibilityUnlocked:
- // if UNLOCKED, we want to add followers to TO, and public and mentions to CC
- toProp.AppendIRI(authorFollowersURI)
- ccProp.AppendIRI(publicURI)
- for _, m := range mentions {
- iri, err := url.Parse(m.TargetAccount.URI)
- if err != nil {
- return nil, gtserror.Newf("error parsing uri %s: %w", m.TargetAccount.URI, err)
- }
- ccProp.AppendIRI(iri)
+
+ switch s.Visibility {
+ // If FOLLOWERS ONLY visibility, then
+ // we want to add followers to TO.
+ case gtsmodel.VisibilityFollowersOnly:
+ ap.AppendTo(statusable, followersURI)
+
+ // If UNLOCKED visibility, then
+ // we want to add followers to TO,
+ // with public in CC.
+ case gtsmodel.VisibilityUnlocked:
+ ap.AppendTo(statusable, followersURI)
+ ap.AppendCc(statusable, ap.PublicIRI())
+
+ // If PUBLIC visibility, then
+ // we want to add public to TO,
+ // with followers in CC.
+ case gtsmodel.VisibilityPublic:
+ ap.AppendTo(statusable, ap.PublicIRI())
+ ap.AppendCc(statusable, followersURI)
}
- case gtsmodel.VisibilityPublic:
- // if PUBLIC, we want to add public to TO, and followers and mentions to CC
- toProp.AppendIRI(publicURI)
- ccProp.AppendIRI(authorFollowersURI)
- for _, m := range mentions {
+
+ // In all non-direct cases,
+ // mentioned accounts go in Cc.
+ for _, m := range s.Mentions {
iri, err := url.Parse(m.TargetAccount.URI)
if err != nil {
return nil, gtserror.Newf("error parsing uri %s: %w", m.TargetAccount.URI, err)
}
- ccProp.AppendIRI(iri)
+ ap.AppendCc(statusable, iri)
}
}
- status.SetActivityStreamsTo(toProp)
- status.SetActivityStreamsCc(ccProp)
-
- // conversation
- // TODO
-
- // content -- the actual post
- // itself, plus the language
- contentProp := streams.NewActivityStreamsContentProperty()
- contentProp.AppendXMLSchemaString(s.Content)
+ // `content` and `contentMap` properties.
+ ap.AppendContent(statusable, s.Content)
if s.Language != "" {
- contentProp.AppendRDFLangString(map[string]string{
- s.Language: s.Content,
- })
+ ap.AppendContentMap(
+ statusable,
+ map[string]string{
+ s.Language: s.Content,
+ },
+ )
}
- status.SetActivityStreamsContent(contentProp)
-
- // attachments
- if err := c.attachAttachments(ctx, s, status); err != nil {
+ // `attachment` property.
+ if err := c.attachAttachments(ctx, s, statusable); err != nil {
return nil, gtserror.Newf("error attaching attachments: %w", err)
}
- // replies
- repliesCollection, err := c.StatusToASRepliesCollection(ctx, s, false)
+ // `replies` collection property.
+ // Todo: add `likes` and `shares` properties.
+ replies, err := c.StatusToASRepliesCollection(ctx, s, false)
if err != nil {
- return nil, fmt.Errorf("error creating repliesCollection: %w", err)
+ return nil, gtserror.Newf("error creating repliesCollection: %w", err)
}
+ ap.SetReplies(statusable, replies)
- repliesProp := streams.NewActivityStreamsRepliesProperty()
- repliesProp.SetActivityStreamsCollection(repliesCollection)
- status.SetActivityStreamsReplies(repliesProp)
+ // `sensitive` property.
+ ap.AppendSensitive(statusable, *s.Sensitive)
- // sensitive
- sensitiveProp := streams.NewActivityStreamsSensitiveProperty()
- sensitiveProp.AppendXMLSchemaBoolean(*s.Sensitive)
- status.SetActivityStreamsSensitive(sensitiveProp)
-
- // interactionPolicy
- if ipa, ok := status.(ap.InteractionPolicyAware); ok {
+ // `interactionPolicy` property.
+ if wip, ok := statusable.(ap.WithInteractionPolicy); ok {
var p *gtsmodel.InteractionPolicy
if s.InteractionPolicy != nil {
// Use InteractionPolicy
@@ -728,28 +676,29 @@ func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (ap.Stat
}
policy, err := c.InteractionPolicyToASInteractionPolicy(ctx, p, s)
if err != nil {
- return nil, fmt.Errorf("error creating interactionPolicy: %w", err)
+ return nil, gtserror.Newf("error creating interactionPolicy: %w", err)
}
// Set interaction policy.
policyProp := streams.NewGoToSocialInteractionPolicyProperty()
policyProp.AppendGoToSocialInteractionPolicy(policy)
- ipa.SetGoToSocialInteractionPolicy(policyProp)
+ wip.SetGoToSocialInteractionPolicy(policyProp)
+ }
- // Parse + set approvedBy.
- if s.ApprovedByURI != "" {
- approvedBy, err := url.Parse(s.ApprovedByURI)
- if err != nil {
- return nil, fmt.Errorf("error parsing approvedBy: %w", err)
- }
+ // `approvedBy` and/or `replyAuthorization` property.
+ if s.ApprovedByURI != "" {
+ err := c.appendASInteractionAuthorization(
+ ctx,
+ s.ApprovedByURI,
+ statusable,
+ )
- approvedByProp := streams.NewGoToSocialApprovedByProperty()
- approvedByProp.Set(approvedBy)
- ipa.SetGoToSocialApprovedBy(approvedByProp)
+ if err != nil {
+ return nil, gtserror.Newf("error setting reply authorization field(s): %w", err)
}
}
- return status, nil
+ return statusable, nil
}
func (c *Converter) addPollToAS(poll *gtsmodel.Poll, dst ap.Pollable) error {
@@ -875,10 +824,7 @@ func (c *Converter) StatusToASDelete(ctx context.Context, s *gtsmodel.Status) (v
// At worst, a remote instance becomes aware of the
// URI for a status which is now deleted anyway.
if s.Visibility != gtsmodel.VisibilityDirect {
- publicURI, err := url.Parse(pub.PublicActivityPubIRI)
- if err != nil {
- return nil, fmt.Errorf("StatusToASDelete: error parsing url %s: %w", pub.PublicActivityPubIRI, err)
- }
+ publicURI := ap.PublicIRI()
toProp.AppendIRI(publicURI)
actorFollowersURI, err := url.Parse(s.Account.FollowersURI)
@@ -1227,180 +1173,137 @@ func (c *Converter) attachAttachments(
// 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"
-// }
+// {
+// "@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.state.DB.GetStatusByID(ctx, f.StatusID)
- if err != nil {
- return nil, fmt.Errorf("FaveToAS: error fetching target status from database: %s", err)
- }
- f.Status = s
- }
-
- // check if the targetAccount is already pinned to this fave, and fetch it if not
- if f.TargetAccount == nil {
- 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)
- }
- f.TargetAccount = a
- }
-
- // check if the faving account is already pinned to this fave, and fetch it if not
- if f.Account == nil {
- 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)
- }
- f.Account = a
+ // Ensure the status fave model is fully populated.
+ if err := c.state.DB.PopulateStatusFave(ctx, f); err != nil {
+ return nil, gtserror.Newf("error populating status fave: %w", err)
}
- // create the like
+ // Start building the Like.
like := streams.NewActivityStreamsLike()
- // set the actor property to the fave-ing account's URI
- actorProp := streams.NewActivityStreamsActorProperty()
- actorIRI, err := url.Parse(f.Account.URI)
- if err != nil {
- return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.Account.URI, err)
+ // `id` property.
+ if err := ap.SetJSONLDIdStr(like, f.URI); err != nil {
+ return nil, gtserror.Newf("error setting id: %w", err)
}
- actorProp.AppendIRI(actorIRI)
- like.SetActivityStreamsActor(actorProp)
- // set the ID property to the fave's URI
- idProp := streams.NewJSONLDIdProperty()
- idIRI, err := url.Parse(f.URI)
+ // `actor` property is the faving account URI.
+ actorIRI, err := url.Parse(f.Account.URI)
if err != nil {
- return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.URI, err)
+ return nil, gtserror.Newf("error parsing actor uri: %w", err)
}
- idProp.Set(idIRI)
- like.SetJSONLDId(idProp)
+ ap.AppendActorIRIs(like, actorIRI)
- // set the object property to the target status's URI
- objectProp := streams.NewActivityStreamsObjectProperty()
- statusIRI, err := url.Parse(f.Status.URI)
+ // `object` property is the target status URI.
+ targetStatusIRI, err := url.Parse(f.Status.URI)
if err != nil {
- return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.Status.URI, err)
+ return nil, gtserror.Newf("error parsing status uri: %w", err)
}
- objectProp.AppendIRI(statusIRI)
- like.SetActivityStreamsObject(objectProp)
+ ap.AppendObjectIRIs(like, targetStatusIRI)
- // set the TO property to the target account's IRI
- toProp := streams.NewActivityStreamsToProperty()
+ // `to` is the owner of the target status.
toIRI, err := url.Parse(f.TargetAccount.URI)
if err != nil {
- return nil, fmt.Errorf("FaveToAS: error parsing uri %s: %s", f.TargetAccount.URI, err)
+ return nil, gtserror.Newf("error parsing account uri: %w", err)
}
- toProp.AppendIRI(toIRI)
- like.SetActivityStreamsTo(toProp)
+ ap.AppendTo(like, toIRI)
- // Parse + set approvedBy.
+ // Parse + set authorization.
if f.ApprovedByURI != "" {
- approvedBy, err := url.Parse(f.ApprovedByURI)
+ err := c.appendASInteractionAuthorization(
+ ctx,
+ f.ApprovedByURI,
+ like,
+ )
if err != nil {
- return nil, fmt.Errorf("error parsing approvedBy: %w", err)
+ return nil, gtserror.Newf("error setting like authorization field(s): %w", err)
}
-
- approvedByProp := streams.NewGoToSocialApprovedByProperty()
- approvedByProp.Set(approvedBy)
- like.SetGoToSocialApprovedBy(approvedByProp)
}
return like, nil
}
-// 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.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)
- }
- boostWrapperStatus.BoostOf = b
+// BoostToAS converts a *gtsmodel.Status boost wrapper into
+// an ActivityStreams Announce activity, suitable for federation.
+//
+// Result will look something like:
+//
+// {
+// "@context": "https://www.w3.org/ns/activitystreams",
+// "actor": "http://localhost:8080/users/the_mighty_zork",
+// "cc": "http://localhost:8080/users/the_mighty_zork",
+// "id": "http://localhost:8080/users/the_mighty_zork/statuses/01G74JJ1KS331G2JXHRMZCE0ER",
+// "object": "http://localhost:8080/users/the_mighty_zork/statuses/01FCTA44PW9H1TB328S9AQXKDS",
+// "published": "2022-06-09T13:12:00Z",
+// "to": "http://localhost:8080/users/the_mighty_zork/followers",
+// "type": "Announce"
+// }
+func (c *Converter) BoostToAS(ctx context.Context, bw *gtsmodel.Status) (vocab.ActivityStreamsAnnounce, error) {
+ // Ensure the status model is fully populated.
+ if err := c.state.DB.PopulateStatus(ctx, bw); err != nil {
+ return nil, gtserror.Newf("error populating boost wrapper status: %w", err)
}
- // create the announce
+ // Start building the Announce.
announce := streams.NewActivityStreamsAnnounce()
- // set the actor
- boosterURI, err := url.Parse(boostingAccount.URI)
- if err != nil {
- return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostingAccount.URI, err)
+ // `id` property.
+ if err := ap.SetJSONLDIdStr(announce, bw.URI); err != nil {
+ return nil, gtserror.Newf("error setting id: %w", err)
}
- actorProp := streams.NewActivityStreamsActorProperty()
- actorProp.AppendIRI(boosterURI)
- announce.SetActivityStreamsActor(actorProp)
- // set the ID
- boostIDURI, err := url.Parse(boostWrapperStatus.URI)
+ // `actor` property.
+ actorURI, err := url.Parse(bw.AccountURI)
if err != nil {
- return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostWrapperStatus.URI, err)
+ return nil, fmt.Errorf("error parsing actor uri: %s", err)
}
- idProp := streams.NewJSONLDIdProperty()
- idProp.SetIRI(boostIDURI)
- announce.SetJSONLDId(idProp)
+ ap.AppendActorIRIs(announce, actorURI)
- // set the object
- boostedStatusURI, err := url.Parse(boostWrapperStatus.BoostOf.URI)
+ // `object` property is the target status URI.
+ boostTargetURI, err := url.Parse(bw.BoostOf.URI)
if err != nil {
- return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostWrapperStatus.BoostOf.URI, err)
+ return nil, fmt.Errorf("error parsing target status uri: %s", err)
}
- objectProp := streams.NewActivityStreamsObjectProperty()
- objectProp.AppendIRI(boostedStatusURI)
- announce.SetActivityStreamsObject(objectProp)
+ ap.AppendObjectIRIs(announce, boostTargetURI)
- // set the published time
- publishedProp := streams.NewActivityStreamsPublishedProperty()
- publishedProp.Set(boostWrapperStatus.CreatedAt)
- announce.SetActivityStreamsPublished(publishedProp)
+ // `published` property is the time of the boost.
+ ap.SetPublished(announce, bw.CreatedAt)
- // set the to
- followersURI, err := url.Parse(boostingAccount.FollowersURI)
+ // `to` property.
+ followersURI, err := url.Parse(bw.Account.FollowersURI)
if err != nil {
- return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostingAccount.FollowersURI, err)
+ return nil, fmt.Errorf("error parsing followers URI: %s", err)
}
- toProp := streams.NewActivityStreamsToProperty()
- toProp.AppendIRI(followersURI)
- announce.SetActivityStreamsTo(toProp)
+ ap.AppendTo(announce, followersURI)
- // set the cc
- ccProp := streams.NewActivityStreamsCcProperty()
- boostedAccountURI, err := url.Parse(boostedAccount.URI)
+ // `cc` property.
+ boostedAccountURI, err := url.Parse(bw.BoostOfAccount.URI)
if err != nil {
- return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", boostedAccount.URI, err)
+ return nil, fmt.Errorf("error parsing target account URI: %s", err)
}
- ccProp.AppendIRI(boostedAccountURI)
+ ap.AppendCc(announce, boostedAccountURI)
- // maybe CC it to public depending on the boosted status visibility
- switch boostWrapperStatus.BoostOf.Visibility {
+ // `cc` should include public if
+ // this is a public or unlocked boost.
+ switch bw.BoostOf.Visibility {
case gtsmodel.VisibilityPublic, gtsmodel.VisibilityUnlocked:
- publicURI, err := url.Parse(pub.PublicActivityPubIRI)
- if err != nil {
- return nil, fmt.Errorf("BoostToAS: error parsing uri %s: %s", pub.PublicActivityPubIRI, err)
- }
- ccProp.AppendIRI(publicURI)
+ ap.AppendCc(announce, ap.PublicIRI())
}
- announce.SetActivityStreamsCc(ccProp)
-
- // Parse + set approvedBy.
- if boostWrapperStatus.ApprovedByURI != "" {
- approvedBy, err := url.Parse(boostWrapperStatus.ApprovedByURI)
+ // Parse + set authorization.
+ if bw.ApprovedByURI != "" {
+ approvedBy, err := url.Parse(bw.ApprovedByURI)
if err != nil {
return nil, fmt.Errorf("error parsing approvedBy: %w", err)
}
-
- approvedByProp := streams.NewGoToSocialApprovedByProperty()
- approvedByProp.Set(approvedBy)
- announce.SetGoToSocialApprovedBy(approvedByProp)
+ ap.SetApprovedBy(announce, approvedBy)
}
return announce, nil
@@ -2172,7 +2075,7 @@ func (c *Converter) InteractionReqToASAccept(
) (vocab.ActivityStreamsAccept, error) {
accept := streams.NewActivityStreamsAccept()
- acceptID, err := url.Parse(req.URI)
+ acceptID, err := url.Parse(req.ResponseURI)
if err != nil {
return nil, gtserror.Newf("invalid accept uri: %w", err)
}
@@ -2187,14 +2090,14 @@ func (c *Converter) InteractionReqToASAccept(
return nil, gtserror.Newf("invalid object uri: %w", err)
}
- if req.Status == nil {
- req.Status, err = c.state.DB.GetStatusByID(ctx, req.StatusID)
+ if req.TargetStatus == nil {
+ req.TargetStatus, err = c.state.DB.GetStatusByID(ctx, req.TargetStatusID)
if err != nil {
return nil, gtserror.Newf("db error getting interaction req target status: %w", err)
}
}
- targetIRI, err := url.Parse(req.Status.URI)
+ targetIRI, err := url.Parse(req.TargetStatus.URI)
if err != nil {
return nil, gtserror.Newf("invalid interaction req target status uri: %w", err)
}
@@ -2204,35 +2107,99 @@ func (c *Converter) InteractionReqToASAccept(
return nil, gtserror.Newf("invalid interacting account uri: %w", err)
}
- // Set id to the URI of
- // interaction request.
+ // Set id.
ap.SetJSONLDId(accept, acceptID)
- // Actor is the account that
- // owns the approval / accept.
+ // Actor is the account
+ // Accepting the interaction.
ap.AppendActorIRIs(accept, actorIRI)
- // Object is the interaction URI.
- ap.AppendObjectIRIs(accept, objectIRI)
+ polite := req.IsPolite()
+ if polite {
+ // If accepting a polite request, put
+ // a barebones version of the *Request
+ // in the `object` property.
+ var (
+ objProp = streams.NewActivityStreamsObjectProperty()
+ ir ap.InteractionRequestable
+ )
+ switch req.InteractionType {
+ case gtsmodel.InteractionLike:
+ v := streams.NewGoToSocialLikeRequest()
+ objProp.AppendGoToSocialLikeRequest(v)
+ ir = v
+ case gtsmodel.InteractionReply:
+ v := streams.NewGoToSocialReplyRequest()
+ objProp.AppendGoToSocialReplyRequest(v)
+ ir = v
+ case gtsmodel.InteractionAnnounce:
+ v := streams.NewGoToSocialAnnounceRequest()
+ objProp.AppendGoToSocialAnnounceRequest(v)
+ ir = v
+ }
+
+ // URI of the interaction request.
+ if err := ap.SetJSONLDIdStr(ir, req.InteractionRequestURI); err != nil {
+ return nil, err
+ }
- // Target is the URI of the
- // status being interacted with.
- ap.AppendTargetIRIs(accept, targetIRI)
+ // URI of the interacting actor.
+ ap.AppendActorIRIs(ir, toIRI)
+
+ // URI of the status.
+ ap.AppendObjectIRIs(ir, targetIRI)
+
+ // URI of the interaction.
+ ap.AppendInstrumentIRIs(ir, objectIRI)
+
+ // Set the thing.
+ accept.SetActivityStreamsObject(objProp)
+
+ // If polite, also include the "result" URI
+ // of the interaction Authorization object.
+ resultIRI, err := url.Parse(req.AuthorizationURI)
+ if err != nil {
+ return nil, gtserror.Newf("invalid authorization uri: %w", err)
+ }
+ ap.AppendResultIRIs(accept, resultIRI)
+
+ } else {
+ // If accepting an impolite request, just set
+ // interaction URI as object and target status
+ // IRI as target. Don't give authorization in
+ // result field, as this will confuse pre v0.20.0
+ // instances who don't understand Auth types yet.
+ //
+ // TODO: remove this path in v0.21.0 and send an
+ // accept of a Request for impolite requests too.
+ ap.AppendObjectIRIs(accept, objectIRI)
+ ap.AppendTargetIRIs(accept, targetIRI)
+ }
// Address to the owner
// of interaction URI.
ap.AppendTo(accept, toIRI)
- // Whether or not we cc this Accept to
- // followers and public depends on the
- // type of interaction it Accepts.
+ // If the request is polite, send
+ // the Accept only to the requester,
+ // no need to cc anything.
+ if polite {
+ return accept, nil
+ }
+
+ // If the request was impolite, it's
+ // helpful for federation to distribute
+ // the Accept to our followers as well,
+ // depending on the type of interaction.
var cc bool
switch req.InteractionType {
case gtsmodel.InteractionLike:
// Accept of Like doesn't get cc'd
- // because it's not that important.
+ // because it's not that important,
+ // as Likes are usually only ever
+ // sent to the post author.
case gtsmodel.InteractionReply:
// Accept of reply gets cc'd.
@@ -2244,10 +2211,7 @@ func (c *Converter) InteractionReqToASAccept(
}
if cc {
- publicIRI, err := url.Parse(pub.PublicActivityPubIRI)
- if err != nil {
- return nil, gtserror.Newf("invalid public uri: %w", err)
- }
+ publicIRI := ap.PublicIRI()
followersIRI, err := url.Parse(req.TargetAccount.FollowersURI)
if err != nil {
@@ -2268,7 +2232,7 @@ func (c *Converter) InteractionReqToASReject(
) (vocab.ActivityStreamsReject, error) {
reject := streams.NewActivityStreamsReject()
- rejectID, err := url.Parse(req.URI)
+ rejectID, err := url.Parse(req.ResponseURI)
if err != nil {
return nil, gtserror.Newf("invalid reject uri: %w", err)
}
@@ -2283,14 +2247,14 @@ func (c *Converter) InteractionReqToASReject(
return nil, gtserror.Newf("invalid object uri: %w", err)
}
- if req.Status == nil {
- req.Status, err = c.state.DB.GetStatusByID(ctx, req.StatusID)
+ if req.TargetStatus == nil {
+ req.TargetStatus, err = c.state.DB.GetStatusByID(ctx, req.TargetStatusID)
if err != nil {
return nil, gtserror.Newf("db error getting interaction req target status: %w", err)
}
}
- targetIRI, err := url.Parse(req.Status.URI)
+ targetIRI, err := url.Parse(req.TargetStatus.URI)
if err != nil {
return nil, gtserror.Newf("invalid interaction req target status uri: %w", err)
}
@@ -2300,28 +2264,80 @@ func (c *Converter) InteractionReqToASReject(
return nil, gtserror.Newf("invalid interacting account uri: %w", err)
}
- // Set id to the URI of
- // interaction request.
+ // Set id.
ap.SetJSONLDId(reject, rejectID)
- // Actor is the account that
- // owns the approval / reject.
+ // Actor is the account
+ // Rejecting the interaction.
ap.AppendActorIRIs(reject, actorIRI)
- // Object is the interaction URI.
- ap.AppendObjectIRIs(reject, objectIRI)
+ polite := req.IsPolite()
+ if polite {
+ // If rejecting a polite request, put
+ // a barebones version of the *Request
+ // in the `object` property.
+ var (
+ objProp = streams.NewActivityStreamsObjectProperty()
+ ir ap.InteractionRequestable
+ )
+ switch req.InteractionType {
+ case gtsmodel.InteractionLike:
+ v := streams.NewGoToSocialLikeRequest()
+ objProp.AppendGoToSocialLikeRequest(v)
+ ir = v
+ case gtsmodel.InteractionReply:
+ v := streams.NewGoToSocialReplyRequest()
+ objProp.AppendGoToSocialReplyRequest(v)
+ ir = v
+ case gtsmodel.InteractionAnnounce:
+ v := streams.NewGoToSocialAnnounceRequest()
+ objProp.AppendGoToSocialAnnounceRequest(v)
+ ir = v
+ }
+
+ // URI of the interaction request.
+ if err := ap.SetJSONLDIdStr(ir, req.InteractionRequestURI); err != nil {
+ return nil, err
+ }
+
+ // URI of the interacting actor.
+ ap.AppendActorIRIs(ir, toIRI)
+
+ // URI of the status.
+ ap.AppendObjectIRIs(ir, targetIRI)
+
+ // URI of the interaction.
+ ap.AppendInstrumentIRIs(ir, objectIRI)
- // Target is the URI of the
- // status being interacted with.
- ap.AppendTargetIRIs(reject, targetIRI)
+ // Set the thing.
+ reject.SetActivityStreamsObject(objProp)
+
+ } else {
+ // If rejecting an impolite request, just set
+ // interaction URI as object and target status
+ // IRI as target.
+ //
+ // TODO: remove this path in v0.21.0 and send a
+ // Reject of a Request for impolite requests too.
+ ap.AppendObjectIRIs(reject, objectIRI)
+ ap.AppendTargetIRIs(reject, targetIRI)
+ }
// Address to the owner
// of interaction URI.
ap.AppendTo(reject, toIRI)
- // Whether or not we cc this Reject to
- // followers and public depends on the
- // type of interaction it Rejects.
+ // If the request is polite, send
+ // the Reject only to the requester,
+ // no need to cc anything.
+ if polite {
+ return reject, nil
+ }
+
+ // If the request was impolite, it's
+ // helpful for federation to distribute
+ // the Reject to our followers as well,
+ // depending on the type of interaction.
var cc bool
switch req.InteractionType {
@@ -2340,10 +2356,7 @@ func (c *Converter) InteractionReqToASReject(
}
if cc {
- publicIRI, err := url.Parse(pub.PublicActivityPubIRI)
- if err != nil {
- return nil, gtserror.Newf("invalid public uri: %w", err)
- }
+ publicIRI := ap.PublicIRI()
followersIRI, err := url.Parse(req.TargetAccount.FollowersURI)
if err != nil {
@@ -2355,3 +2368,136 @@ func (c *Converter) InteractionReqToASReject(
return reject, nil
}
+
+// InteractionReqToASAuthorization converts an approved *gtsmodel.InteractionRequest
+// to a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization object.
+//
+// End result will look something like this:
+//
+// {
+// "@context": [
+// "https://gotosocial.org/ns",
+// "https://www.w3.org/ns/activitystreams"
+// ],
+// "attributedTo": "http://localhost:8080/users/the_mighty_zork",
+// "id": "http://localhost:8080/users/the_mighty_zork/authorizations/01J1AKMZ8JE5NW0ZSFTRC1JJNE",
+// "interactingObject": "https://fossbros-anonymous.io/users/foss_satan/likes/01J1AKRRHQ6MDDQHV0TP716T2K",
+// "interactionTarget": "http://localhost:8080/users/the_mighty_zork/statuses/01JJYCVKCXB9JTQD1XW2KB8MT3",
+// "type": "LikeAuthorization"
+// }
+func (c *Converter) InteractionReqToASAuthorization(
+ ctx context.Context,
+ req *gtsmodel.InteractionRequest,
+) (ap.Authorizationable, error) {
+ if !req.IsAccepted() {
+ const text = "cannot convert not-accepted interaction request to Authorization type"
+ return nil, gtserror.New(text)
+ }
+
+ if err := c.state.DB.PopulateInteractionRequest(ctx, req); err != nil {
+ return nil, gtserror.Newf("error populating interaction request: %w", err)
+ }
+
+ var auth ap.Authorizationable
+ switch req.InteractionType {
+ case gtsmodel.InteractionLike:
+ auth = streams.NewGoToSocialLikeAuthorization()
+ case gtsmodel.InteractionReply:
+ auth = streams.NewGoToSocialReplyAuthorization()
+ case gtsmodel.InteractionAnnounce:
+ auth = streams.NewGoToSocialAnnounceAuthorization()
+ }
+
+ // Set the ID.
+ if err := ap.SetJSONLDIdStr(auth, req.AuthorizationURI); err != nil {
+ return nil, err
+ }
+
+ // Set attributed to actor URI.
+ attributedToURI, err := url.Parse(req.TargetAccount.URI)
+ if err != nil {
+ return nil, gtserror.Newf("invalid target account URI: %w", err)
+ }
+ ap.AppendAttributedTo(auth, attributedToURI)
+
+ // Set interaction URI (eg., uri of the fave, reply, or announce).
+ intObjURI, err := url.Parse(req.InteractionURI)
+ if err != nil {
+ return nil, gtserror.Newf("invalid interaction URI: %w", err)
+ }
+ ap.AppendInteractingObject(auth, intObjURI)
+
+ // Set interaction target URI (ie., uri of the status interacted with).
+ intTargetURI, err := url.Parse(req.TargetStatus.URI)
+ if err != nil {
+ return nil, gtserror.Newf("invalid interaction target URI: %w", err)
+ }
+ ap.AppendInteractionTarget(auth, intTargetURI)
+
+ return auth, nil
+}
+
+// appendASInteractionAuthorization is a utility function
+// that sets `approvedBy`, and `likeAuthorization`,
+// `replyAuthorization`, or `announceAuthorization`.
+func (c *Converter) appendASInteractionAuthorization(
+ ctx context.Context,
+ approvedByURIStr string,
+ t vocab.Type,
+) error {
+ // ApprovedByURI is the URI of an
+ // Authorization for this interaction.
+ approvedByURI, err := url.Parse(approvedByURIStr)
+ if err != nil {
+ return gtserror.Newf("error parsing approvedByURIStr: %w", err)
+ }
+
+ // Fetch relevant approved interaction
+ // request for this approvedByURIStr.
+ intReq, err := c.state.DB.GetInteractionRequestByAuthorizationURI(
+ gtscontext.SetBarebones(ctx),
+ approvedByURIStr,
+ )
+ if err != nil {
+ return gtserror.Newf("db error checking for int req: %w", err)
+ }
+
+ // Make sure it's actually accepted.
+ if !intReq.IsAccepted() {
+ return gtserror.Newf(
+ "approvedByURIStr %s corresponded to not-accepted interaction request %s",
+ approvedByURIStr, intReq.ID,
+ )
+ }
+
+ // Deprecated: Set `approvedBy`
+ // property to URI of the Accept.
+ //
+ // Todo: Remove this in v0.21.0.
+ if wap, ok := t.(ap.WithApprovedBy); ok {
+ responseURI, err := url.Parse(intReq.ResponseURI)
+ if err != nil {
+ return gtserror.Newf("error parsing responseURI: %w", err)
+ }
+ ap.SetApprovedBy(wap, responseURI)
+ }
+
+ // Set the appropriate authorization
+ // property depending on type.
+ switch intReq.InteractionType {
+ case gtsmodel.InteractionLike:
+ if wla, ok := t.(ap.WithLikeAuthorization); ok {
+ ap.SetLikeAuthorization(wla, approvedByURI)
+ }
+ case gtsmodel.InteractionReply:
+ if wra, ok := t.(ap.WithReplyAuthorization); ok {
+ ap.SetReplyAuthorization(wra, approvedByURI)
+ }
+ case gtsmodel.InteractionAnnounce:
+ if waa, ok := t.(ap.WithAnnounceAuthorization); ok {
+ ap.SetAnnounceAuthorization(waa, approvedByURI)
+ }
+ }
+
+ return nil
+}
diff --git a/internal/typeutils/internaltoas_test.go b/internal/typeutils/internaltoas_test.go
index 5c3e52ddb..970e16cfa 100644
--- a/internal/typeutils/internaltoas_test.go
+++ b/internal/typeutils/internaltoas_test.go
@@ -25,6 +25,8 @@ import (
"code.superseriousbusiness.org/gotosocial/internal/ap"
"code.superseriousbusiness.org/gotosocial/internal/db"
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
+ "code.superseriousbusiness.org/gotosocial/internal/uris"
+ "code.superseriousbusiness.org/gotosocial/internal/util"
"code.superseriousbusiness.org/gotosocial/testrig"
"github.com/stretchr/testify/suite"
)
@@ -947,6 +949,264 @@ func (suite *InternalToASTestSuite) TestStatusToASWithMentions() {
}`, string(bytes))
}
+func (suite *InternalToASTestSuite) TestStatusToASPoliteApproved() {
+ ctx := suite.T().Context()
+
+ // Take a status from admin that replies to turtle.
+ testStatus := new(gtsmodel.Status)
+ *testStatus = *suite.testStatuses["admin_account_status_5"]
+
+ // Take corresponding interaction request.
+ intReq := new(gtsmodel.InteractionRequest)
+ *intReq = *suite.testInteractionRequests["admin_account_reply_turtle"]
+
+ // Mark the status as approved by updating the
+ // status + corresponding interaction request.
+ username := suite.testAccounts["local_account_2"].Username
+ intReq.ResponseURI = uris.GenerateURIForAccept(
+ username,
+ intReq.ID,
+ )
+ intReq.AuthorizationURI = uris.GenerateURIForAuthorization(
+ username,
+ intReq.ID,
+ )
+ intReq.AcceptedAt = testrig.TimeMustParse("2024-11-01T11:00:00+02:00")
+
+ // Mark it as polite too.
+ intReq.Polite = util.Ptr(true)
+
+ if err := suite.state.DB.UpdateInteractionRequest(
+ ctx,
+ intReq,
+ "response_uri",
+ "authorization_uri",
+ "accepted_at",
+ "polite",
+ ); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ testStatus.ApprovedByURI = intReq.AuthorizationURI
+ if err := suite.state.DB.UpdateStatus(
+ ctx,
+ testStatus,
+ "approved_by_uri",
+ ); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ asStatus, err := suite.typeconverter.StatusToAS(ctx, testStatus)
+ suite.NoError(err)
+
+ ser, err := ap.Serialize(asStatus)
+ suite.NoError(err)
+
+ bytes, err := json.MarshalIndent(ser, "", " ")
+ suite.NoError(err)
+
+ suite.Equal(`{
+ "@context": [
+ "https://gotosocial.org/ns",
+ "https://www.w3.org/ns/activitystreams",
+ {
+ "sensitive": "as:sensitive"
+ }
+ ],
+ "approvedBy": "http://localhost:8080/users/1happyturtle/accepts/01J5QVXCCEATJYSXM9H6MZT4JR",
+ "attachment": [],
+ "attributedTo": "http://localhost:8080/users/admin",
+ "cc": [
+ "http://localhost:8080/users/admin/followers",
+ "http://localhost:8080/users/1happyturtle"
+ ],
+ "content": "\u003cp\u003eHi \u003cspan class=\"h-card\"\u003e\u003ca href=\"http://localhost:8080/@1happyturtle\" class=\"u-url mention\" rel=\"nofollow noreferrer noopener\" target=\"_blank\"\u003e@\u003cspan\u003e1happyturtle\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e, can I reply?\u003c/p\u003e",
+ "id": "http://localhost:8080/users/admin/statuses/01J5QVB9VC76NPPRQ207GG4DRZ",
+ "inReplyTo": "http://localhost:8080/users/1happyturtle/statuses/01F8MHC8VWDRBQR0N1BATDDEM5",
+ "interactionPolicy": {
+ "canAnnounce": {
+ "always": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "approvalRequired": [],
+ "automaticApproval": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "manualApproval": []
+ },
+ "canLike": {
+ "always": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "approvalRequired": [],
+ "automaticApproval": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "manualApproval": []
+ },
+ "canReply": {
+ "always": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "approvalRequired": [],
+ "automaticApproval": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "manualApproval": []
+ }
+ },
+ "published": "2024-02-20T12:41:37+02:00",
+ "replies": {
+ "first": {
+ "id": "http://localhost:8080/users/admin/statuses/01J5QVB9VC76NPPRQ207GG4DRZ/replies?page=true",
+ "next": "http://localhost:8080/users/admin/statuses/01J5QVB9VC76NPPRQ207GG4DRZ/replies?page=true\u0026only_other_accounts=false",
+ "partOf": "http://localhost:8080/users/admin/statuses/01J5QVB9VC76NPPRQ207GG4DRZ/replies",
+ "type": "CollectionPage"
+ },
+ "id": "http://localhost:8080/users/admin/statuses/01J5QVB9VC76NPPRQ207GG4DRZ/replies",
+ "type": "Collection"
+ },
+ "replyAuthorization": "http://localhost:8080/users/1happyturtle/authorizations/01J5QVXCCEATJYSXM9H6MZT4JR",
+ "sensitive": false,
+ "summary": "",
+ "tag": {
+ "href": "http://localhost:8080/users/1happyturtle",
+ "name": "@1happyturtle@localhost:8080",
+ "type": "Mention"
+ },
+ "to": "https://www.w3.org/ns/activitystreams#Public",
+ "type": "Note",
+ "url": "http://localhost:8080/@admin/statuses/01J5QVB9VC76NPPRQ207GG4DRZ"
+}`, string(bytes))
+}
+
+func (suite *InternalToASTestSuite) TestStatusToASPImpoliteApproved() {
+ ctx := suite.T().Context()
+
+ // Take a status from admin that replies to turtle.
+ testStatus := new(gtsmodel.Status)
+ *testStatus = *suite.testStatuses["admin_account_status_5"]
+
+ // Take corresponding interaction request.
+ intReq := new(gtsmodel.InteractionRequest)
+ *intReq = *suite.testInteractionRequests["admin_account_reply_turtle"]
+
+ // Mark the status as approved by updating the
+ // status + corresponding interaction request.
+ username := suite.testAccounts["local_account_2"].Username
+ intReq.ResponseURI = uris.GenerateURIForAccept(
+ username,
+ intReq.ID,
+ )
+ intReq.AuthorizationURI = uris.GenerateURIForAuthorization(
+ username,
+ intReq.ID,
+ )
+ intReq.AcceptedAt = testrig.TimeMustParse("2024-11-01T11:00:00+02:00")
+
+ if err := suite.state.DB.UpdateInteractionRequest(
+ ctx,
+ intReq,
+ "response_uri",
+ "authorization_uri",
+ "accepted_at",
+ ); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ testStatus.ApprovedByURI = intReq.AuthorizationURI
+ if err := suite.state.DB.UpdateStatus(
+ ctx,
+ testStatus,
+ "approved_by_uri",
+ ); err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ asStatus, err := suite.typeconverter.StatusToAS(ctx, testStatus)
+ suite.NoError(err)
+
+ ser, err := ap.Serialize(asStatus)
+ suite.NoError(err)
+
+ bytes, err := json.MarshalIndent(ser, "", " ")
+ suite.NoError(err)
+
+ suite.Equal(`{
+ "@context": [
+ "https://gotosocial.org/ns",
+ "https://www.w3.org/ns/activitystreams",
+ {
+ "sensitive": "as:sensitive"
+ }
+ ],
+ "approvedBy": "http://localhost:8080/users/1happyturtle/accepts/01J5QVXCCEATJYSXM9H6MZT4JR",
+ "attachment": [],
+ "attributedTo": "http://localhost:8080/users/admin",
+ "cc": [
+ "http://localhost:8080/users/admin/followers",
+ "http://localhost:8080/users/1happyturtle"
+ ],
+ "content": "\u003cp\u003eHi \u003cspan class=\"h-card\"\u003e\u003ca href=\"http://localhost:8080/@1happyturtle\" class=\"u-url mention\" rel=\"nofollow noreferrer noopener\" target=\"_blank\"\u003e@\u003cspan\u003e1happyturtle\u003c/span\u003e\u003c/a\u003e\u003c/span\u003e, can I reply?\u003c/p\u003e",
+ "id": "http://localhost:8080/users/admin/statuses/01J5QVB9VC76NPPRQ207GG4DRZ",
+ "inReplyTo": "http://localhost:8080/users/1happyturtle/statuses/01F8MHC8VWDRBQR0N1BATDDEM5",
+ "interactionPolicy": {
+ "canAnnounce": {
+ "always": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "approvalRequired": [],
+ "automaticApproval": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "manualApproval": []
+ },
+ "canLike": {
+ "always": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "approvalRequired": [],
+ "automaticApproval": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "manualApproval": []
+ },
+ "canReply": {
+ "always": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "approvalRequired": [],
+ "automaticApproval": [
+ "https://www.w3.org/ns/activitystreams#Public"
+ ],
+ "manualApproval": []
+ }
+ },
+ "published": "2024-02-20T12:41:37+02:00",
+ "replies": {
+ "first": {
+ "id": "http://localhost:8080/users/admin/statuses/01J5QVB9VC76NPPRQ207GG4DRZ/replies?page=true",
+ "next": "http://localhost:8080/users/admin/statuses/01J5QVB9VC76NPPRQ207GG4DRZ/replies?page=true\u0026only_other_accounts=false",
+ "partOf": "http://localhost:8080/users/admin/statuses/01J5QVB9VC76NPPRQ207GG4DRZ/replies",
+ "type": "CollectionPage"
+ },
+ "id": "http://localhost:8080/users/admin/statuses/01J5QVB9VC76NPPRQ207GG4DRZ/replies",
+ "type": "Collection"
+ },
+ "replyAuthorization": "http://localhost:8080/users/1happyturtle/authorizations/01J5QVXCCEATJYSXM9H6MZT4JR",
+ "sensitive": false,
+ "summary": "",
+ "tag": {
+ "href": "http://localhost:8080/users/1happyturtle",
+ "name": "@1happyturtle@localhost:8080",
+ "type": "Mention"
+ },
+ "to": "https://www.w3.org/ns/activitystreams#Public",
+ "type": "Note",
+ "url": "http://localhost:8080/@admin/statuses/01J5QVB9VC76NPPRQ207GG4DRZ"
+}`, string(bytes))
+}
+
func (suite *InternalToASTestSuite) TestStatusToASDeletePublicReply() {
testStatus := suite.testStatuses["admin_account_status_3"]
ctx := suite.T().Context()
@@ -1119,7 +1379,7 @@ func (suite *InternalToASTestSuite) TestSelfBoostFollowersOnlyToAS() {
boostWrapperStatus.URI = "http://localhost:8080/users/the_mighty_zork/statuses/01G74JJ1KS331G2JXHRMZCE0ER"
boostWrapperStatus.CreatedAt = testrig.TimeMustParse("2022-06-09T13:12:00Z")
- asBoost, err := suite.typeconverter.BoostToAS(ctx, boostWrapperStatus, testAccount, testAccount)
+ asBoost, err := suite.typeconverter.BoostToAS(ctx, boostWrapperStatus)
suite.NoError(err)
ser, err := ap.Serialize(asBoost)
@@ -1320,23 +1580,24 @@ func (suite *InternalToASTestSuite) TestPollVoteToASCreate() {
}`, string(bytes1))
}
-func (suite *InternalToASTestSuite) TestInteractionReqToASAcceptAnnounce() {
+func (suite *InternalToASTestSuite) TestImpoliteInteractionReqToASAcceptAnnounce() {
acceptingAccount := suite.testAccounts["local_account_1"]
interactingAccount := suite.testAccounts["remote_account_1"]
req := &gtsmodel.InteractionRequest{
- ID: "01J1AKMZ8JE5NW0ZSFTRC1JJNE",
- CreatedAt: testrig.TimeMustParse("2022-06-09T13:12:00Z"),
- StatusID: "01JJYCVKCXB9JTQD1XW2KB8MT3",
- Status: &gtsmodel.Status{URI: "http://localhost:8080/users/the_mighty_zork/statuses/01JJYCVKCXB9JTQD1XW2KB8MT3"},
- TargetAccountID: acceptingAccount.ID,
- TargetAccount: acceptingAccount,
- InteractingAccountID: interactingAccount.ID,
- InteractingAccount: interactingAccount,
- InteractionURI: "https://fossbros-anonymous.io/users/foss_satan/statuses/01J1AKRRHQ6MDDQHV0TP716T2K",
- InteractionType: gtsmodel.InteractionAnnounce,
- URI: "http://localhost:8080/users/the_mighty_zork/accepts/01J1AKMZ8JE5NW0ZSFTRC1JJNE",
- AcceptedAt: testrig.TimeMustParse("2022-06-09T13:12:00Z"),
+ ID: "01J1AKMZ8JE5NW0ZSFTRC1JJNE",
+ TargetStatusID: "01JJYCVKCXB9JTQD1XW2KB8MT3",
+ TargetStatus: &gtsmodel.Status{URI: "http://localhost:8080/users/the_mighty_zork/statuses/01JJYCVKCXB9JTQD1XW2KB8MT3"},
+ TargetAccountID: acceptingAccount.ID,
+ TargetAccount: acceptingAccount,
+ InteractingAccountID: interactingAccount.ID,
+ InteractingAccount: interactingAccount,
+ InteractionRequestURI: "https://fossbros-anonymous.io/users/foss_satan/statuses/01J1AKRRHQ6MDDQHV0TP716T2K" + gtsmodel.AnnounceRequestSuffix,
+ InteractionURI: "https://fossbros-anonymous.io/users/foss_satan/statuses/01J1AKRRHQ6MDDQHV0TP716T2K",
+ InteractionType: gtsmodel.InteractionAnnounce,
+ Polite: util.Ptr(false),
+ ResponseURI: "http://localhost:8080/users/the_mighty_zork/accepts/01J1AKMZ8JE5NW0ZSFTRC1JJNE",
+ AcceptedAt: testrig.TimeMustParse("2022-06-09T13:12:00Z"),
}
accept, err := suite.typeconverter.InteractionReqToASAccept(
@@ -1372,23 +1633,24 @@ func (suite *InternalToASTestSuite) TestInteractionReqToASAcceptAnnounce() {
}`, string(b))
}
-func (suite *InternalToASTestSuite) TestInteractionReqToASAcceptLike() {
+func (suite *InternalToASTestSuite) TestImpoliteInteractionReqToASAcceptLike() {
acceptingAccount := suite.testAccounts["local_account_1"]
interactingAccount := suite.testAccounts["remote_account_1"]
req := &gtsmodel.InteractionRequest{
- ID: "01J1AKMZ8JE5NW0ZSFTRC1JJNE",
- CreatedAt: testrig.TimeMustParse("2022-06-09T13:12:00Z"),
- StatusID: "01JJYCVKCXB9JTQD1XW2KB8MT3",
- Status: &gtsmodel.Status{URI: "http://localhost:8080/users/the_mighty_zork/statuses/01JJYCVKCXB9JTQD1XW2KB8MT3"},
- TargetAccountID: acceptingAccount.ID,
- TargetAccount: acceptingAccount,
- InteractingAccountID: interactingAccount.ID,
- InteractingAccount: interactingAccount,
- InteractionURI: "https://fossbros-anonymous.io/users/foss_satan/statuses/01J1AKRRHQ6MDDQHV0TP716T2K",
- InteractionType: gtsmodel.InteractionLike,
- URI: "http://localhost:8080/users/the_mighty_zork/accepts/01J1AKMZ8JE5NW0ZSFTRC1JJNE",
- AcceptedAt: testrig.TimeMustParse("2022-06-09T13:12:00Z"),
+ ID: "01J1AKMZ8JE5NW0ZSFTRC1JJNE",
+ TargetStatusID: "01JJYCVKCXB9JTQD1XW2KB8MT3",
+ TargetStatus: &gtsmodel.Status{URI: "http://localhost:8080/users/the_mighty_zork/statuses/01JJYCVKCXB9JTQD1XW2KB8MT3"},
+ TargetAccountID: acceptingAccount.ID,
+ TargetAccount: acceptingAccount,
+ InteractingAccountID: interactingAccount.ID,
+ InteractingAccount: interactingAccount,
+ InteractionRequestURI: "https://fossbros-anonymous.io/users/foss_satan/likes/01J1AKRRHQ6MDDQHV0TP716T2K" + gtsmodel.LikeRequestSuffix,
+ InteractionURI: "https://fossbros-anonymous.io/users/foss_satan/likes/01J1AKRRHQ6MDDQHV0TP716T2K",
+ InteractionType: gtsmodel.InteractionLike,
+ Polite: util.Ptr(false),
+ ResponseURI: "http://localhost:8080/users/the_mighty_zork/accepts/01J1AKMZ8JE5NW0ZSFTRC1JJNE",
+ AcceptedAt: testrig.TimeMustParse("2022-06-09T13:12:00Z"),
}
accept, err := suite.typeconverter.InteractionReqToASAccept(
@@ -1413,13 +1675,124 @@ func (suite *InternalToASTestSuite) TestInteractionReqToASAcceptLike() {
"@context": "https://www.w3.org/ns/activitystreams",
"actor": "http://localhost:8080/users/the_mighty_zork",
"id": "http://localhost:8080/users/the_mighty_zork/accepts/01J1AKMZ8JE5NW0ZSFTRC1JJNE",
- "object": "https://fossbros-anonymous.io/users/foss_satan/statuses/01J1AKRRHQ6MDDQHV0TP716T2K",
+ "object": "https://fossbros-anonymous.io/users/foss_satan/likes/01J1AKRRHQ6MDDQHV0TP716T2K",
"target": "http://localhost:8080/users/the_mighty_zork/statuses/01JJYCVKCXB9JTQD1XW2KB8MT3",
"to": "http://fossbros-anonymous.io/users/foss_satan",
"type": "Accept"
}`, string(b))
}
+func (suite *InternalToASTestSuite) TestInteractionReqToASAcceptLikePolite() {
+ acceptingAccount := suite.testAccounts["local_account_1"]
+ interactingAccount := suite.testAccounts["remote_account_1"]
+
+ req := &gtsmodel.InteractionRequest{
+ ID: "01J1AKMZ8JE5NW0ZSFTRC1JJNE",
+ TargetStatusID: "01JJYCVKCXB9JTQD1XW2KB8MT3",
+ TargetStatus: &gtsmodel.Status{URI: "http://localhost:8080/users/the_mighty_zork/statuses/01JJYCVKCXB9JTQD1XW2KB8MT3"},
+ TargetAccountID: acceptingAccount.ID,
+ TargetAccount: acceptingAccount,
+ InteractingAccountID: interactingAccount.ID,
+ InteractingAccount: interactingAccount,
+ InteractionRequestURI: "https://fossbros-anonymous.io/users/foss_satan/interaction_requests/01J1AKRRHQ6MDDQHV0TP716T2K",
+ InteractionURI: "https://fossbros-anonymous.io/users/foss_satan/likes/01J1AKRRHQ6MDDQHV0TP716T2K",
+ InteractionType: gtsmodel.InteractionLike,
+ Polite: util.Ptr(true),
+ ResponseURI: "http://localhost:8080/users/the_mighty_zork/accepts/01J1AKMZ8JE5NW0ZSFTRC1JJNE",
+ AuthorizationURI: "http://localhost:8080/users/the_mighty_zork/authorizations/01J1AKMZ8JE5NW0ZSFTRC1JJNE",
+ AcceptedAt: testrig.TimeMustParse("2022-06-09T13:12:00Z"),
+ }
+
+ accept, err := suite.typeconverter.InteractionReqToASAccept(
+ suite.T().Context(),
+ req,
+ )
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ i, err := ap.Serialize(accept)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ b, err := json.MarshalIndent(i, "", " ")
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ suite.Equal(`{
+ "@context": [
+ "https://gotosocial.org/ns",
+ "https://www.w3.org/ns/activitystreams"
+ ],
+ "actor": "http://localhost:8080/users/the_mighty_zork",
+ "id": "http://localhost:8080/users/the_mighty_zork/accepts/01J1AKMZ8JE5NW0ZSFTRC1JJNE",
+ "object": {
+ "actor": "http://fossbros-anonymous.io/users/foss_satan",
+ "id": "https://fossbros-anonymous.io/users/foss_satan/interaction_requests/01J1AKRRHQ6MDDQHV0TP716T2K",
+ "instrument": "https://fossbros-anonymous.io/users/foss_satan/likes/01J1AKRRHQ6MDDQHV0TP716T2K",
+ "object": "http://localhost:8080/users/the_mighty_zork/statuses/01JJYCVKCXB9JTQD1XW2KB8MT3",
+ "type": "LikeRequest"
+ },
+ "result": "http://localhost:8080/users/the_mighty_zork/authorizations/01J1AKMZ8JE5NW0ZSFTRC1JJNE",
+ "to": "http://fossbros-anonymous.io/users/foss_satan",
+ "type": "Accept"
+}`, string(b))
+}
+
+func (suite *InternalToASTestSuite) TestPoliteInteractionReqToASAuthorization() {
+ acceptingAccount := suite.testAccounts["local_account_1"]
+ interactingAccount := suite.testAccounts["remote_account_1"]
+
+ req := &gtsmodel.InteractionRequest{
+ ID: "01J1AKMZ8JE5NW0ZSFTRC1JJNE",
+ TargetStatusID: "01JJYCVKCXB9JTQD1XW2KB8MT3",
+ TargetStatus: &gtsmodel.Status{URI: "http://localhost:8080/users/the_mighty_zork/statuses/01JJYCVKCXB9JTQD1XW2KB8MT3"},
+ TargetAccountID: acceptingAccount.ID,
+ TargetAccount: acceptingAccount,
+ InteractingAccountID: interactingAccount.ID,
+ InteractingAccount: interactingAccount,
+ InteractionURI: "https://fossbros-anonymous.io/users/foss_satan/likes/01J1AKRRHQ6MDDQHV0TP716T2K",
+ InteractionType: gtsmodel.InteractionLike,
+ Polite: util.Ptr(true),
+ InteractionRequestURI: "https://fossbros-anonymous.io/users/foss_satan/interaction_requests/01J1AKRRHQ6MDDQHV0TP716T2K",
+ ResponseURI: "http://localhost:8080/users/the_mighty_zork/accepts/01J1AKMZ8JE5NW0ZSFTRC1JJNE",
+ AuthorizationURI: "http://localhost:8080/users/the_mighty_zork/authorizations/01J1AKMZ8JE5NW0ZSFTRC1JJNE",
+ AcceptedAt: testrig.TimeMustParse("2022-06-09T13:12:00Z"),
+ }
+
+ auth, err := suite.typeconverter.InteractionReqToASAuthorization(
+ suite.T().Context(),
+ req,
+ )
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ i, err := ap.Serialize(auth)
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ b, err := json.MarshalIndent(i, "", " ")
+ if err != nil {
+ suite.FailNow(err.Error())
+ }
+
+ suite.Equal(`{
+ "@context": [
+ "https://gotosocial.org/ns",
+ "https://www.w3.org/ns/activitystreams"
+ ],
+ "attributedTo": "http://localhost:8080/users/the_mighty_zork",
+ "id": "http://localhost:8080/users/the_mighty_zork/authorizations/01J1AKMZ8JE5NW0ZSFTRC1JJNE",
+ "interactingObject": "https://fossbros-anonymous.io/users/foss_satan/likes/01J1AKRRHQ6MDDQHV0TP716T2K",
+ "interactionTarget": "http://localhost:8080/users/the_mighty_zork/statuses/01JJYCVKCXB9JTQD1XW2KB8MT3",
+ "type": "LikeAuthorization"
+}`, string(b))
+}
+
func TestInternalToASTestSuite(t *testing.T) {
suite.Run(t, new(InternalToASTestSuite))
}
diff --git a/internal/typeutils/internaltofrontend.go b/internal/typeutils/internaltofrontend.go
index b2a4652d3..961e99206 100644
--- a/internal/typeutils/internaltofrontend.go
+++ b/internal/typeutils/internaltofrontend.go
@@ -2883,7 +2883,7 @@ func (c *Converter) InteractionReqToAPIInteractionReq(
interactedStatus, err := c.StatusToAPIStatus(
ctx,
- req.Status,
+ req.TargetStatus,
requestingAcct,
)
if err != nil {
@@ -2921,16 +2921,21 @@ func (c *Converter) InteractionReqToAPIInteractionReq(
rejectedAt = util.FormatISO8601(req.RejectedAt)
}
+ createdAt, err := id.TimeFromULID(req.ID)
+ if err != nil {
+ err := gtserror.Newf("error converting id to time: %w", err)
+ return nil, err
+ }
+
return &apimodel.InteractionRequest{
ID: req.ID,
Type: req.InteractionType.String(),
- CreatedAt: util.FormatISO8601(req.CreatedAt),
+ CreatedAt: util.FormatISO8601(createdAt),
Account: interactingAcct,
Status: interactedStatus,
Reply: reply,
AcceptedAt: acceptedAt,
RejectedAt: rejectedAt,
- URI: req.URI,
}, nil
}
diff --git a/internal/typeutils/internaltofrontend_test.go b/internal/typeutils/internaltofrontend_test.go
index 47ad284e6..5d066f410 100644
--- a/internal/typeutils/internaltofrontend_test.go
+++ b/internal/typeutils/internaltofrontend_test.go
@@ -3054,7 +3054,7 @@ func (suite *InternalToFrontendTestSuite) TestIntReqToAPI() {
suite.Equal(`{
"id": "01J5QVXCCEATJYSXM9H6MZT4JR",
"type": "reply",
- "created_at": "2024-02-20T10:41:37.000Z",
+ "created_at": "2024-08-20T12:24:13.966Z",
"account": {
"id": "01F8MH17FWEB39HZJ76B6VXSKF",
"username": "admin",
diff --git a/internal/typeutils/wrap.go b/internal/typeutils/wrap.go
index 59c7e2264..ae83ea4c7 100644
--- a/internal/typeutils/wrap.go
+++ b/internal/typeutils/wrap.go
@@ -53,7 +53,7 @@ func (c *Converter) WrapAccountableInUpdate(accountable ap.Accountable) (vocab.A
update.SetActivityStreamsObject(objectProp)
// to should be public.
- ap.AppendTo(update, ap.PublicURI())
+ ap.AppendTo(update, ap.PublicIRI())
// bcc should be followers.
ap.AppendBcc(update, ap.GetFollowers(accountable))