summaryrefslogtreecommitdiff
path: root/internal/processing/polls/expiry.go
diff options
context:
space:
mode:
authorLibravatar kim <89579420+NyaaaWhatsUpDoc@users.noreply.github.com>2023-11-08 14:32:17 +0000
committerLibravatar GitHub <noreply@github.com>2023-11-08 14:32:17 +0000
commite9e5dc5a40926e5320cb131b035c46b1e1b0bd59 (patch)
tree52edc9fa5742f28e1e5223f51cda628ec1c35a24 /internal/processing/polls/expiry.go
parent[chore]: Bump github.com/spf13/cobra from 1.7.0 to 1.8.0 (#2338) (diff)
downloadgotosocial-e9e5dc5a40926e5320cb131b035c46b1e1b0bd59.tar.xz
[feature] add support for polls + receiving federated status edits (#2330)
Diffstat (limited to 'internal/processing/polls/expiry.go')
-rw-r--r--internal/processing/polls/expiry.go126
1 files changed, 126 insertions, 0 deletions
diff --git a/internal/processing/polls/expiry.go b/internal/processing/polls/expiry.go
new file mode 100644
index 000000000..59d0f17fe
--- /dev/null
+++ b/internal/processing/polls/expiry.go
@@ -0,0 +1,126 @@
+// GoToSocial
+// Copyright (C) GoToSocial Authors admin@gotosocial.org
+// SPDX-License-Identifier: AGPL-3.0-or-later
+//
+// This program is free software: you can redistribute it and/or modify
+// it under the terms of the GNU Affero General Public License as published by
+// the Free Software Foundation, either version 3 of the License, or
+// (at your option) any later version.
+//
+// This program is distributed in the hope that it will be useful,
+// but WITHOUT ANY WARRANTY; without even the implied warranty of
+// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+// GNU Affero General Public License for more details.
+//
+// You should have received a copy of the GNU Affero General Public License
+// along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+package polls
+
+import (
+ "context"
+ "time"
+
+ "github.com/superseriousbusiness/gotosocial/internal/ap"
+ "github.com/superseriousbusiness/gotosocial/internal/gtscontext"
+ "github.com/superseriousbusiness/gotosocial/internal/gtserror"
+ "github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
+ "github.com/superseriousbusiness/gotosocial/internal/log"
+ "github.com/superseriousbusiness/gotosocial/internal/messages"
+)
+
+func (p *Processor) ScheduleAll(ctx context.Context) error {
+ // Fetch all open polls from the database (barebones models are enough).
+ polls, err := p.state.DB.GetOpenPolls(gtscontext.SetBarebones(ctx))
+ if err != nil {
+ return gtserror.Newf("error getting open polls from db: %w", err)
+ }
+
+ var errs gtserror.MultiError
+
+ for _, poll := range polls {
+ // Schedule each of the polls and catch any errors.
+ if err := p.ScheduleExpiry(ctx, poll); err != nil {
+ errs.Append(err)
+ }
+ }
+
+ return errs.Combine()
+}
+
+func (p *Processor) ScheduleExpiry(ctx context.Context, poll *gtsmodel.Poll) error {
+ // Ensure has a valid expiry.
+ if !poll.ClosedAt.IsZero() {
+ return gtserror.Newf("poll %s already expired", poll.ID)
+ }
+
+ // Add the given poll to the scheduler.
+ ok := p.state.Workers.Scheduler.AddOnce(
+ poll.ID,
+ poll.ExpiresAt,
+ p.onExpiry(poll.ID),
+ )
+
+ if !ok {
+ // Failed to add the poll to the scheduler, either it was
+ // starting / stopping or there already exists a task for poll.
+ return gtserror.Newf("failed adding poll %s to scheduler", poll.ID)
+ }
+
+ atStr := poll.ExpiresAt.Local().Format("Jan _2 2006 15:04:05")
+ log.Infof(ctx, "scheduled poll expiry for %s at '%s'", poll.ID, atStr)
+ return nil
+}
+
+// onExpiry returns a callback function to be used by the scheduler when the given poll expires.
+func (p *Processor) onExpiry(pollID string) func(context.Context, time.Time) {
+ return func(ctx context.Context, now time.Time) {
+ // Get the latest version of poll from database.
+ poll, err := p.state.DB.GetPollByID(ctx, pollID)
+ if err != nil {
+ log.Errorf(ctx, "error getting poll %s from db: %v", pollID, err)
+ return
+ }
+
+ if !poll.ClosedAt.IsZero() {
+ // Expiry handler has already been run for this poll.
+ log.Errorf(ctx, "poll %s already closed", pollID)
+ return
+ }
+
+ // Extract status and
+ // set its Poll field.
+ status := poll.Status
+ status.Poll = poll
+
+ // Ensure the status is fully populated (we need the account)
+ if err := p.state.DB.PopulateStatus(ctx, status); err != nil {
+ log.Errorf(ctx, "error populating poll %s status: %v", pollID, err)
+
+ if status.Account == nil {
+ // cannot continue without
+ // status account author.
+ return
+ }
+ }
+
+ // Set "closed" time.
+ poll.ClosedAt = now
+ poll.Closing = true
+
+ // Update the Poll to mark it as closed in the database.
+ if err := p.state.DB.UpdatePoll(ctx, poll, "closed_at"); err != nil {
+ log.Errorf(ctx, "error updating poll %s in db: %v", pollID, err)
+ return
+ }
+
+ // Enqueue a status update operation to the client API worker,
+ // this will asynchronously send an update with the Poll close time.
+ p.state.Workers.EnqueueClientAPI(ctx, messages.FromClientAPI{
+ APActivityType: ap.ActivityUpdate,
+ APObjectType: ap.ObjectNote,
+ GTSModel: status,
+ OriginAccount: status.Account,
+ })
+ }
+}