[bugfix] Add Actor to outgoing poll vote Create; other fixes (#2384)

This commit is contained in:
tobi 2023-11-27 14:14:28 +01:00 committed by GitHub
commit e4e0a5e3f6
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 108 additions and 51 deletions

View file

@ -194,14 +194,13 @@ func (f *federate) CreatePollVote(ctx context.Context, poll *gtsmodel.Poll, vote
return err
}
// Convert votes to AS PollOptionable implementing type.
notes, err := f.converter.PollVoteToASOptions(ctx, vote)
// Convert vote to AS Create with vote choices as Objects.
create, err := f.converter.PollVoteToASCreate(ctx, vote)
if err != nil {
return gtserror.Newf("error converting to notes: %w", err)
}
// Send a Create activity with PollOptionables via the Actor's outbox.
create := typeutils.WrapPollOptionablesInCreate(notes...)
// Send the Create via the Actor's outbox.
if _, err := f.FederatingActor().Send(ctx, outboxIRI, create); err != nil {
return gtserror.Newf("error sending Create activity via outbox %s: %w", outboxIRI, err)
}

View file

@ -480,6 +480,7 @@ type TypeUtilsTestSuite struct {
testEmojis map[string]*gtsmodel.Emoji
testReports map[string]*gtsmodel.Report
testMentions map[string]*gtsmodel.Mention
testPollVotes map[string]*gtsmodel.PollVote
typeconverter *typeutils.Converter
}
@ -502,6 +503,7 @@ func (suite *TypeUtilsTestSuite) SetupTest() {
suite.testEmojis = testrig.NewTestEmojis()
suite.testReports = testrig.NewTestReports()
suite.testMentions = testrig.NewTestMentions()
suite.testPollVotes = testrig.NewTestPollVotes()
suite.typeconverter = typeutils.NewConverter(&suite.state)
testrig.StandardDBSetup(suite.db, nil)

View file

@ -1659,7 +1659,17 @@ func (c *Converter) ReportToASFlag(ctx context.Context, r *gtsmodel.Report) (voc
return flag, nil
}
func (c *Converter) PollVoteToASOptions(ctx context.Context, vote *gtsmodel.PollVote) ([]ap.PollOptionable, error) {
// PollVoteToASCreate converts a vote on a poll into a Create
// activity, suitable for federation, with each choice in the
// vote appended as a Note to the Create's Object field.
func (c *Converter) PollVoteToASCreate(
ctx context.Context,
vote *gtsmodel.PollVote,
) (vocab.ActivityStreamsCreate, error) {
if len(vote.Choices) == 0 {
panic("no vote.Choices")
}
// Ensure the vote is fully populated (this fetches author).
if err := c.state.DB.PopulatePollVote(ctx, vote); err != nil {
return nil, gtserror.Newf("error populating vote from db: %w", err)
@ -1694,11 +1704,22 @@ func (c *Converter) PollVoteToASOptions(ctx context.Context, vote *gtsmodel.Poll
return nil, gtserror.Newf("invalid account uri: %w", err)
}
// Preallocate the return slice of notes.
notes := make([]ap.PollOptionable, len(vote.Choices))
// Allocate Create activity and address 'To' poll author.
create := streams.NewActivityStreamsCreate()
ap.AppendTo(create, pollAuthorIRI)
for i, choice := range vote.Choices {
// Create new note to represent vote.
// Create ID formatted as: {$voterIRI}/activity#vote/{$statusIRI}.
id := author.URI + "/activity#vote/" + poll.Status.URI
ap.MustSet(ap.SetJSONLDIdStr, ap.WithJSONLDId(create), id)
// Set Create actor appropriately.
ap.AppendActor(create, authorIRI)
// Set publish time for activity.
ap.SetPublished(create, vote.CreatedAt)
// Parse each choice to a Note and add it to the Create.
for _, choice := range vote.Choices {
note := streams.NewActivityStreamsNote()
// For AP IRI generate from author URI + poll ID + vote choice.
@ -1715,9 +1736,9 @@ func (c *Converter) PollVoteToASOptions(ctx context.Context, vote *gtsmodel.Poll
ap.AppendInReplyTo(note, statusIRI)
ap.AppendTo(note, pollAuthorIRI)
// Set note in return slice.
notes[i] = note
// Append this note as Create Object.
appendStatusableToActivity(create, note, false)
}
return notes, nil
return create, nil
}

View file

@ -879,6 +879,48 @@ func (suite *InternalToASTestSuite) TestPinnedStatusesToASOneItem() {
}`, string(bytes))
}
func (suite *InternalToASTestSuite) TestPollVoteToASCreate() {
vote := suite.testPollVotes["remote_account_1_status_2_poll_vote_local_account_1"]
create, err := suite.typeconverter.PollVoteToASCreate(context.Background(), vote)
if err != nil {
suite.FailNow(err.Error())
}
createI, err := ap.Serialize(create)
suite.NoError(err)
bytes, err := json.MarshalIndent(createI, "", " ")
suite.NoError(err)
suite.Equal(`{
"@context": "https://www.w3.org/ns/activitystreams",
"actor": "http://localhost:8080/users/the_mighty_zork",
"id": "http://localhost:8080/users/the_mighty_zork/activity#vote/http://fossbros-anonymous.io/users/foss_satan/statuses/01HEN2QRFA8H3C6QPN7RD4KSR6",
"object": [
{
"attributedTo": "http://localhost:8080/users/the_mighty_zork",
"id": "http://localhost:8080/users/the_mighty_zork#01HEN2R65468ZG657C4ZPHJ4EX/votes/1",
"inReplyTo": "http://fossbros-anonymous.io/users/foss_satan/statuses/01HEN2QRFA8H3C6QPN7RD4KSR6",
"name": "tissues",
"to": "http://fossbros-anonymous.io/users/foss_satan",
"type": "Note"
},
{
"attributedTo": "http://localhost:8080/users/the_mighty_zork",
"id": "http://localhost:8080/users/the_mighty_zork#01HEN2R65468ZG657C4ZPHJ4EX/votes/2",
"inReplyTo": "http://fossbros-anonymous.io/users/foss_satan/statuses/01HEN2QRFA8H3C6QPN7RD4KSR6",
"name": "financial times",
"to": "http://fossbros-anonymous.io/users/foss_satan",
"type": "Note"
}
],
"published": "2021-09-11T11:45:37+02:00",
"to": "http://fossbros-anonymous.io/users/foss_satan",
"type": "Create"
}`, string(bytes))
}
func TestInternalToASTestSuite(t *testing.T) {
suite.Run(t, new(InternalToASTestSuite))
}

View file

@ -19,7 +19,6 @@ package typeutils
import (
"net/url"
"time"
"github.com/superseriousbusiness/activity/pub"
"github.com/superseriousbusiness/activity/streams"
@ -91,43 +90,6 @@ func WrapStatusableInCreate(status ap.Statusable, iriOnly bool) vocab.ActivitySt
return create
}
func WrapPollOptionablesInCreate(options ...ap.PollOptionable) vocab.ActivityStreamsCreate {
if len(options) == 0 {
panic("no options")
}
// Extract attributedTo IRI from any option.
attribTos := ap.GetAttributedTo(options[0])
if len(attribTos) != 1 {
panic("invalid attributedTo count")
}
// Extract target status IRI from any option.
replyTos := ap.GetInReplyTo(options[0])
if len(replyTos) != 1 {
panic("invalid inReplyTo count")
}
// Allocate create activity and copy over 'To' property.
create := streams.NewActivityStreamsCreate()
ap.AppendTo(create, ap.GetTo(options[0])...)
// Activity ID formatted as: {$statusIRI}/activity#vote/{$voterIRI}.
id := replyTos[0].String() + "/activity#vote/" + attribTos[0].String()
ap.MustSet(ap.SetJSONLDIdStr, ap.WithJSONLDId(create), id)
// Set a current publish time for activity.
ap.SetPublished(create, time.Now())
// Append each poll option as object to activity.
for _, option := range options {
status, _ := ap.ToStatusable(option)
appendStatusableToActivity(create, status, false)
}
return create
}
func WrapStatusableInUpdate(status ap.Statusable, iriOnly bool) vocab.ActivityStreamsUpdate {
update := streams.NewActivityStreamsUpdate()
wrapStatusableInActivity(update, status, iriOnly)