diff options
Diffstat (limited to 'internal/processing/workers/fromclientapi.go')
-rw-r--r-- | internal/processing/workers/fromclientapi.go | 398 |
1 files changed, 324 insertions, 74 deletions
diff --git a/internal/processing/workers/fromclientapi.go b/internal/processing/workers/fromclientapi.go index 1a37341f8..c723a6001 100644 --- a/internal/processing/workers/fromclientapi.go +++ b/internal/processing/workers/fromclientapi.go @@ -20,18 +20,23 @@ package workers import ( "context" "errors" + "time" "codeberg.org/gruf/go-kv" "codeberg.org/gruf/go-logger/v2/level" "github.com/superseriousbusiness/gotosocial/internal/ap" "github.com/superseriousbusiness/gotosocial/internal/db" + "github.com/superseriousbusiness/gotosocial/internal/gtscontext" "github.com/superseriousbusiness/gotosocial/internal/gtserror" "github.com/superseriousbusiness/gotosocial/internal/gtsmodel" + "github.com/superseriousbusiness/gotosocial/internal/id" "github.com/superseriousbusiness/gotosocial/internal/log" "github.com/superseriousbusiness/gotosocial/internal/messages" "github.com/superseriousbusiness/gotosocial/internal/processing/account" + "github.com/superseriousbusiness/gotosocial/internal/processing/common" "github.com/superseriousbusiness/gotosocial/internal/state" "github.com/superseriousbusiness/gotosocial/internal/typeutils" + "github.com/superseriousbusiness/gotosocial/internal/uris" "github.com/superseriousbusiness/gotosocial/internal/util" ) @@ -44,6 +49,7 @@ type clientAPI struct { surface *Surface federate *federate account *account.Processor + common *common.Processor utils *utils } @@ -160,6 +166,18 @@ func (p *Processor) ProcessFromClientAPI(ctx context.Context, cMsg *messages.Fro // REJECT USER (ie., new user+account sign-up) case ap.ObjectProfile: return p.clientAPI.RejectUser(ctx, cMsg) + + // REJECT NOTE/STATUS (ie., reject a reply) + case ap.ObjectNote: + return p.clientAPI.RejectReply(ctx, cMsg) + + // REJECT LIKE + case ap.ActivityLike: + return p.clientAPI.RejectLike(ctx, cMsg) + + // REJECT BOOST + case ap.ActivityAnnounce: + return p.clientAPI.RejectAnnounce(ctx, cMsg) } // UNDO SOMETHING @@ -261,15 +279,13 @@ func (p *clientAPI) CreateStatus(ctx context.Context, cMsg *messages.FromClientA // and/or notify the account that's being // interacted with (if it's local): they can // approve or deny the interaction later. - - // Notify *local* account of pending reply. - if err := p.surface.notifyPendingReply(ctx, status); err != nil { - log.Errorf(ctx, "error notifying pending reply: %v", err) + if err := p.utils.requestReply(ctx, status); err != nil { + return gtserror.Newf("error pending reply: %w", err) } // Send Create to *remote* account inbox ONLY. if err := p.federate.CreateStatus(ctx, status); err != nil { - log.Errorf(ctx, "error federating pending reply: %v", err) + return gtserror.Newf("error federating pending reply: %w", err) } // Return early. @@ -285,14 +301,38 @@ func (p *clientAPI) CreateStatus(ctx context.Context, cMsg *messages.FromClientA // sending out the Create with the approval // URI attached. - // Put approval in the database and - // update the status with approvedBy URI. - approval, err := p.utils.approveReply(ctx, status) - if err != nil { - return gtserror.Newf("error pre-approving reply: %w", err) + // Store an already-accepted interaction request. + id := id.NewULID() + approval := >smodel.InteractionRequest{ + ID: id, + StatusID: status.InReplyToID, + TargetAccountID: status.InReplyToAccountID, + TargetAccount: status.InReplyToAccount, + InteractingAccountID: status.AccountID, + InteractingAccount: status.Account, + InteractionURI: status.URI, + InteractionType: gtsmodel.InteractionLike, + Reply: status, + URI: uris.GenerateURIForAccept(status.InReplyToAccount.Username, id), + AcceptedAt: time.Now(), + } + if err := p.state.DB.PutInteractionRequest(ctx, approval); err != nil { + return gtserror.Newf("db error putting pre-approved interaction request: %w", err) + } + + // Mark the status as now approved. + status.PendingApproval = util.Ptr(false) + status.PreApproved = false + status.ApprovedByURI = approval.URI + if err := p.state.DB.UpdateStatus( + ctx, + status, + "pending_approval", + "approved_by_uri", + ); err != nil { + return gtserror.Newf("db error updating status: %w", err) } - // Send out the approval as Accept. if err := p.federate.AcceptInteraction(ctx, approval); err != nil { return gtserror.Newf("error federating pre-approval of reply: %w", err) } @@ -309,16 +349,16 @@ func (p *clientAPI) CreateStatus(ctx context.Context, cMsg *messages.FromClientA log.Errorf(ctx, "error timelining and notifying status: %v", err) } + if err := p.federate.CreateStatus(ctx, status); err != nil { + log.Errorf(ctx, "error federating status: %v", err) + } + if status.InReplyToID != "" { // Interaction counts changed on the replied status; // uncache the prepared version from all timelines. p.surface.invalidateStatusFromTimelines(ctx, status.InReplyToID) } - if err := p.federate.CreateStatus(ctx, status); err != nil { - log.Errorf(ctx, "error federating status: %v", err) - } - return nil } @@ -344,9 +384,6 @@ func (p *clientAPI) CreatePollVote(ctx context.Context, cMsg *messages.FromClien status := vote.Poll.Status status.Poll = vote.Poll - // Interaction counts changed on the source status, uncache from timelines. - p.surface.invalidateStatusFromTimelines(ctx, vote.Poll.StatusID) - if *status.Local { // These are poll votes in a local status, we only need to // federate the updated status model with latest vote counts. @@ -360,6 +397,9 @@ func (p *clientAPI) CreatePollVote(ctx context.Context, cMsg *messages.FromClien } } + // Interaction counts changed on the source status, uncache from timelines. + p.surface.invalidateStatusFromTimelines(ctx, vote.Poll.StatusID) + return nil } @@ -429,10 +469,7 @@ func (p *clientAPI) CreateLike(ctx context.Context, cMsg *messages.FromClientAPI // If pending approval is true then fave must // target a status (either one of ours or a // remote) that requires approval for the fave. - pendingApproval := util.PtrOrValue( - fave.PendingApproval, - false, - ) + pendingApproval := util.PtrOrZero(fave.PendingApproval) switch { case pendingApproval && !fave.PreApproved: @@ -442,15 +479,13 @@ func (p *clientAPI) CreateLike(ctx context.Context, cMsg *messages.FromClientAPI // and/or notify the account that's being // interacted with (if it's local): they can // approve or deny the interaction later. - - // Notify *local* account of pending reply. - if err := p.surface.notifyPendingFave(ctx, fave); err != nil { - log.Errorf(ctx, "error notifying pending fave: %v", err) + if err := p.utils.requestFave(ctx, fave); err != nil { + return gtserror.Newf("error pending fave: %w", err) } // Send Like to *remote* account inbox ONLY. if err := p.federate.Like(ctx, fave); err != nil { - log.Errorf(ctx, "error federating pending Like: %v", err) + return gtserror.Newf("error federating pending Like: %v", err) } // Return early. @@ -466,14 +501,38 @@ func (p *clientAPI) CreateLike(ctx context.Context, cMsg *messages.FromClientAPI // sending out the Like with the approval // URI attached. - // Put approval in the database and - // update the fave with approvedBy URI. - approval, err := p.utils.approveFave(ctx, fave) - if err != nil { - return gtserror.Newf("error pre-approving fave: %w", err) + // Store an already-accepted interaction request. + id := id.NewULID() + approval := >smodel.InteractionRequest{ + ID: id, + StatusID: fave.StatusID, + TargetAccountID: fave.TargetAccountID, + TargetAccount: fave.TargetAccount, + InteractingAccountID: fave.AccountID, + InteractingAccount: fave.Account, + InteractionURI: fave.URI, + InteractionType: gtsmodel.InteractionLike, + Like: fave, + URI: uris.GenerateURIForAccept(fave.TargetAccount.Username, id), + AcceptedAt: time.Now(), + } + if err := p.state.DB.PutInteractionRequest(ctx, approval); err != nil { + return gtserror.Newf("db error putting pre-approved interaction request: %w", err) + } + + // Mark the fave itself as now approved. + fave.PendingApproval = util.Ptr(false) + fave.PreApproved = false + fave.ApprovedByURI = approval.URI + if err := p.state.DB.UpdateStatusFave( + ctx, + fave, + "pending_approval", + "approved_by_uri", + ); err != nil { + return gtserror.Newf("db error updating status fave: %w", err) } - // Send out the approval as Accept. if err := p.federate.AcceptInteraction(ctx, approval); err != nil { return gtserror.Newf("error federating pre-approval of fave: %w", err) } @@ -485,14 +544,14 @@ func (p *clientAPI) CreateLike(ctx context.Context, cMsg *messages.FromClientAPI log.Errorf(ctx, "error notifying fave: %v", err) } - // Interaction counts changed on the faved status; - // uncache the prepared version from all timelines. - p.surface.invalidateStatusFromTimelines(ctx, fave.StatusID) - if err := p.federate.Like(ctx, fave); err != nil { log.Errorf(ctx, "error federating like: %v", err) } + // Interaction counts changed on the faved status; + // uncache the prepared version from all timelines. + p.surface.invalidateStatusFromTimelines(ctx, fave.StatusID) + return nil } @@ -505,10 +564,7 @@ func (p *clientAPI) CreateAnnounce(ctx context.Context, cMsg *messages.FromClien // If pending approval is true then status must // boost a status (either one of ours or a // remote) that requires approval for the boost. - pendingApproval := util.PtrOrValue( - boost.PendingApproval, - false, - ) + pendingApproval := util.PtrOrZero(boost.PendingApproval) switch { case pendingApproval && !boost.PreApproved: @@ -518,15 +574,13 @@ func (p *clientAPI) CreateAnnounce(ctx context.Context, cMsg *messages.FromClien // and/or notify the account that's being // interacted with (if it's local): they can // approve or deny the interaction later. - - // Notify *local* account of pending announce. - if err := p.surface.notifyPendingAnnounce(ctx, boost); err != nil { - log.Errorf(ctx, "error notifying pending boost: %v", err) + if err := p.utils.requestAnnounce(ctx, boost); err != nil { + return gtserror.Newf("error pending boost: %w", err) } // Send Announce to *remote* account inbox ONLY. if err := p.federate.Announce(ctx, boost); err != nil { - log.Errorf(ctx, "error federating pending Announce: %v", err) + return gtserror.Newf("error federating pending Announce: %v", err) } // Return early. @@ -542,14 +596,38 @@ func (p *clientAPI) CreateAnnounce(ctx context.Context, cMsg *messages.FromClien // sending out the Create with the approval // URI attached. - // Put approval in the database and - // update the boost with approvedBy URI. - approval, err := p.utils.approveAnnounce(ctx, boost) - if err != nil { - return gtserror.Newf("error pre-approving boost: %w", err) + // Store an already-accepted interaction request. + id := id.NewULID() + approval := >smodel.InteractionRequest{ + ID: id, + StatusID: boost.BoostOfID, + TargetAccountID: boost.BoostOfAccountID, + TargetAccount: boost.BoostOfAccount, + InteractingAccountID: boost.AccountID, + InteractingAccount: boost.Account, + InteractionURI: boost.URI, + InteractionType: gtsmodel.InteractionLike, + Announce: boost, + URI: uris.GenerateURIForAccept(boost.BoostOfAccount.Username, id), + AcceptedAt: time.Now(), + } + if err := p.state.DB.PutInteractionRequest(ctx, approval); err != nil { + return gtserror.Newf("db error putting pre-approved interaction request: %w", err) + } + + // Mark the boost itself as now approved. + boost.PendingApproval = util.Ptr(false) + boost.PreApproved = false + boost.ApprovedByURI = approval.URI + if err := p.state.DB.UpdateStatus( + ctx, + boost, + "pending_approval", + "approved_by_uri", + ); err != nil { + return gtserror.Newf("db error updating status: %w", err) } - // Send out the approval as Accept. if err := p.federate.AcceptInteraction(ctx, approval); err != nil { return gtserror.Newf("error federating pre-approval of boost: %w", err) } @@ -572,14 +650,14 @@ func (p *clientAPI) CreateAnnounce(ctx context.Context, cMsg *messages.FromClien log.Errorf(ctx, "error notifying boost: %v", err) } - // Interaction counts changed on the boosted status; - // uncache the prepared version from all timelines. - p.surface.invalidateStatusFromTimelines(ctx, boost.BoostOfID) - if err := p.federate.Announce(ctx, boost); err != nil { log.Errorf(ctx, "error federating announce: %v", err) } + // Interaction counts changed on the boosted status; + // uncache the prepared version from all timelines. + p.surface.invalidateStatusFromTimelines(ctx, boost.BoostOfID) + return nil } @@ -629,9 +707,6 @@ func (p *clientAPI) UpdateStatus(ctx context.Context, cMsg *messages.FromClientA log.Errorf(ctx, "error federating status update: %v", err) } - // Status representation has changed, invalidate from timelines. - p.surface.invalidateStatusFromTimelines(ctx, status.ID) - if status.Poll != nil && status.Poll.Closing { // If the latest status has a newly closed poll, at least compared @@ -646,6 +721,9 @@ func (p *clientAPI) UpdateStatus(ctx context.Context, cMsg *messages.FromClientA log.Errorf(ctx, "error streaming status edit: %v", err) } + // Status representation has changed, invalidate from timelines. + p.surface.invalidateStatusFromTimelines(ctx, status.ID) + return nil } @@ -791,14 +869,14 @@ func (p *clientAPI) UndoFave(ctx context.Context, cMsg *messages.FromClientAPI) return gtserror.Newf("%T not parseable as *gtsmodel.StatusFave", cMsg.GTSModel) } - // Interaction counts changed on the faved status; - // uncache the prepared version from all timelines. - p.surface.invalidateStatusFromTimelines(ctx, statusFave.StatusID) - if err := p.federate.UndoLike(ctx, statusFave); err != nil { log.Errorf(ctx, "error federating like undo: %v", err) } + // Interaction counts changed on the faved status; + // uncache the prepared version from all timelines. + p.surface.invalidateStatusFromTimelines(ctx, statusFave.StatusID) + return nil } @@ -821,14 +899,14 @@ func (p *clientAPI) UndoAnnounce(ctx context.Context, cMsg *messages.FromClientA log.Errorf(ctx, "error removing timelined status: %v", err) } - // Interaction counts changed on the boosted status; - // uncache the prepared version from all timelines. - p.surface.invalidateStatusFromTimelines(ctx, status.BoostOfID) - if err := p.federate.UndoAnnounce(ctx, status); err != nil { log.Errorf(ctx, "error federating announce undo: %v", err) } + // Interaction counts changed on the boosted status; + // uncache the prepared version from all timelines. + p.surface.invalidateStatusFromTimelines(ctx, status.BoostOfID) + return nil } @@ -874,16 +952,16 @@ func (p *clientAPI) DeleteStatus(ctx context.Context, cMsg *messages.FromClientA log.Errorf(ctx, "error updating account stats: %v", err) } + if err := p.federate.DeleteStatus(ctx, status); err != nil { + log.Errorf(ctx, "error federating status delete: %v", err) + } + if status.InReplyToID != "" { // Interaction counts changed on the replied status; // uncache the prepared version from all timelines. p.surface.invalidateStatusFromTimelines(ctx, status.InReplyToID) } - if err := p.federate.DeleteStatus(ctx, status); err != nil { - log.Errorf(ctx, "error federating status delete: %v", err) - } - return nil } @@ -1050,16 +1128,188 @@ func (p *clientAPI) RejectUser(ctx context.Context, cMsg *messages.FromClientAPI } func (p *clientAPI) AcceptLike(ctx context.Context, cMsg *messages.FromClientAPI) error { - // TODO + req, ok := cMsg.GTSModel.(*gtsmodel.InteractionRequest) + if !ok { + return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", cMsg.GTSModel) + } + + // Notify the fave (distinct from the notif for the pending fave). + if err := p.surface.notifyFave(ctx, req.Like); err != nil { + log.Errorf(ctx, "error notifying fave: %v", err) + } + + // Send out the Accept. + if err := p.federate.AcceptInteraction(ctx, req); err != nil { + log.Errorf(ctx, "error federating approval of like: %v", err) + } + + // Interaction counts changed on the faved status; + // uncache the prepared version from all timelines. + p.surface.invalidateStatusFromTimelines(ctx, req.Like.StatusID) + return nil } func (p *clientAPI) AcceptReply(ctx context.Context, cMsg *messages.FromClientAPI) error { - // TODO + req, ok := cMsg.GTSModel.(*gtsmodel.InteractionRequest) + if !ok { + return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", cMsg.GTSModel) + } + + var ( + interactingAcct = req.InteractingAccount + reply = req.Reply + ) + + // Update stats for the reply author account. + if err := p.utils.incrementStatusesCount(ctx, interactingAcct, reply); err != nil { + log.Errorf(ctx, "error updating account stats: %v", err) + } + + // Timeline the reply + notify relevant accounts. + if err := p.surface.timelineAndNotifyStatus(ctx, reply); err != nil { + log.Errorf(ctx, "error timelining and notifying status reply: %v", err) + } + + // Send out the Accept. + if err := p.federate.AcceptInteraction(ctx, req); err != nil { + log.Errorf(ctx, "error federating approval of reply: %v", err) + } + + // Interaction counts changed on the replied status; + // uncache the prepared version from all timelines. + p.surface.invalidateStatusFromTimelines(ctx, reply.InReplyToID) + return nil } func (p *clientAPI) AcceptAnnounce(ctx context.Context, cMsg *messages.FromClientAPI) error { - // TODO + req, ok := cMsg.GTSModel.(*gtsmodel.InteractionRequest) + if !ok { + return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", cMsg.GTSModel) + } + + var ( + interactingAcct = req.InteractingAccount + boost = req.Announce + ) + + // Update stats for the boost author account. + if err := p.utils.incrementStatusesCount(ctx, interactingAcct, boost); err != nil { + log.Errorf(ctx, "error updating account stats: %v", err) + } + + // Timeline and notify the announce. + if err := p.surface.timelineAndNotifyStatus(ctx, boost); err != nil { + log.Errorf(ctx, "error timelining and notifying status: %v", err) + } + + // Notify the announce (distinct from the notif for the pending announce). + if err := p.surface.notifyAnnounce(ctx, boost); err != nil { + log.Errorf(ctx, "error notifying announce: %v", err) + } + + // Send out the Accept. + if err := p.federate.AcceptInteraction(ctx, req); err != nil { + log.Errorf(ctx, "error federating approval of announce: %v", err) + } + + // Interaction counts changed on the original status; + // uncache the prepared version from all timelines. + p.surface.invalidateStatusFromTimelines(ctx, boost.BoostOfID) + + return nil +} + +func (p *clientAPI) RejectLike(ctx context.Context, cMsg *messages.FromClientAPI) error { + req, ok := cMsg.GTSModel.(*gtsmodel.InteractionRequest) + if !ok { + return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", cMsg.GTSModel) + } + + // At this point the InteractionRequest should already + // be in the database, we just need to do side effects. + + // Send out the Reject. + if err := p.federate.RejectInteraction(ctx, req); err != nil { + log.Errorf(ctx, "error federating rejection of like: %v", err) + } + + // Get the rejected fave. + fave, err := p.state.DB.GetStatusFaveByURI( + gtscontext.SetBarebones(ctx), + req.InteractionURI, + ) + if err != nil { + return gtserror.Newf("db error getting rejected fave: %w", err) + } + + // Delete the status fave. + if err := p.state.DB.DeleteStatusFaveByID(ctx, fave.ID); err != nil { + return gtserror.Newf("db error deleting status fave: %w", err) + } + + return nil +} + +func (p *clientAPI) RejectReply(ctx context.Context, cMsg *messages.FromClientAPI) error { + req, ok := cMsg.GTSModel.(*gtsmodel.InteractionRequest) + if !ok { + return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", cMsg.GTSModel) + } + + // At this point the InteractionRequest should already + // be in the database, we just need to do side effects. + + // Send out the Reject. + if err := p.federate.RejectInteraction(ctx, req); err != nil { + log.Errorf(ctx, "error federating rejection of reply: %v", err) + } + + // Get the rejected status. + status, err := p.state.DB.GetStatusByURI( + gtscontext.SetBarebones(ctx), + req.InteractionURI, + ) + if err != nil { + return gtserror.Newf("db error getting rejected reply: %w", err) + } + + // Totally wipe the status. + if err := p.utils.wipeStatus(ctx, status, true); err != nil { + return gtserror.Newf("error wiping status: %w", err) + } + + return nil +} + +func (p *clientAPI) RejectAnnounce(ctx context.Context, cMsg *messages.FromClientAPI) error { + req, ok := cMsg.GTSModel.(*gtsmodel.InteractionRequest) + if !ok { + return gtserror.Newf("%T not parseable as *gtsmodel.InteractionRequest", cMsg.GTSModel) + } + + // At this point the InteractionRequest should already + // be in the database, we just need to do side effects. + + // Send out the Reject. + if err := p.federate.RejectInteraction(ctx, req); err != nil { + log.Errorf(ctx, "error federating rejection of announce: %v", err) + } + + // Get the rejected boost. + boost, err := p.state.DB.GetStatusByURI( + gtscontext.SetBarebones(ctx), + req.InteractionURI, + ) + if err != nil { + return gtserror.Newf("db error getting rejected announce: %w", err) + } + + // Totally wipe the status. + if err := p.utils.wipeStatus(ctx, boost, true); err != nil { + return gtserror.Newf("error wiping status: %w", err) + } + return nil } |