diff --git a/internal/db/bundb/poll.go b/internal/db/bundb/poll.go index b9384774b..e8c3e7e54 100644 --- a/internal/db/bundb/poll.go +++ b/internal/db/bundb/poll.go @@ -88,12 +88,15 @@ func (p *pollDB) getPoll(ctx context.Context, lookup string, dbQuery func(*gtsmo func (p *pollDB) GetOpenPolls(ctx context.Context) ([]*gtsmodel.Poll, error) { var pollIDs []string - // Select all polls with unset `closed_at` time. + // Select all polls with: + // - UNSET `closed_at` + // - SET `expires_at` if err := p.db.NewSelect(). Table("polls"). Column("polls.id"). Join("JOIN ? ON ? = ?", bun.Ident("statuses"), bun.Ident("polls.id"), bun.Ident("statuses.poll_id")). Where("? = true", bun.Ident("statuses.local")). + Where("? IS NOT NULL", bun.Ident("polls.expires_at")). Where("? IS NULL", bun.Ident("polls.closed_at")). Scan(ctx, &pollIDs); err != nil { return nil, err diff --git a/internal/processing/status/create.go b/internal/processing/status/create.go index ef8f8aa56..340cf9ff3 100644 --- a/internal/processing/status/create.go +++ b/internal/processing/status/create.go @@ -85,25 +85,8 @@ func (p *Processor) Create( PendingApproval: util.Ptr(false), } - if form.Poll != nil { - // Update the status AS type to "Question". - status.ActivityStreamsType = ap.ActivityQuestion - - // Create new poll for status from form. - secs := time.Duration(form.Poll.ExpiresIn) - status.Poll = >smodel.Poll{ - ID: id.NewULID(), - Multiple: &form.Poll.Multiple, - HideCounts: &form.Poll.HideTotals, - Options: form.Poll.Options, - StatusID: statusID, - Status: status, - ExpiresAt: now.Add(secs * time.Second), - } - - // Set poll ID on the status. - status.PollID = status.Poll.ID - } + // Process any attached poll. + p.processPoll(status, form.Poll) // Check + attach in-reply-to status. if errWithCode := p.processInReplyTo(ctx, @@ -153,6 +136,14 @@ func (p *Processor) Create( return nil, gtserror.NewErrorInternalError(err) } + if status.Poll != nil && !status.Poll.ExpiresAt.IsZero() { + // Now that the status is inserted, and side effects queued, + // attempt to schedule an expiry handler for the status poll. + if err := p.polls.ScheduleExpiry(ctx, status.Poll); err != nil { + log.Errorf(ctx, "error scheduling poll expiry: %v", err) + } + } + // send it back to the client API worker for async side-effects. p.state.Workers.Client.Queue.Push(&messages.FromClientAPI{ APObjectType: ap.ObjectNote, @@ -161,14 +152,6 @@ func (p *Processor) Create( Origin: requester, }) - if status.Poll != nil { - // Now that the status is inserted, and side effects queued, - // attempt to schedule an expiry handler for the status poll. - if err := p.polls.ScheduleExpiry(ctx, status.Poll); err != nil { - log.Errorf(ctx, "error scheduling poll expiry: %v", err) - } - } - // If the new status replies to a status that // replies to us, use our reply as an implicit // accept of any pending interaction. @@ -189,6 +172,43 @@ func (p *Processor) Create( return p.c.GetAPIStatus(ctx, requester, status) } +func (p *Processor) processPoll(status *gtsmodel.Status, poll *apimodel.PollRequest) { + if poll == nil { + // No poll set. + // Nothing to do. + return + } + + var expiresAt time.Time + + // Now will have been set + // as the status creation. + now := status.CreatedAt + + // Update the status AS type to "Question". + status.ActivityStreamsType = ap.ActivityQuestion + + // Set an expiry time if one given. + if in := poll.ExpiresIn; in > 0 { + expiresIn := time.Duration(in) + expiresAt = now.Add(expiresIn * time.Second) + } + + // Create new poll for status. + status.Poll = >smodel.Poll{ + ID: id.NewULID(), + Multiple: &poll.Multiple, + HideCounts: &poll.HideTotals, + Options: poll.Options, + StatusID: status.ID, + Status: status, + ExpiresAt: expiresAt, + } + + // Set poll ID on the status. + status.PollID = status.Poll.ID +} + func (p *Processor) processInReplyTo(ctx context.Context, requester *gtsmodel.Account, status *gtsmodel.Status, inReplyToID string) gtserror.WithCode { if inReplyToID == "" { // Not a reply. diff --git a/internal/typeutils/internaltoas.go b/internal/typeutils/internaltoas.go index a81e5d2c0..d46ce64e2 100644 --- a/internal/typeutils/internaltoas.go +++ b/internal/typeutils/internaltoas.go @@ -444,7 +444,7 @@ func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (ap.Stat poll := streams.NewActivityStreamsQuestion() // Add required status poll data to AS Question. - if err := c.addPollToAS(ctx, s.Poll, poll); err != nil { + if err := c.addPollToAS(s.Poll, poll); err != nil { return nil, gtserror.Newf("error converting poll: %w", err) } @@ -708,7 +708,7 @@ func (c *Converter) StatusToAS(ctx context.Context, s *gtsmodel.Status) (ap.Stat return status, nil } -func (c *Converter) addPollToAS(ctx context.Context, poll *gtsmodel.Poll, dst ap.Pollable) error { +func (c *Converter) addPollToAS(poll *gtsmodel.Poll, dst ap.Pollable) error { var optionsProp interface { // the minimum interface for appending AS Notes // to an AS type options property of some kind.