diff --git a/internal/processing/workers/federate.go b/internal/processing/workers/federate.go index a0fd6bf69..8c08c42b7 100644 --- a/internal/processing/workers/federate.go +++ b/internal/processing/workers/federate.go @@ -217,18 +217,23 @@ func (f *federate) CreatePollVote(ctx context.Context, poll *gtsmodel.Poll, vote return err } - // Convert vote to AS Create with vote choices as Objects. - create, err := f.converter.PollVoteToASCreate(ctx, vote) + // Convert vote to AS Creates with vote choices as Objects. + creates, err := f.converter.PollVoteToASCreates(ctx, vote) if err != nil { return gtserror.Newf("error converting to notes: %w", err) } - // 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) + var errs gtserror.MultiError + + // Send each create activity. + actor := f.FederatingActor() + for _, create := range creates { + if _, err := actor.Send(ctx, outboxIRI, create); err != nil { + errs.Appendf("error sending Create activity via outbox %s: %w", outboxIRI, err) + } } - return nil + return errs.Combine() } func (f *federate) DeleteStatus(ctx context.Context, status *gtsmodel.Status) error { diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go index ed8bc1d8d..a81e5d2c0 100644 --- a/internal/typeutils/internaltoas.go +++ b/internal/typeutils/internaltoas.go @@ -1701,10 +1701,14 @@ func (c *Converter) ReportToASFlag(ctx context.Context, r *gtsmodel.Report) (voc // 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( +// +// TODO: as soon as other AP server implementations support +// the use of multiple objects in a single create, update this +// to return just the one create event again. +func (c *Converter) PollVoteToASCreates( ctx context.Context, vote *gtsmodel.PollVote, -) (vocab.ActivityStreamsCreate, error) { +) ([]vocab.ActivityStreamsCreate, error) { if len(vote.Choices) == 0 { panic("no vote.Choices") } @@ -1743,22 +1747,25 @@ func (c *Converter) PollVoteToASCreate( return nil, gtserror.Newf("invalid account uri: %w", err) } - // Allocate Create activity and address 'To' poll author. - create := streams.NewActivityStreamsCreate() - ap.AppendTo(create, pollAuthorIRI) + // Parse each choice to a Note and add it to the list of Creates. + creates := make([]vocab.ActivityStreamsCreate, len(vote.Choices)) + for i, choice := range vote.Choices { - // Create ID formatted as: {$voterIRI}/activity#vote/{$statusIRI}. - id := author.URI + "/activity#vote/" + poll.Status.URI - ap.MustSet(ap.SetJSONLDIdStr, ap.WithJSONLDId(create), id) + // Allocate Create activity and address 'To' poll author. + create := streams.NewActivityStreamsCreate() + ap.AppendTo(create, pollAuthorIRI) - // Set Create actor appropriately. - ap.AppendActorIRIs(create, authorIRI) + // Create ID formatted as: {$voterIRI}/activity#vote{$index}/{$statusIRI}. + createID := fmt.Sprintf("%s/activity#vote%d/%s", author.URI, i, poll.Status.URI) + ap.MustSet(ap.SetJSONLDIdStr, ap.WithJSONLDId(create), createID) - // Set publish time for activity. - ap.SetPublished(create, vote.CreatedAt) + // Set Create actor appropriately. + ap.AppendActorIRIs(create, authorIRI) - // Parse each choice to a Note and add it to the Create. - for _, choice := range vote.Choices { + // Set publish time for activity. + ap.SetPublished(create, vote.CreatedAt) + + // Allocate new note to hold the vote. note := streams.NewActivityStreamsNote() // For AP IRI generate from author URI + poll ID + vote choice. @@ -1775,11 +1782,14 @@ func (c *Converter) PollVoteToASCreate( ap.AppendInReplyTo(note, statusIRI) ap.AppendTo(note, pollAuthorIRI) - // Append this note as Create Object. + // Append this note to the Create Object. appendStatusableToActivity(create, note, false) + + // Set create in slice. + creates[i] = create } - return create, nil + return creates, nil } // populateValuesForProp appends the given PolicyValues diff --git a/internal/typeutils/internaltoas_test.go b/internal/typeutils/internaltoas_test.go index a97eee2b8..c847cfc93 100644 --- a/internal/typeutils/internaltoas_test.go +++ b/internal/typeutils/internaltoas_test.go @@ -1104,43 +1104,55 @@ func (suite *InternalToASTestSuite) TestPinnedStatusesToASOneItem() { 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()) - } + creates, err := suite.typeconverter.PollVoteToASCreates(context.Background(), vote) + suite.NoError(err) + suite.Len(creates, 2) - createI, err := ap.Serialize(create) + createI0, err := ap.Serialize(creates[0]) suite.NoError(err) - bytes, err := json.MarshalIndent(createI, "", " ") + createI1, err := ap.Serialize(creates[1]) + suite.NoError(err) + + bytes0, err := json.MarshalIndent(createI0, "", " ") + suite.NoError(err) + + bytes1, err := json.MarshalIndent(createI1, "", " ") 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" - } - ], + "id": "http://localhost:8080/users/the_mighty_zork/activity#vote0/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" + }, "published": "2021-09-11T11:45:37+02:00", "to": "http://fossbros-anonymous.io/users/foss_satan", "type": "Create" -}`, string(bytes)) +}`, string(bytes0)) + + 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#vote1/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/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(bytes1)) } func (suite *InternalToASTestSuite) TestInteractionReqToASAcceptAnnounce() {