[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:
tobi
2024-08-24 11:49:37 +02:00
committed by GitHub
parent 8e5a72ac5c
commit f23f04e0b1
72 changed files with 4446 additions and 663 deletions

View File

@ -26,12 +26,11 @@ import (
"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/processing/account"
"github.com/superseriousbusiness/gotosocial/internal/processing/media"
"github.com/superseriousbusiness/gotosocial/internal/state"
"github.com/superseriousbusiness/gotosocial/internal/uris"
"github.com/superseriousbusiness/gotosocial/internal/typeutils"
"github.com/superseriousbusiness/gotosocial/internal/util"
)
@ -488,128 +487,143 @@ func (u *utils) decrementFollowRequestsCount(
return nil
}
// approveFave stores + returns an
// interactionApproval for a fave.
func (u *utils) approveFave(
// requestFave stores an interaction request
// for the given fave, and notifies the interactee.
func (u *utils) requestFave(
ctx context.Context,
fave *gtsmodel.StatusFave,
) (*gtsmodel.InteractionApproval, error) {
id := id.NewULID()
approval := &gtsmodel.InteractionApproval{
ID: id,
AccountID: fave.TargetAccountID,
Account: fave.TargetAccount,
InteractingAccountID: fave.AccountID,
InteractingAccount: fave.Account,
InteractionURI: fave.URI,
InteractionType: gtsmodel.InteractionLike,
URI: uris.GenerateURIForAccept(fave.TargetAccount.Username, id),
) error {
// Only create interaction request
// if fave targets a local status.
if fave.Status == nil ||
!fave.Status.IsLocal() {
return nil
}
if err := u.state.DB.PutInteractionApproval(ctx, approval); err != nil {
err := gtserror.Newf("db error inserting interaction approval: %w", err)
return nil, err
// Lock on the interaction URI.
unlock := u.state.ProcessingLocks.Lock(fave.URI)
defer unlock()
// Ensure no req with this URI exists already.
req, err := u.state.DB.GetInteractionRequestByInteractionURI(ctx, fave.URI)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return gtserror.Newf("db error checking for existing 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 := u.state.DB.UpdateStatusFave(
ctx,
fave,
"pending_approval",
"approved_by_uri",
); err != nil {
err := gtserror.Newf("db error updating status fave: %w", err)
return nil, err
if req != nil {
// Interaction req already exists,
// no need to do anything else.
return nil
}
return approval, nil
// Create + store new interaction request.
req, err = typeutils.StatusFaveToInteractionRequest(ctx, fave)
if err != nil {
return gtserror.Newf("error creating interaction request: %w", err)
}
if err := u.state.DB.PutInteractionRequest(ctx, req); err != nil {
return gtserror.Newf("db error storing interaction request: %w", err)
}
// Notify *local* account of pending announce.
if err := u.surface.notifyPendingFave(ctx, fave); err != nil {
return gtserror.Newf("error notifying pending fave: %w", err)
}
return nil
}
// approveReply stores + returns an
// interactionApproval for a reply.
func (u *utils) approveReply(
// requestReply stores an interaction request
// for the given reply, and notifies the interactee.
func (u *utils) requestReply(
ctx context.Context,
status *gtsmodel.Status,
) (*gtsmodel.InteractionApproval, error) {
id := id.NewULID()
approval := &gtsmodel.InteractionApproval{
ID: id,
AccountID: status.InReplyToAccountID,
Account: status.InReplyToAccount,
InteractingAccountID: status.AccountID,
InteractingAccount: status.Account,
InteractionURI: status.URI,
InteractionType: gtsmodel.InteractionReply,
URI: uris.GenerateURIForAccept(status.InReplyToAccount.Username, id),
reply *gtsmodel.Status,
) error {
// Only create interaction request if
// status replies to a local status.
if reply.InReplyTo == nil ||
!reply.InReplyTo.IsLocal() {
return nil
}
if err := u.state.DB.PutInteractionApproval(ctx, approval); err != nil {
err := gtserror.Newf("db error inserting interaction approval: %w", err)
return nil, err
// Lock on the interaction URI.
unlock := u.state.ProcessingLocks.Lock(reply.URI)
defer unlock()
// Ensure no req with this URI exists already.
req, err := u.state.DB.GetInteractionRequestByInteractionURI(ctx, reply.URI)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return gtserror.Newf("db error checking for existing interaction request: %w", err)
}
// Mark the status itself as now approved.
status.PendingApproval = util.Ptr(false)
status.PreApproved = false
status.ApprovedByURI = approval.URI
if err := u.state.DB.UpdateStatus(
ctx,
status,
"pending_approval",
"approved_by_uri",
); err != nil {
err := gtserror.Newf("db error updating status: %w", err)
return nil, err
if req != nil {
// Interaction req already exists,
// no need to do anything else.
return nil
}
return approval, nil
// Create + store interaction request.
req, err = typeutils.StatusToInteractionRequest(ctx, reply)
if err != nil {
return gtserror.Newf("error creating interaction request: %w", err)
}
if err := u.state.DB.PutInteractionRequest(ctx, req); err != nil {
return gtserror.Newf("db error storing interaction request: %w", err)
}
// Notify *local* account of pending reply.
if err := u.surface.notifyPendingReply(ctx, reply); err != nil {
return gtserror.Newf("error notifying pending reply: %w", err)
}
return nil
}
// approveAnnounce stores + returns an
// interactionApproval for an announce.
func (u *utils) approveAnnounce(
// requestAnnounce stores an interaction request
// for the given announce, and notifies the interactee.
func (u *utils) requestAnnounce(
ctx context.Context,
boost *gtsmodel.Status,
) (*gtsmodel.InteractionApproval, error) {
id := id.NewULID()
approval := &gtsmodel.InteractionApproval{
ID: id,
AccountID: boost.BoostOfAccountID,
Account: boost.BoostOfAccount,
InteractingAccountID: boost.AccountID,
InteractingAccount: boost.Account,
InteractionURI: boost.URI,
InteractionType: gtsmodel.InteractionReply,
URI: uris.GenerateURIForAccept(boost.BoostOfAccount.Username, id),
) error {
// Only create interaction request if
// status announces a local status.
if boost.BoostOf == nil ||
!boost.BoostOf.IsLocal() {
return nil
}
if err := u.state.DB.PutInteractionApproval(ctx, approval); err != nil {
err := gtserror.Newf("db error inserting interaction approval: %w", err)
return nil, err
// Lock on the interaction URI.
unlock := u.state.ProcessingLocks.Lock(boost.URI)
defer unlock()
// Ensure no req with this URI exists already.
req, err := u.state.DB.GetInteractionRequestByInteractionURI(ctx, boost.URI)
if err != nil && !errors.Is(err, db.ErrNoEntries) {
return gtserror.Newf("db error checking for existing interaction request: %w", err)
}
// Mark the status itself as now approved.
boost.PendingApproval = util.Ptr(false)
boost.PreApproved = false
boost.ApprovedByURI = approval.URI
if err := u.state.DB.UpdateStatus(
ctx,
boost,
"pending_approval",
"approved_by_uri",
); err != nil {
err := gtserror.Newf("db error updating boost wrapper status: %w", err)
return nil, err
if req != nil {
// Interaction req already exists,
// no need to do anything else.
return nil
}
return approval, nil
// Create + store interaction request.
req, err = typeutils.StatusToInteractionRequest(ctx, boost)
if err != nil {
return gtserror.Newf("error creating interaction request: %w", err)
}
if err := u.state.DB.PutInteractionRequest(ctx, req); err != nil {
return gtserror.Newf("db error storing interaction request: %w", err)
}
// Notify *local* account of pending announce.
if err := u.surface.notifyPendingAnnounce(ctx, boost); err != nil {
return gtserror.Newf("error notifying pending announce: %w", err)
}
return nil
}