mirror of
https://github.com/superseriousbusiness/gotosocial
synced 2025-06-05 21:59:39 +02:00
[feature] Interaction requests client api + settings panel (#3215)
* [feature] Interaction requests client api + settings panel * test accept / reject * fmt * don't pin rejected interaction * use single db model for interaction accept, reject, and request * swaggor * env sharting * append errors * remove ErrNoEntries checks * change intReqID to reqID * rename "pend" to "request" * markIntsPending -> mark interactionsPending * use log instead of returning error when rejecting interaction * empty migration * jolly renaming * make interactionURI unique again * swag grr * remove unnecessary locks * invalidate as last step
This commit is contained in:
@ -20,18 +20,22 @@ 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/federation/dereferencing"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/id"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/uris"
|
||||
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
||||
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
||||
"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/util"
|
||||
)
|
||||
@ -44,6 +48,7 @@ type fediAPI struct {
|
||||
surface *Surface
|
||||
federate *federate
|
||||
account *account.Processor
|
||||
common *common.Processor
|
||||
utils *utils
|
||||
}
|
||||
|
||||
@ -231,10 +236,7 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI)
|
||||
// If pending approval is true then
|
||||
// status must reply to a LOCAL status
|
||||
// that requires approval for the reply.
|
||||
pendingApproval := util.PtrOrValue(
|
||||
status.PendingApproval,
|
||||
false,
|
||||
)
|
||||
pendingApproval := util.PtrOrZero(status.PendingApproval)
|
||||
|
||||
switch {
|
||||
case pendingApproval && !status.PreApproved:
|
||||
@ -242,10 +244,8 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI)
|
||||
// preapproved, then just notify the account
|
||||
// that's being interacted with: 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)
|
||||
}
|
||||
|
||||
// Return early.
|
||||
@ -259,11 +259,33 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI)
|
||||
// collection. Do the Accept immediately and
|
||||
// then process everything else as normal.
|
||||
|
||||
// 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(),
|
||||
}
|
||||
|
||||
// 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.
|
||||
@ -279,6 +301,10 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI)
|
||||
log.Errorf(ctx, "error updating account stats: %v", err)
|
||||
}
|
||||
|
||||
if err := p.surface.timelineAndNotifyStatus(ctx, status); err != nil {
|
||||
log.Errorf(ctx, "error timelining and notifying status: %v", err)
|
||||
}
|
||||
|
||||
if status.InReplyToID != "" {
|
||||
// Interaction counts changed on the replied status; uncache the
|
||||
// prepared version from all timelines. The status dereferencer
|
||||
@ -286,10 +312,6 @@ func (p *fediAPI) CreateStatus(ctx context.Context, fMsg *messages.FromFediAPI)
|
||||
p.surface.invalidateStatusFromTimelines(ctx, status.InReplyToID)
|
||||
}
|
||||
|
||||
if err := p.surface.timelineAndNotifyStatus(ctx, status); err != nil {
|
||||
log.Errorf(ctx, "error timelining and notifying status: %v", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -320,9 +342,6 @@ func (p *fediAPI) CreatePollVote(ctx context.Context, fMsg *messages.FromFediAPI
|
||||
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 {
|
||||
// Before federating it, increment the
|
||||
// poll vote counts on our local copy.
|
||||
@ -335,6 +354,9 @@ func (p *fediAPI) CreatePollVote(ctx context.Context, fMsg *messages.FromFediAPI
|
||||
}
|
||||
}
|
||||
|
||||
// Interaction counts changed on the source status, uncache from timelines.
|
||||
p.surface.invalidateStatusFromTimelines(ctx, vote.Poll.StatusID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -409,10 +431,7 @@ func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) er
|
||||
// If pending approval is true then
|
||||
// fave must target a LOCAL status
|
||||
// that requires approval for the fave.
|
||||
pendingApproval := util.PtrOrValue(
|
||||
fave.PendingApproval,
|
||||
false,
|
||||
)
|
||||
pendingApproval := util.PtrOrZero(fave.PendingApproval)
|
||||
|
||||
switch {
|
||||
case pendingApproval && !fave.PreApproved:
|
||||
@ -420,10 +439,8 @@ func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) er
|
||||
// preapproved, then just notify the account
|
||||
// that's being interacted with: they can
|
||||
// approve or deny the interaction later.
|
||||
|
||||
// Notify *local* account of pending fave.
|
||||
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)
|
||||
}
|
||||
|
||||
// Return early.
|
||||
@ -437,11 +454,33 @@ func (p *fediAPI) CreateLike(ctx context.Context, fMsg *messages.FromFediAPI) er
|
||||
// collection. Do the Accept immediately and
|
||||
// then process everything else as normal.
|
||||
|
||||
// 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(),
|
||||
}
|
||||
|
||||
// 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.
|
||||
@ -496,10 +535,7 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
|
||||
// If pending approval is true then
|
||||
// boost must target a LOCAL status
|
||||
// that requires approval for the boost.
|
||||
pendingApproval := util.PtrOrValue(
|
||||
boost.PendingApproval,
|
||||
false,
|
||||
)
|
||||
pendingApproval := util.PtrOrZero(boost.PendingApproval)
|
||||
|
||||
switch {
|
||||
case pendingApproval && !boost.PreApproved:
|
||||
@ -507,10 +543,8 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
|
||||
// preapproved, then just notify the account
|
||||
// that's being interacted with: 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)
|
||||
}
|
||||
|
||||
// Return early.
|
||||
@ -524,11 +558,33 @@ func (p *fediAPI) CreateAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
|
||||
// collection. Do the Accept immediately and
|
||||
// then process everything else as normal.
|
||||
|
||||
// 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(),
|
||||
}
|
||||
|
||||
// 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.
|
||||
@ -729,15 +785,15 @@ func (p *fediAPI) AcceptReply(ctx context.Context, fMsg *messages.FromFediAPI) e
|
||||
log.Errorf(ctx, "error timelining and notifying status: %v", err)
|
||||
}
|
||||
|
||||
// Interaction counts changed on the replied-to status;
|
||||
// uncache the prepared version from all timelines.
|
||||
p.surface.invalidateStatusFromTimelines(ctx, status.InReplyToID)
|
||||
|
||||
// Send out the reply again, fully this time.
|
||||
if err := p.federate.CreateStatus(ctx, status); err != nil {
|
||||
log.Errorf(ctx, "error federating announce: %v", err)
|
||||
}
|
||||
|
||||
// Interaction counts changed on the replied-to status;
|
||||
// uncache the prepared version from all timelines.
|
||||
p.surface.invalidateStatusFromTimelines(ctx, status.InReplyToID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
@ -757,15 +813,15 @@ func (p *fediAPI) AcceptAnnounce(ctx context.Context, fMsg *messages.FromFediAPI
|
||||
log.Errorf(ctx, "error timelining and notifying status: %v", err)
|
||||
}
|
||||
|
||||
// Interaction counts changed on the boosted status;
|
||||
// uncache the prepared version from all timelines.
|
||||
p.surface.invalidateStatusFromTimelines(ctx, boost.BoostOfID)
|
||||
|
||||
// Send out the boost again, fully this time.
|
||||
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
|
||||
}
|
||||
|
||||
@ -792,9 +848,6 @@ func (p *fediAPI) UpdateStatus(ctx context.Context, fMsg *messages.FromFediAPI)
|
||||
log.Errorf(ctx, "error refreshing status: %v", err)
|
||||
}
|
||||
|
||||
// Status representation was refetched, uncache 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
|
||||
@ -809,6 +862,9 @@ func (p *fediAPI) UpdateStatus(ctx context.Context, fMsg *messages.FromFediAPI)
|
||||
log.Errorf(ctx, "error streaming status edit: %v", err)
|
||||
}
|
||||
|
||||
// Status representation was refetched, uncache from timelines.
|
||||
p.surface.invalidateStatusFromTimelines(ctx, status.ID)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
|
Reference in New Issue
Block a user