mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[bugfix] update fedi api to support multiple separate votes in same multiple choice poll (#3809)
This commit is contained in:
@@ -20,6 +20,7 @@ package federatingdb
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"slices"
|
||||
|
||||
"github.com/superseriousbusiness/activity/streams/vocab"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
||||
@@ -138,6 +139,10 @@ func (f *federatingDB) createPollOptionables(
|
||||
// then re-used in each further iteration.
|
||||
inReplyTo *gtsmodel.Status
|
||||
|
||||
// existing poll vote by requesting user
|
||||
// in receiving user's poll, if it exists.
|
||||
vote *gtsmodel.PollVote
|
||||
|
||||
// the resulting slices of Poll.Option
|
||||
// choice indices passed into the new
|
||||
// created PollVote object.
|
||||
@@ -169,15 +174,17 @@ func (f *federatingDB) createPollOptionables(
|
||||
case inReplyTo.PollID == "":
|
||||
return gtserror.Newf("poll vote in status %s without poll", statusURI)
|
||||
|
||||
// We don't own the poll ...
|
||||
case !*inReplyTo.Local:
|
||||
return gtserror.Newf("poll vote in remote status %s", statusURI)
|
||||
// Ensure poll isn't closed.
|
||||
case inReplyTo.Poll.Closed():
|
||||
return gtserror.Newf("poll vote in closed poll %s", statusURI)
|
||||
|
||||
// Ensure receiver actually owns poll.
|
||||
case inReplyTo.AccountID != receiver.ID:
|
||||
return gtserror.Newf("receiving account %s does not own poll %s", receiver.URI, statusURI)
|
||||
}
|
||||
|
||||
// Check whether user has already vote in this poll.
|
||||
// (we only check this for the first object, as multiple
|
||||
// may be sent in response to a multiple-choice poll).
|
||||
vote, err := f.state.DB.GetPollVoteBy(
|
||||
// Try load any existing vote(s) by user.
|
||||
vote, err = f.state.DB.GetPollVoteBy(
|
||||
gtscontext.SetBarebones(ctx),
|
||||
inReplyTo.PollID,
|
||||
requester.ID,
|
||||
@@ -185,11 +192,6 @@ func (f *federatingDB) createPollOptionables(
|
||||
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
||||
return gtserror.Newf("error getting status %s poll votes from database: %w", statusURI, err)
|
||||
}
|
||||
|
||||
if vote != nil {
|
||||
log.Warnf(ctx, "%s has already voted in poll %s", requester.URI, statusURI)
|
||||
return nil // this is a useful warning for admins to report to us from logs
|
||||
}
|
||||
}
|
||||
|
||||
if statusURI != inReplyTo.URI {
|
||||
@@ -210,21 +212,60 @@ func (f *federatingDB) createPollOptionables(
|
||||
choices = append(choices, choice)
|
||||
}
|
||||
|
||||
// Enqueue message to the fedi API worker with poll vote(s).
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APActivityType: ap.ActivityCreate,
|
||||
APObjectType: ap.ActivityQuestion,
|
||||
GTSModel: >smodel.PollVote{
|
||||
ID: id.NewULID(),
|
||||
Choices: choices,
|
||||
AccountID: requester.ID,
|
||||
Account: requester,
|
||||
PollID: inReplyTo.PollID,
|
||||
Poll: inReplyTo.Poll,
|
||||
},
|
||||
Receiving: receiver,
|
||||
Requesting: requester,
|
||||
})
|
||||
if vote != nil {
|
||||
// Ensure this isn't a multiple vote
|
||||
// by same account in the same poll.
|
||||
if !*inReplyTo.Poll.Multiple {
|
||||
log.Warnf(ctx, "%s has already voted in single-choice poll %s", requester.URI, inReplyTo.URI)
|
||||
return nil // this is a useful warning for admins to report to us from logs
|
||||
}
|
||||
|
||||
// Drop all existing votes from the new slice of choices.
|
||||
choices = slices.DeleteFunc(choices, func(choice int) bool {
|
||||
return slices.Contains(vote.Choices, choice)
|
||||
})
|
||||
|
||||
// Update poll with new choices but *not* voters.
|
||||
inReplyTo.Poll.IncrementVotes(choices, false)
|
||||
|
||||
// Append new vote choices to existing
|
||||
// vote, then sort them by index size.
|
||||
vote.Choices = append(vote.Choices, choices...)
|
||||
slices.Sort(vote.Choices)
|
||||
|
||||
// Populate the poll vote,
|
||||
// later used in fedi worker.
|
||||
vote.Poll = inReplyTo.Poll
|
||||
vote.Account = requester
|
||||
|
||||
// TODO: would it be useful to store an UpdatedAt field
|
||||
// on poll votes? i'm not sure we'd actually use it...
|
||||
|
||||
// Enqueue an update event for poll vote to fedi API worker.
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APActivityType: ap.ActivityUpdate,
|
||||
APObjectType: ap.ActivityQuestion,
|
||||
GTSModel: vote,
|
||||
Receiving: receiver,
|
||||
Requesting: requester,
|
||||
})
|
||||
} else {
|
||||
// Create new poll vote and enqueue create to fedi API worker.
|
||||
f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{
|
||||
APActivityType: ap.ActivityCreate,
|
||||
APObjectType: ap.ActivityQuestion,
|
||||
GTSModel: >smodel.PollVote{
|
||||
ID: id.NewULID(),
|
||||
Choices: choices,
|
||||
AccountID: requester.ID,
|
||||
Account: requester,
|
||||
PollID: inReplyTo.PollID,
|
||||
Poll: inReplyTo.Poll,
|
||||
},
|
||||
Receiving: receiver,
|
||||
Requesting: requester,
|
||||
})
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
Reference in New Issue
Block a user