2024-03-13 13:53:29 +01:00
|
|
|
// 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 workers
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"context"
|
|
|
|
|
"errors"
|
|
|
|
|
|
2025-04-26 15:34:10 +02:00
|
|
|
apimodel "code.superseriousbusiness.org/gotosocial/internal/api/model"
|
|
|
|
|
"code.superseriousbusiness.org/gotosocial/internal/db"
|
|
|
|
|
"code.superseriousbusiness.org/gotosocial/internal/gtscontext"
|
|
|
|
|
"code.superseriousbusiness.org/gotosocial/internal/gtserror"
|
|
|
|
|
"code.superseriousbusiness.org/gotosocial/internal/gtsmodel"
|
|
|
|
|
"code.superseriousbusiness.org/gotosocial/internal/log"
|
|
|
|
|
"code.superseriousbusiness.org/gotosocial/internal/processing/account"
|
|
|
|
|
"code.superseriousbusiness.org/gotosocial/internal/processing/media"
|
|
|
|
|
"code.superseriousbusiness.org/gotosocial/internal/state"
|
|
|
|
|
"code.superseriousbusiness.org/gotosocial/internal/typeutils"
|
|
|
|
|
"code.superseriousbusiness.org/gotosocial/internal/util"
|
2024-03-13 13:53:29 +01:00
|
|
|
)
|
|
|
|
|
|
2024-04-26 13:50:46 +01:00
|
|
|
// util provides util functions used by both
|
2024-03-13 13:53:29 +01:00
|
|
|
// the fromClientAPI and fromFediAPI functions.
|
2024-04-26 13:50:46 +01:00
|
|
|
type utils struct {
|
2024-09-10 14:34:49 +02:00
|
|
|
state *state.State
|
|
|
|
|
media *media.Processor
|
|
|
|
|
account *account.Processor
|
|
|
|
|
surface *Surface
|
|
|
|
|
converter *typeutils.Converter
|
2024-03-13 13:53:29 +01:00
|
|
|
}
|
|
|
|
|
|
2024-09-10 14:34:49 +02:00
|
|
|
// wipeStatus encapsulates common logic used to
|
|
|
|
|
// totally delete a status + all its attachments,
|
|
|
|
|
// notifications, boosts, and timeline entries.
|
|
|
|
|
//
|
|
|
|
|
// If deleteAttachments is true, then any status
|
|
|
|
|
// attachments will also be deleted, else they
|
|
|
|
|
// will just be detached.
|
|
|
|
|
//
|
|
|
|
|
// If copyToSinBin is true, then a version of the
|
|
|
|
|
// status will be put in the `sin_bin_statuses`
|
|
|
|
|
// table prior to deletion.
|
2024-04-26 13:50:46 +01:00
|
|
|
func (u *utils) wipeStatus(
|
2024-03-13 13:53:29 +01:00
|
|
|
ctx context.Context,
|
2024-09-10 14:34:49 +02:00
|
|
|
status *gtsmodel.Status,
|
2024-03-13 13:53:29 +01:00
|
|
|
deleteAttachments bool,
|
2024-09-10 14:34:49 +02:00
|
|
|
copyToSinBin bool,
|
2024-03-13 13:53:29 +01:00
|
|
|
) error {
|
|
|
|
|
var errs gtserror.MultiError
|
|
|
|
|
|
2024-09-10 14:34:49 +02:00
|
|
|
if copyToSinBin {
|
|
|
|
|
// Copy this status to the sin bin before we delete it.
|
|
|
|
|
sbStatus, err := u.converter.StatusToSinBinStatus(ctx, status)
|
|
|
|
|
if err != nil {
|
|
|
|
|
errs.Appendf("error converting status to sinBinStatus: %w", err)
|
|
|
|
|
} else {
|
|
|
|
|
if err := u.state.DB.PutSinBinStatus(ctx, sbStatus); err != nil {
|
|
|
|
|
errs.Appendf("db error storing sinBinStatus: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-05 13:35:07 +00:00
|
|
|
// Before handling media, ensure
|
|
|
|
|
// historic edits are populated.
|
|
|
|
|
if !status.EditsPopulated() {
|
|
|
|
|
var err error
|
|
|
|
|
|
|
|
|
|
// Fetch all historical edits of status from database.
|
|
|
|
|
status.Edits, err = u.state.DB.GetStatusEditsByIDs(
|
|
|
|
|
gtscontext.SetBarebones(ctx),
|
|
|
|
|
status.EditIDs,
|
|
|
|
|
)
|
|
|
|
|
if err != nil {
|
|
|
|
|
errs.Appendf("error getting status edits from database: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-03-13 13:53:29 +01:00
|
|
|
// Either delete all attachments for this status,
|
2024-09-10 14:34:49 +02:00
|
|
|
// or simply detach + clean them separately later.
|
2024-03-13 13:53:29 +01:00
|
|
|
//
|
2024-09-10 14:34:49 +02:00
|
|
|
// Reason to detach rather than delete is that
|
|
|
|
|
// the author might want to reattach them to another
|
|
|
|
|
// status immediately (in case of delete + redraft).
|
2024-03-13 13:53:29 +01:00
|
|
|
if deleteAttachments {
|
|
|
|
|
// todo:u.state.DB.DeleteAttachmentsForStatus
|
2024-12-05 13:35:07 +00:00
|
|
|
for _, id := range status.AllAttachmentIDs() {
|
2024-03-13 13:53:29 +01:00
|
|
|
if err := u.media.Delete(ctx, id); err != nil {
|
|
|
|
|
errs.Appendf("error deleting media: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
} else {
|
|
|
|
|
// todo:u.state.DB.UnattachAttachmentsForStatus
|
2024-12-05 13:35:07 +00:00
|
|
|
for _, id := range status.AllAttachmentIDs() {
|
2024-09-10 14:34:49 +02:00
|
|
|
if _, err := u.media.Unattach(ctx, status.Account, id); err != nil {
|
2024-03-13 13:53:29 +01:00
|
|
|
errs.Appendf("error unattaching media: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-05 13:35:07 +00:00
|
|
|
// Delete all historical edits of status.
|
|
|
|
|
if ids := status.EditIDs; len(ids) > 0 {
|
|
|
|
|
if err := u.state.DB.DeleteStatusEdits(ctx, ids); err != nil {
|
|
|
|
|
errs.Appendf("error deleting status edits: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-10 14:34:49 +02:00
|
|
|
// Delete all mentions generated by this status.
|
2024-03-13 13:53:29 +01:00
|
|
|
// todo:u.state.DB.DeleteMentionsForStatus
|
2024-09-10 14:34:49 +02:00
|
|
|
for _, id := range status.MentionIDs {
|
2024-03-13 13:53:29 +01:00
|
|
|
if err := u.state.DB.DeleteMentionByID(ctx, id); err != nil {
|
|
|
|
|
errs.Appendf("error deleting status mention: %w", err)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-10 14:34:49 +02:00
|
|
|
// Delete all notifications generated by this status.
|
|
|
|
|
if err := u.state.DB.DeleteNotificationsForStatus(ctx, status.ID); err != nil {
|
2024-03-13 13:53:29 +01:00
|
|
|
errs.Appendf("error deleting status notifications: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-10 14:34:49 +02:00
|
|
|
// Delete all bookmarks of this status.
|
|
|
|
|
if err := u.state.DB.DeleteStatusBookmarksForStatus(ctx, status.ID); err != nil {
|
2024-03-13 13:53:29 +01:00
|
|
|
errs.Appendf("error deleting status bookmarks: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-10 14:34:49 +02:00
|
|
|
// Delete all faves of this status.
|
|
|
|
|
if err := u.state.DB.DeleteStatusFavesForStatus(ctx, status.ID); err != nil {
|
2024-03-13 13:53:29 +01:00
|
|
|
errs.Appendf("error deleting status faves: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
2024-12-05 13:35:07 +00:00
|
|
|
if id := status.PollID; id != "" {
|
2024-03-13 13:53:29 +01:00
|
|
|
// Delete this poll by ID from the database.
|
2024-12-05 13:35:07 +00:00
|
|
|
if err := u.state.DB.DeletePollByID(ctx, id); err != nil {
|
2024-03-13 13:53:29 +01:00
|
|
|
errs.Appendf("error deleting status poll: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Cancel any scheduled expiry task for poll.
|
2024-12-05 13:35:07 +00:00
|
|
|
_ = u.state.Workers.Scheduler.Cancel(id)
|
2024-03-13 13:53:29 +01:00
|
|
|
}
|
|
|
|
|
|
2024-09-10 14:34:49 +02:00
|
|
|
// Get all boost of this status so that we can
|
|
|
|
|
// delete those boosts + remove them from timelines.
|
2024-03-13 13:53:29 +01:00
|
|
|
boosts, err := u.state.DB.GetStatusBoosts(
|
2024-12-05 13:35:07 +00:00
|
|
|
|
2024-09-10 14:34:49 +02:00
|
|
|
// We MUST set a barebones context here,
|
2024-03-13 13:53:29 +01:00
|
|
|
// as depending on where it came from the
|
|
|
|
|
// original BoostOf may already be gone.
|
|
|
|
|
gtscontext.SetBarebones(ctx),
|
2024-09-10 14:34:49 +02:00
|
|
|
status.ID)
|
2024-03-13 13:53:29 +01:00
|
|
|
if err != nil {
|
|
|
|
|
errs.Appendf("error fetching status boosts: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, boost := range boosts {
|
2024-09-10 14:34:49 +02:00
|
|
|
// Delete the boost itself.
|
2024-03-13 13:53:29 +01:00
|
|
|
if err := u.state.DB.DeleteStatusByID(ctx, boost.ID); err != nil {
|
|
|
|
|
errs.Appendf("error deleting boost: %w", err)
|
|
|
|
|
}
|
2024-09-10 14:34:49 +02:00
|
|
|
|
|
|
|
|
// Remove the boost from any and all timelines.
|
[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values :facepalm:
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
2025-04-26 09:56:15 +00:00
|
|
|
u.surface.deleteStatusFromTimelines(ctx, boost.ID)
|
2024-03-13 13:53:29 +01:00
|
|
|
}
|
|
|
|
|
|
2024-09-10 14:34:49 +02:00
|
|
|
// Delete the status itself from any and all timelines.
|
[performance] rewrite timelines to rely on new timeline cache type (#3941)
* start work rewriting timeline cache type
* further work rewriting timeline caching
* more work integration new timeline code
* remove old code
* add local timeline, fix up merge conflicts
* remove old use of go-bytes
* implement new timeline code into more areas of codebase, pull in latest go-mangler, go-mutexes, go-structr
* remove old timeline package, add local timeline cache
* remove references to old timeline types that needed starting up in tests
* start adding page validation
* fix test-identified timeline cache package issues
* fix up more tests, fix missing required changes, etc
* add exclusion for test.out in gitignore
* clarify some things better in code comments
* tweak cache size limits
* fix list timeline cache fetching
* further list timeline fixes
* linter, ssssssssshhhhhhhhhhhh please
* fix linter hints
* reslice the output if it's beyond length of 'lim'
* remove old timeline initialization code, bump go-structr to v0.9.4
* continued from previous commit
* improved code comments
* don't allow multiple entries for BoostOfID values to prevent repeated boosts of same boosts
* finish writing more code comments
* some variable renaming, for ease of following
* change the way we update lo,hi paging values during timeline load
* improved code comments for updated / returned lo , hi paging values
* finish writing code comments for the StatusTimeline{} type itself
* fill in more code comments
* update go-structr version to latest with changed timeline unique indexing logic
* have a local and public timeline *per user*
* rewrite calls to public / local timeline calls
* remove the zero length check, as lo, hi values might still be set
* simplify timeline cache loading, fix lo/hi returns, fix timeline invalidation side-effects missing for some federated actions
* swap the lo, hi values :facepalm:
* add (now) missing slice reverse of tag timeline statuses when paging ASC
* remove local / public caches (is out of scope for this work), share more timeline code
* remove unnecessary change
* again, remove more unused code
* remove unused function to appease the linter
* move boost checking to prepare function
* fix use of timeline.lastOrder, fix incorrect range functions used
* remove comments for repeat code
* remove the boost logic from prepare function
* do a maximum of 5 loads, not 10
* add repeat boost filtering logic, update go-structr, general improvements
* more code comments
* add important note
* fix timeline tests now that timelines are returned in page order
* remove unused field
* add StatusTimeline{} tests
* add more status timeline tests
* start adding preloading support
* ensure repeat boosts are marked in preloaded entries
* share a bunch of the database load code in timeline cache, don't clear timelines on relationship change
* add logic to allow dynamic clear / preloading of timelines
* comment-out unused functions, but leave in place as we might end-up using them
* fix timeline preload state check
* much improved status timeline code comments
* more code comments, don't bother inserting statuses if timeline not preloaded
* shift around some logic to make sure things aren't accidentally left set
* finish writing code comments
* remove trim-after-insert behaviour
* fix-up some comments referring to old logic
* remove unsetting of lo, hi
* fix preload repeatBoost checking logic
* don't return on status filter errors, these are usually transient
* better concurrency safety in Clear() and Done()
* fix test broken due to addition of preloader
* fix repeatBoost logic that doesn't account for already-hidden repeatBoosts
* ensure edit submodels are dropped on cache insertion
* update code-comment to expand CAS accronym
* use a plus1hULID() instead of 24h
* remove unused functions
* add note that public / local timeline requester can be nil
* fix incorrect visibility filtering of tag timeline statuses
* ensure we filter home timeline statuses on local only
* some small re-orderings to confirm query params in correct places
* fix the local only home timeline filter func
2025-04-26 09:56:15 +00:00
|
|
|
u.surface.deleteStatusFromTimelines(ctx, status.ID)
|
2024-03-13 13:53:29 +01:00
|
|
|
|
2024-09-10 14:34:49 +02:00
|
|
|
// Delete this status from any conversations it's part of.
|
|
|
|
|
if err := u.state.DB.DeleteStatusFromConversations(ctx, status.ID); err != nil {
|
2024-07-23 12:44:31 -07:00
|
|
|
errs.Appendf("error deleting status from conversations: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
2024-09-10 14:34:49 +02:00
|
|
|
// Finally delete the status itself.
|
|
|
|
|
if err := u.state.DB.DeleteStatusByID(ctx, status.ID); err != nil {
|
2024-03-13 13:53:29 +01:00
|
|
|
errs.Appendf("error deleting status: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return errs.Combine()
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// redirectFollowers redirects all local
|
|
|
|
|
// followers of originAcct to targetAcct.
|
|
|
|
|
//
|
|
|
|
|
// Both accounts must be fully dereferenced
|
|
|
|
|
// already, and the Move must be valid.
|
|
|
|
|
//
|
|
|
|
|
// Return bool will be true if all goes OK.
|
2024-04-26 13:50:46 +01:00
|
|
|
func (u *utils) redirectFollowers(
|
2024-03-13 13:53:29 +01:00
|
|
|
ctx context.Context,
|
|
|
|
|
originAcct *gtsmodel.Account,
|
|
|
|
|
targetAcct *gtsmodel.Account,
|
|
|
|
|
) bool {
|
|
|
|
|
// Any local followers of originAcct should
|
|
|
|
|
// send follow requests to targetAcct instead,
|
|
|
|
|
// and have followers of originAcct removed.
|
|
|
|
|
//
|
|
|
|
|
// Select local followers with barebones, since
|
|
|
|
|
// we only need follow.Account and we can get
|
|
|
|
|
// that ourselves.
|
|
|
|
|
followers, err := u.state.DB.GetAccountLocalFollowers(
|
|
|
|
|
gtscontext.SetBarebones(ctx),
|
|
|
|
|
originAcct.ID,
|
|
|
|
|
)
|
|
|
|
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
|
|
|
|
log.Errorf(ctx,
|
|
|
|
|
"db error getting follows targeting originAcct: %v",
|
|
|
|
|
err,
|
|
|
|
|
)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
for _, follow := range followers {
|
|
|
|
|
// Fetch the local account that
|
|
|
|
|
// owns the follow targeting originAcct.
|
|
|
|
|
if follow.Account, err = u.state.DB.GetAccountByID(
|
|
|
|
|
gtscontext.SetBarebones(ctx),
|
|
|
|
|
follow.AccountID,
|
|
|
|
|
); err != nil {
|
|
|
|
|
log.Errorf(ctx,
|
|
|
|
|
"db error getting follow account %s: %v",
|
|
|
|
|
follow.AccountID, err,
|
|
|
|
|
)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Use the account processor FollowCreate
|
|
|
|
|
// function to send off the new follow,
|
|
|
|
|
// carrying over the Reblogs and Notify
|
|
|
|
|
// values from the old follow to the new.
|
|
|
|
|
//
|
|
|
|
|
// This will also handle cases where our
|
|
|
|
|
// account has already followed the target
|
|
|
|
|
// account, by just updating the existing
|
|
|
|
|
// follow of target account.
|
|
|
|
|
//
|
|
|
|
|
// Also, ensure new follow wouldn't be a
|
|
|
|
|
// self follow, since that will error.
|
|
|
|
|
if follow.AccountID != targetAcct.ID {
|
|
|
|
|
if _, err := u.account.FollowCreate(
|
|
|
|
|
ctx,
|
|
|
|
|
follow.Account,
|
|
|
|
|
&apimodel.AccountFollowRequest{
|
|
|
|
|
ID: targetAcct.ID,
|
|
|
|
|
Reblogs: follow.ShowReblogs,
|
|
|
|
|
Notify: follow.Notify,
|
|
|
|
|
},
|
|
|
|
|
); err != nil {
|
|
|
|
|
log.Errorf(ctx,
|
|
|
|
|
"error creating new follow for account %s: %v",
|
|
|
|
|
follow.AccountID, err,
|
|
|
|
|
)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// New follow is in the process of
|
|
|
|
|
// sending, remove the existing follow.
|
|
|
|
|
// This will send out an Undo Activity for each Follow.
|
|
|
|
|
if _, err := u.account.FollowRemove(
|
|
|
|
|
ctx,
|
|
|
|
|
follow.Account,
|
|
|
|
|
follow.TargetAccountID,
|
|
|
|
|
); err != nil {
|
|
|
|
|
log.Errorf(ctx,
|
|
|
|
|
"error removing old follow for account %s: %v",
|
|
|
|
|
follow.AccountID, err,
|
|
|
|
|
)
|
|
|
|
|
return false
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return true
|
|
|
|
|
}
|
2024-04-16 13:10:13 +02:00
|
|
|
|
2024-04-26 13:50:46 +01:00
|
|
|
func (u *utils) incrementStatusesCount(
|
2024-04-16 13:10:13 +02:00
|
|
|
ctx context.Context,
|
|
|
|
|
account *gtsmodel.Account,
|
|
|
|
|
status *gtsmodel.Status,
|
|
|
|
|
) error {
|
|
|
|
|
// Lock on this account since we're changing stats.
|
2024-05-02 14:43:00 +02:00
|
|
|
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
2024-04-16 13:10:13 +02:00
|
|
|
defer unlock()
|
|
|
|
|
|
2024-08-11 09:23:36 +00:00
|
|
|
// Ensure account stats are populated.
|
2024-08-02 12:15:11 +00:00
|
|
|
if err := u.state.DB.PopulateAccountStats(ctx, account); err != nil {
|
|
|
|
|
return gtserror.Newf("db error getting account stats: %w", err)
|
2024-04-16 13:10:13 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-11 09:23:36 +00:00
|
|
|
// Update status meta for account.
|
2024-04-16 13:10:13 +02:00
|
|
|
*account.Stats.StatusesCount++
|
|
|
|
|
account.Stats.LastStatusAt = status.CreatedAt
|
2024-08-11 09:23:36 +00:00
|
|
|
|
|
|
|
|
// Update details in the database for stats.
|
|
|
|
|
if err := u.state.DB.UpdateAccountStats(ctx,
|
2024-04-16 13:10:13 +02:00
|
|
|
account.Stats,
|
|
|
|
|
"statuses_count",
|
|
|
|
|
"last_status_at",
|
|
|
|
|
); err != nil {
|
|
|
|
|
return gtserror.Newf("db error updating account stats: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 13:50:46 +01:00
|
|
|
func (u *utils) decrementStatusesCount(
|
2024-04-16 13:10:13 +02:00
|
|
|
ctx context.Context,
|
|
|
|
|
account *gtsmodel.Account,
|
2024-08-11 09:23:36 +00:00
|
|
|
status *gtsmodel.Status,
|
2024-04-16 13:10:13 +02:00
|
|
|
) error {
|
|
|
|
|
// Lock on this account since we're changing stats.
|
2024-05-02 14:43:00 +02:00
|
|
|
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
2024-04-16 13:10:13 +02:00
|
|
|
defer unlock()
|
|
|
|
|
|
2024-08-11 09:23:36 +00:00
|
|
|
// Ensure account stats are populated.
|
2024-08-02 12:15:11 +00:00
|
|
|
if err := u.state.DB.PopulateAccountStats(ctx, account); err != nil {
|
|
|
|
|
return gtserror.Newf("db error getting account stats: %w", err)
|
2024-04-16 13:10:13 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-11 09:23:36 +00:00
|
|
|
// Update status meta for account (safely checking for zero value).
|
|
|
|
|
*account.Stats.StatusesCount = util.Decr(*account.Stats.StatusesCount)
|
|
|
|
|
|
|
|
|
|
if !status.PinnedAt.IsZero() {
|
|
|
|
|
// Update status pinned count for account (safely checking for zero value).
|
|
|
|
|
*account.Stats.StatusesPinnedCount = util.Decr(*account.Stats.StatusesPinnedCount)
|
2024-04-16 13:10:13 +02:00
|
|
|
}
|
2024-08-11 09:23:36 +00:00
|
|
|
|
|
|
|
|
// Update details in the database for stats.
|
|
|
|
|
if err := u.state.DB.UpdateAccountStats(ctx,
|
2024-04-16 13:10:13 +02:00
|
|
|
account.Stats,
|
|
|
|
|
"statuses_count",
|
2024-08-11 09:23:36 +00:00
|
|
|
"statuses_pinned_count",
|
2024-04-16 13:10:13 +02:00
|
|
|
); err != nil {
|
|
|
|
|
return gtserror.Newf("db error updating account stats: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 13:50:46 +01:00
|
|
|
func (u *utils) incrementFollowersCount(
|
2024-04-16 13:10:13 +02:00
|
|
|
ctx context.Context,
|
|
|
|
|
account *gtsmodel.Account,
|
|
|
|
|
) error {
|
|
|
|
|
// Lock on this account since we're changing stats.
|
2024-05-02 14:43:00 +02:00
|
|
|
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
2024-04-16 13:10:13 +02:00
|
|
|
defer unlock()
|
|
|
|
|
|
2024-08-11 09:23:36 +00:00
|
|
|
// Ensure account stats are populated.
|
2024-08-02 12:15:11 +00:00
|
|
|
if err := u.state.DB.PopulateAccountStats(ctx, account); err != nil {
|
|
|
|
|
return gtserror.Newf("db error getting account stats: %w", err)
|
2024-04-16 13:10:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update stats by incrementing followers
|
|
|
|
|
// count by one and setting last posted.
|
|
|
|
|
*account.Stats.FollowersCount++
|
|
|
|
|
if err := u.state.DB.UpdateAccountStats(
|
|
|
|
|
ctx,
|
|
|
|
|
account.Stats,
|
|
|
|
|
"followers_count",
|
|
|
|
|
); err != nil {
|
|
|
|
|
return gtserror.Newf("db error updating account stats: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 13:50:46 +01:00
|
|
|
func (u *utils) decrementFollowersCount(
|
2024-04-16 13:10:13 +02:00
|
|
|
ctx context.Context,
|
|
|
|
|
account *gtsmodel.Account,
|
|
|
|
|
) error {
|
|
|
|
|
// Lock on this account since we're changing stats.
|
2024-05-02 14:43:00 +02:00
|
|
|
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
2024-04-16 13:10:13 +02:00
|
|
|
defer unlock()
|
|
|
|
|
|
2024-08-11 09:23:36 +00:00
|
|
|
// Ensure account stats are populated.
|
2024-08-02 12:15:11 +00:00
|
|
|
if err := u.state.DB.PopulateAccountStats(ctx, account); err != nil {
|
|
|
|
|
return gtserror.Newf("db error getting account stats: %w", err)
|
2024-04-16 13:10:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update stats by decrementing
|
|
|
|
|
// followers count by one.
|
|
|
|
|
//
|
|
|
|
|
// Clamp to 0 to avoid funny business.
|
|
|
|
|
*account.Stats.FollowersCount--
|
|
|
|
|
if *account.Stats.FollowersCount < 0 {
|
|
|
|
|
*account.Stats.FollowersCount = 0
|
|
|
|
|
}
|
|
|
|
|
if err := u.state.DB.UpdateAccountStats(
|
|
|
|
|
ctx,
|
|
|
|
|
account.Stats,
|
|
|
|
|
"followers_count",
|
|
|
|
|
); err != nil {
|
|
|
|
|
return gtserror.Newf("db error updating account stats: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 13:50:46 +01:00
|
|
|
func (u *utils) incrementFollowingCount(
|
2024-04-16 13:10:13 +02:00
|
|
|
ctx context.Context,
|
|
|
|
|
account *gtsmodel.Account,
|
|
|
|
|
) error {
|
|
|
|
|
// Lock on this account since we're changing stats.
|
2024-05-02 14:43:00 +02:00
|
|
|
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
2024-04-16 13:10:13 +02:00
|
|
|
defer unlock()
|
|
|
|
|
|
2024-08-11 09:23:36 +00:00
|
|
|
// Ensure account stats are populated.
|
2024-08-02 12:15:11 +00:00
|
|
|
if err := u.state.DB.PopulateAccountStats(ctx, account); err != nil {
|
|
|
|
|
return gtserror.Newf("db error getting account stats: %w", err)
|
2024-04-16 13:10:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update stats by incrementing
|
|
|
|
|
// followers count by one.
|
|
|
|
|
*account.Stats.FollowingCount++
|
|
|
|
|
if err := u.state.DB.UpdateAccountStats(
|
|
|
|
|
ctx,
|
|
|
|
|
account.Stats,
|
|
|
|
|
"following_count",
|
|
|
|
|
); err != nil {
|
|
|
|
|
return gtserror.Newf("db error updating account stats: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 13:50:46 +01:00
|
|
|
func (u *utils) decrementFollowingCount(
|
2024-04-16 13:10:13 +02:00
|
|
|
ctx context.Context,
|
|
|
|
|
account *gtsmodel.Account,
|
|
|
|
|
) error {
|
|
|
|
|
// Lock on this account since we're changing stats.
|
2024-05-02 14:43:00 +02:00
|
|
|
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
2024-04-16 13:10:13 +02:00
|
|
|
defer unlock()
|
|
|
|
|
|
2024-08-11 09:23:36 +00:00
|
|
|
// Ensure account stats are populated.
|
2024-08-02 12:15:11 +00:00
|
|
|
if err := u.state.DB.PopulateAccountStats(ctx, account); err != nil {
|
|
|
|
|
return gtserror.Newf("db error getting account stats: %w", err)
|
2024-04-16 13:10:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update stats by decrementing
|
|
|
|
|
// following count by one.
|
|
|
|
|
//
|
|
|
|
|
// Clamp to 0 to avoid funny business.
|
|
|
|
|
*account.Stats.FollowingCount--
|
|
|
|
|
if *account.Stats.FollowingCount < 0 {
|
|
|
|
|
*account.Stats.FollowingCount = 0
|
|
|
|
|
}
|
|
|
|
|
if err := u.state.DB.UpdateAccountStats(
|
|
|
|
|
ctx,
|
|
|
|
|
account.Stats,
|
|
|
|
|
"following_count",
|
|
|
|
|
); err != nil {
|
|
|
|
|
return gtserror.Newf("db error updating account stats: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 13:50:46 +01:00
|
|
|
func (u *utils) incrementFollowRequestsCount(
|
2024-04-16 13:10:13 +02:00
|
|
|
ctx context.Context,
|
|
|
|
|
account *gtsmodel.Account,
|
|
|
|
|
) error {
|
|
|
|
|
// Lock on this account since we're changing stats.
|
2024-05-02 14:43:00 +02:00
|
|
|
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
2024-04-16 13:10:13 +02:00
|
|
|
defer unlock()
|
|
|
|
|
|
2024-08-11 09:23:36 +00:00
|
|
|
// Ensure account stats are populated.
|
2024-08-02 12:15:11 +00:00
|
|
|
if err := u.state.DB.PopulateAccountStats(ctx, account); err != nil {
|
|
|
|
|
return gtserror.Newf("db error getting account stats: %w", err)
|
2024-04-16 13:10:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update stats by incrementing
|
|
|
|
|
// follow requests count by one.
|
|
|
|
|
*account.Stats.FollowRequestsCount++
|
|
|
|
|
if err := u.state.DB.UpdateAccountStats(
|
|
|
|
|
ctx,
|
|
|
|
|
account.Stats,
|
|
|
|
|
"follow_requests_count",
|
|
|
|
|
); err != nil {
|
|
|
|
|
return gtserror.Newf("db error updating account stats: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
2024-04-26 13:50:46 +01:00
|
|
|
func (u *utils) decrementFollowRequestsCount(
|
2024-04-16 13:10:13 +02:00
|
|
|
ctx context.Context,
|
|
|
|
|
account *gtsmodel.Account,
|
|
|
|
|
) error {
|
|
|
|
|
// Lock on this account since we're changing stats.
|
2024-05-02 14:43:00 +02:00
|
|
|
unlock := u.state.ProcessingLocks.Lock(account.URI)
|
2024-04-16 13:10:13 +02:00
|
|
|
defer unlock()
|
|
|
|
|
|
2024-08-11 09:23:36 +00:00
|
|
|
// Ensure account stats are populated.
|
2024-08-02 12:15:11 +00:00
|
|
|
if err := u.state.DB.PopulateAccountStats(ctx, account); err != nil {
|
|
|
|
|
return gtserror.Newf("db error getting account stats: %w", err)
|
2024-04-16 13:10:13 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Update stats by decrementing
|
|
|
|
|
// follow requests count by one.
|
|
|
|
|
//
|
|
|
|
|
// Clamp to 0 to avoid funny business.
|
|
|
|
|
*account.Stats.FollowRequestsCount--
|
|
|
|
|
if *account.Stats.FollowRequestsCount < 0 {
|
|
|
|
|
*account.Stats.FollowRequestsCount = 0
|
|
|
|
|
}
|
|
|
|
|
if err := u.state.DB.UpdateAccountStats(
|
|
|
|
|
ctx,
|
|
|
|
|
account.Stats,
|
|
|
|
|
"follow_requests_count",
|
|
|
|
|
); err != nil {
|
|
|
|
|
return gtserror.Newf("db error updating account stats: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2024-07-26 12:04:28 +02:00
|
|
|
|
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394)
~~Still WIP!~~
This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0.
Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them.
As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type.
Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation).
Migrations tested on both Postgres and SQLite.
Co-authored-by: kim <grufwub@gmail.com>
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394
Co-authored-by: tobi <tobi.smethurst@protonmail.com>
Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-09-14 15:37:35 +02:00
|
|
|
// impoliteFaveRequest stores an interaction request
|
2024-08-24 11:49:37 +02:00
|
|
|
// for the given fave, and notifies the interactee.
|
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394)
~~Still WIP!~~
This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0.
Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them.
As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type.
Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation).
Migrations tested on both Postgres and SQLite.
Co-authored-by: kim <grufwub@gmail.com>
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394
Co-authored-by: tobi <tobi.smethurst@protonmail.com>
Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-09-14 15:37:35 +02:00
|
|
|
//
|
|
|
|
|
// It should be used only when an actor has sent a Like
|
|
|
|
|
// directly in response to a post that requires approval
|
|
|
|
|
// for it, instead of sending a LikeRequest.
|
|
|
|
|
func (u *utils) impoliteFaveRequest(
|
2024-07-26 12:04:28 +02:00
|
|
|
ctx context.Context,
|
|
|
|
|
fave *gtsmodel.StatusFave,
|
2024-08-24 11:49:37 +02:00
|
|
|
) error {
|
|
|
|
|
// Only create interaction request
|
|
|
|
|
// if fave targets a local status.
|
|
|
|
|
if fave.Status == nil ||
|
|
|
|
|
!fave.Status.IsLocal() {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2024-07-26 12:04:28 +02:00
|
|
|
|
2024-08-24 11:49:37 +02:00
|
|
|
// 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)
|
2024-07-26 12:04:28 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-24 11:49:37 +02:00
|
|
|
if req != nil {
|
|
|
|
|
// Interaction req already exists,
|
|
|
|
|
// no need to do anything else.
|
|
|
|
|
return nil
|
2024-07-26 12:04:28 +02:00
|
|
|
}
|
|
|
|
|
|
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394)
~~Still WIP!~~
This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0.
Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them.
As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type.
Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation).
Migrations tested on both Postgres and SQLite.
Co-authored-by: kim <grufwub@gmail.com>
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394
Co-authored-by: tobi <tobi.smethurst@protonmail.com>
Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-09-14 15:37:35 +02:00
|
|
|
// Create + store new impolite interaction request.
|
|
|
|
|
req = typeutils.StatusFaveToImpoliteInteractionRequest(fave)
|
2024-08-24 11:49:37 +02:00
|
|
|
if err := u.state.DB.PutInteractionRequest(ctx, req); err != nil {
|
|
|
|
|
return gtserror.Newf("db error storing interaction request: %w", err)
|
2024-07-26 12:04:28 +02:00
|
|
|
}
|
|
|
|
|
|
2025-07-14 17:12:47 +02:00
|
|
|
// Notify *local* account of pending fave.
|
2024-08-24 11:49:37 +02:00
|
|
|
if err := u.surface.notifyPendingFave(ctx, fave); err != nil {
|
|
|
|
|
return gtserror.Newf("error notifying pending fave: %w", err)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
return nil
|
2024-07-26 12:04:28 +02:00
|
|
|
}
|
|
|
|
|
|
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394)
~~Still WIP!~~
This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0.
Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them.
As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type.
Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation).
Migrations tested on both Postgres and SQLite.
Co-authored-by: kim <grufwub@gmail.com>
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394
Co-authored-by: tobi <tobi.smethurst@protonmail.com>
Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-09-14 15:37:35 +02:00
|
|
|
// impoliteReplyRequest stores an interaction request
|
2024-08-24 11:49:37 +02:00
|
|
|
// for the given reply, and notifies the interactee.
|
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394)
~~Still WIP!~~
This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0.
Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them.
As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type.
Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation).
Migrations tested on both Postgres and SQLite.
Co-authored-by: kim <grufwub@gmail.com>
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394
Co-authored-by: tobi <tobi.smethurst@protonmail.com>
Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-09-14 15:37:35 +02:00
|
|
|
//
|
|
|
|
|
// It should be used only when an actor has sent a reply
|
|
|
|
|
// directly in response to a post that requires approval
|
|
|
|
|
// for it, instead of sending a ReplyRequest.
|
|
|
|
|
func (u *utils) impoliteReplyRequest(
|
2024-07-26 12:04:28 +02:00
|
|
|
ctx context.Context,
|
2024-08-24 11:49:37 +02:00
|
|
|
reply *gtsmodel.Status,
|
|
|
|
|
) error {
|
|
|
|
|
// Only create interaction request if
|
|
|
|
|
// status replies to a local status.
|
|
|
|
|
if reply.InReplyTo == nil ||
|
|
|
|
|
!reply.InReplyTo.IsLocal() {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
2024-07-26 12:04:28 +02:00
|
|
|
|
2024-08-24 11:49:37 +02:00
|
|
|
// 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)
|
2024-07-26 12:04:28 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-24 11:49:37 +02:00
|
|
|
if req != nil {
|
|
|
|
|
// Interaction req already exists,
|
|
|
|
|
// no need to do anything else.
|
|
|
|
|
return nil
|
2024-07-26 12:04:28 +02:00
|
|
|
}
|
|
|
|
|
|
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394)
~~Still WIP!~~
This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0.
Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them.
As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type.
Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation).
Migrations tested on both Postgres and SQLite.
Co-authored-by: kim <grufwub@gmail.com>
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394
Co-authored-by: tobi <tobi.smethurst@protonmail.com>
Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-09-14 15:37:35 +02:00
|
|
|
// Create + store impolite interaction request.
|
|
|
|
|
req = typeutils.StatusToImpoliteInteractionRequest(reply)
|
2024-08-24 11:49:37 +02:00
|
|
|
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)
|
2024-07-26 12:04:28 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-24 11:49:37 +02:00
|
|
|
return nil
|
2024-07-26 12:04:28 +02:00
|
|
|
}
|
|
|
|
|
|
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394)
~~Still WIP!~~
This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0.
Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them.
As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type.
Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation).
Migrations tested on both Postgres and SQLite.
Co-authored-by: kim <grufwub@gmail.com>
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394
Co-authored-by: tobi <tobi.smethurst@protonmail.com>
Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-09-14 15:37:35 +02:00
|
|
|
// impoliteAnnounceRequest stores an interaction request
|
2024-08-24 11:49:37 +02:00
|
|
|
// for the given announce, and notifies the interactee.
|
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394)
~~Still WIP!~~
This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0.
Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them.
As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type.
Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation).
Migrations tested on both Postgres and SQLite.
Co-authored-by: kim <grufwub@gmail.com>
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394
Co-authored-by: tobi <tobi.smethurst@protonmail.com>
Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-09-14 15:37:35 +02:00
|
|
|
//
|
|
|
|
|
// It should be used only when an actor has sent an Announce
|
|
|
|
|
// directly in response to a post that requires approval
|
|
|
|
|
// for it, instead of sending an AnnounceRequest.
|
|
|
|
|
func (u *utils) impoliteAnnounceRequest(
|
2024-07-26 12:04:28 +02:00
|
|
|
ctx context.Context,
|
|
|
|
|
boost *gtsmodel.Status,
|
2024-08-24 11:49:37 +02:00
|
|
|
) error {
|
|
|
|
|
// Only create interaction request if
|
|
|
|
|
// status announces a local status.
|
|
|
|
|
if boost.BoostOf == nil ||
|
|
|
|
|
!boost.BoostOf.IsLocal() {
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Lock on the interaction URI.
|
|
|
|
|
unlock := u.state.ProcessingLocks.Lock(boost.URI)
|
|
|
|
|
defer unlock()
|
2024-07-26 12:04:28 +02:00
|
|
|
|
2024-08-24 11:49:37 +02:00
|
|
|
// 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)
|
2024-07-26 12:04:28 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-24 11:49:37 +02:00
|
|
|
if req != nil {
|
|
|
|
|
// Interaction req already exists,
|
|
|
|
|
// no need to do anything else.
|
|
|
|
|
return nil
|
2024-07-26 12:04:28 +02:00
|
|
|
}
|
|
|
|
|
|
[feature] Support new model of interaction flow for forward compat with v0.21.0 (#4394)
~~Still WIP!~~
This PR allows v0.20.0 of GtS to be forward-compatible with the interaction request / authorization flow that will fully replace the current flow in v0.21.0.
Basically, this means we need to recognize LikeRequest, ReplyRequest, and AnnounceRequest, and in response to those requests, deliver either a Reject or an Accept, with the latter pointing towards a LikeAuthorization, ReplyAuthorization, or AnnounceAuthorization, respectively. This can then be used by the remote instance to prove to third parties that the interaction has been accepted by the interactee. These Authorization types need to be dereferencable to third parties, so we need to serve them.
As well as recognizing the above "polite" interaction request types, we also need to still serve appropriate responses to "impolite" interaction request types, where an instance that's unaware of interaction policies tries to interact with a post by sending a reply, like, or boost directly, without wrapping it in a WhateverRequest type.
Doesn't fully close https://codeberg.org/superseriousbusiness/gotosocial/issues/4026 but gets damn near (just gotta update the federating with GtS documentation).
Migrations tested on both Postgres and SQLite.
Co-authored-by: kim <grufwub@gmail.com>
Reviewed-on: https://codeberg.org/superseriousbusiness/gotosocial/pulls/4394
Co-authored-by: tobi <tobi.smethurst@protonmail.com>
Co-committed-by: tobi <tobi.smethurst@protonmail.com>
2025-09-14 15:37:35 +02:00
|
|
|
// Create + store impolite interaction request.
|
|
|
|
|
req = typeutils.StatusToImpoliteInteractionRequest(boost)
|
2024-08-24 11:49:37 +02:00
|
|
|
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)
|
2024-07-26 12:04:28 +02:00
|
|
|
}
|
|
|
|
|
|
2024-08-24 11:49:37 +02:00
|
|
|
return nil
|
2024-07-26 12:04:28 +02:00
|
|
|
}
|