2023-03-12 15:00:57 +00: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/>.
|
2021-05-27 14:06:24 +00:00
|
|
|
|
|
|
|
package federatingdb
|
|
|
|
|
|
|
|
import (
|
|
|
|
"context"
|
|
|
|
"errors"
|
|
|
|
"fmt"
|
2023-10-31 11:05:17 +00:00
|
|
|
"strings"
|
2021-05-27 14:06:24 +00:00
|
|
|
|
2022-07-19 08:47:55 +00:00
|
|
|
"codeberg.org/gruf/go-logger/v2/level"
|
2023-08-08 11:26:34 +00:00
|
|
|
"github.com/superseriousbusiness/activity/pub"
|
2021-11-13 16:29:43 +00:00
|
|
|
"github.com/superseriousbusiness/activity/streams/vocab"
|
2021-08-31 13:59:12 +00:00
|
|
|
"github.com/superseriousbusiness/gotosocial/internal/ap"
|
2023-10-31 11:05:17 +00:00
|
|
|
"github.com/superseriousbusiness/gotosocial/internal/config"
|
2021-05-27 14:06:24 +00:00
|
|
|
"github.com/superseriousbusiness/gotosocial/internal/db"
|
2023-08-08 11:26:34 +00:00
|
|
|
"github.com/superseriousbusiness/gotosocial/internal/gtserror"
|
2021-10-10 10:39:25 +00:00
|
|
|
"github.com/superseriousbusiness/gotosocial/internal/gtsmodel"
|
2021-06-13 16:42:28 +00:00
|
|
|
"github.com/superseriousbusiness/gotosocial/internal/id"
|
2022-07-19 08:47:55 +00:00
|
|
|
"github.com/superseriousbusiness/gotosocial/internal/log"
|
2021-08-31 13:59:12 +00:00
|
|
|
"github.com/superseriousbusiness/gotosocial/internal/messages"
|
2023-10-31 11:05:17 +00:00
|
|
|
"github.com/superseriousbusiness/gotosocial/internal/util"
|
2021-05-27 14:06:24 +00:00
|
|
|
)
|
|
|
|
|
|
|
|
// Create adds a new entry to the database which must be able to be
|
|
|
|
// keyed by its id.
|
|
|
|
//
|
|
|
|
// Note that Activity values received from federated peers may also be
|
|
|
|
// created in the database this way if the Federating Protocol is
|
|
|
|
// enabled. The client may freely decide to store only the id instead of
|
|
|
|
// the entire value.
|
|
|
|
//
|
|
|
|
// The library makes this call only after acquiring a lock first.
|
|
|
|
//
|
|
|
|
// Under certain conditions and network activities, Create may be called
|
|
|
|
// multiple times for the same ActivityStreams object.
|
|
|
|
func (f *federatingDB) Create(ctx context.Context, asType vocab.Type) error {
|
2023-08-08 11:26:34 +00:00
|
|
|
if log.Level() >= level.TRACE {
|
2021-10-04 13:24:19 +00:00
|
|
|
i, err := marshalItem(asType)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2023-08-08 11:26:34 +00:00
|
|
|
|
|
|
|
log.
|
|
|
|
WithContext(ctx).
|
|
|
|
WithField("create", i).
|
|
|
|
Trace("entering Create")
|
2021-05-27 14:06:24 +00:00
|
|
|
}
|
2021-10-04 13:24:19 +00:00
|
|
|
|
2023-06-13 14:47:56 +00:00
|
|
|
receivingAccount, requestingAccount, internal := extractFromCtx(ctx)
|
|
|
|
if internal {
|
|
|
|
return nil // Already processed.
|
2021-05-27 14:06:24 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
switch asType.GetTypeName() {
|
2021-10-10 10:39:25 +00:00
|
|
|
case ap.ActivityBlock:
|
|
|
|
// BLOCK SOMETHING
|
2022-04-28 12:23:11 +00:00
|
|
|
return f.activityBlock(ctx, asType, receivingAccount, requestingAccount)
|
2021-08-31 13:59:12 +00:00
|
|
|
case ap.ActivityCreate:
|
2021-06-17 16:02:33 +00:00
|
|
|
// CREATE SOMETHING
|
2022-04-28 12:23:11 +00:00
|
|
|
return f.activityCreate(ctx, asType, receivingAccount, requestingAccount)
|
2021-08-31 13:59:12 +00:00
|
|
|
case ap.ActivityFollow:
|
2021-07-11 14:22:21 +00:00
|
|
|
// FOLLOW SOMETHING
|
2022-04-28 12:23:11 +00:00
|
|
|
return f.activityFollow(ctx, asType, receivingAccount, requestingAccount)
|
2021-10-10 10:39:25 +00:00
|
|
|
case ap.ActivityLike:
|
|
|
|
// LIKE SOMETHING
|
2022-04-28 12:23:11 +00:00
|
|
|
return f.activityLike(ctx, asType, receivingAccount, requestingAccount)
|
2023-01-25 10:12:27 +00:00
|
|
|
case ap.ActivityFlag:
|
|
|
|
// FLAG / REPORT SOMETHING
|
|
|
|
return f.activityFlag(ctx, asType, receivingAccount, requestingAccount)
|
2021-10-10 10:39:25 +00:00
|
|
|
}
|
2023-10-04 12:09:42 +00:00
|
|
|
|
2021-10-10 10:39:25 +00:00
|
|
|
return nil
|
|
|
|
}
|
2021-05-27 14:06:24 +00:00
|
|
|
|
2021-10-10 10:39:25 +00:00
|
|
|
/*
|
|
|
|
BLOCK HANDLERS
|
|
|
|
*/
|
2021-05-27 14:06:24 +00:00
|
|
|
|
2022-04-28 12:23:11 +00:00
|
|
|
func (f *federatingDB) activityBlock(ctx context.Context, asType vocab.Type, receiving *gtsmodel.Account, requestingAccount *gtsmodel.Account) error {
|
2021-10-10 10:39:25 +00:00
|
|
|
blockable, ok := asType.(vocab.ActivityStreamsBlock)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("activityBlock: could not convert type to block")
|
|
|
|
}
|
2021-06-13 16:42:28 +00:00
|
|
|
|
2023-09-23 16:44:11 +00:00
|
|
|
block, err := f.converter.ASBlockToBlock(ctx, blockable)
|
2021-10-10 10:39:25 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("activityBlock: could not convert Block to gts model block")
|
|
|
|
}
|
2021-05-27 14:06:24 +00:00
|
|
|
|
2023-02-03 20:03:05 +00:00
|
|
|
block.ID = id.NewULID()
|
2021-05-27 14:06:24 +00:00
|
|
|
|
2023-03-01 18:26:53 +00:00
|
|
|
if err := f.state.DB.PutBlock(ctx, block); err != nil {
|
2021-10-10 10:39:25 +00:00
|
|
|
return fmt.Errorf("activityBlock: database error inserting block: %s", err)
|
|
|
|
}
|
|
|
|
|
2023-08-09 17:14:33 +00:00
|
|
|
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
2021-10-10 10:39:25 +00:00
|
|
|
APObjectType: ap.ActivityBlock,
|
|
|
|
APActivityType: ap.ActivityCreate,
|
|
|
|
GTSModel: block,
|
|
|
|
ReceivingAccount: receiving,
|
2022-04-28 12:23:11 +00:00
|
|
|
})
|
2023-10-04 12:09:42 +00:00
|
|
|
|
2021-10-10 10:39:25 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
CREATE HANDLERS
|
|
|
|
*/
|
|
|
|
|
2023-08-08 11:26:34 +00:00
|
|
|
// activityCreate handles asType Create by checking
|
|
|
|
// the Object entries of the Create and calling other
|
|
|
|
// handlers as appropriate.
|
|
|
|
func (f *federatingDB) activityCreate(
|
|
|
|
ctx context.Context,
|
|
|
|
asType vocab.Type,
|
|
|
|
receivingAccount *gtsmodel.Account,
|
|
|
|
requestingAccount *gtsmodel.Account,
|
|
|
|
) error {
|
2021-10-10 10:39:25 +00:00
|
|
|
create, ok := asType.(vocab.ActivityStreamsCreate)
|
|
|
|
if !ok {
|
2023-08-08 11:26:34 +00:00
|
|
|
return gtserror.Newf("could not convert asType %T to ActivityStreamsCreate", asType)
|
2021-10-10 10:39:25 +00:00
|
|
|
}
|
|
|
|
|
2023-10-04 12:09:42 +00:00
|
|
|
for _, object := range ap.ExtractObjects(create) {
|
|
|
|
// Try to get object as vocab.Type,
|
|
|
|
// else skip handling (likely) IRI.
|
|
|
|
objType := object.GetType()
|
|
|
|
if objType == nil {
|
2021-10-10 10:39:25 +00:00
|
|
|
continue
|
2021-05-27 14:06:24 +00:00
|
|
|
}
|
|
|
|
|
2023-10-04 12:09:42 +00:00
|
|
|
if statusable, ok := ap.ToStatusable(objType); ok {
|
|
|
|
return f.createStatusable(ctx, statusable, receivingAccount, requestingAccount)
|
2021-06-13 16:42:28 +00:00
|
|
|
}
|
2021-10-10 10:39:25 +00:00
|
|
|
|
2023-10-04 12:09:42 +00:00
|
|
|
// TODO: handle CREATE of other types?
|
2021-10-10 10:39:25 +00:00
|
|
|
}
|
2021-06-13 16:42:28 +00:00
|
|
|
|
2021-10-10 10:39:25 +00:00
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-08-08 11:26:34 +00:00
|
|
|
// createStatusable handles a Create activity for a Statusable.
|
|
|
|
// This function won't insert anything in the database yet,
|
|
|
|
// but will pass the Statusable (if appropriate) through to
|
|
|
|
// the processor for further asynchronous processing.
|
|
|
|
func (f *federatingDB) createStatusable(
|
|
|
|
ctx context.Context,
|
|
|
|
statusable ap.Statusable,
|
|
|
|
receivingAccount *gtsmodel.Account,
|
|
|
|
requestingAccount *gtsmodel.Account,
|
|
|
|
) error {
|
|
|
|
// Statusable must have an attributedTo.
|
|
|
|
attrToProp := statusable.GetActivityStreamsAttributedTo()
|
|
|
|
if attrToProp == nil {
|
|
|
|
return gtserror.Newf("statusable had no attributedTo")
|
|
|
|
}
|
2021-10-10 10:39:25 +00:00
|
|
|
|
2023-08-08 11:26:34 +00:00
|
|
|
// Statusable must have an ID.
|
|
|
|
idProp := statusable.GetJSONLDId()
|
|
|
|
if idProp == nil || !idProp.IsIRI() {
|
|
|
|
return gtserror.Newf("statusable had no id, or id was not a URI")
|
2021-10-10 10:39:25 +00:00
|
|
|
}
|
|
|
|
|
2023-08-08 11:26:34 +00:00
|
|
|
statusableURI := idProp.GetIRI()
|
|
|
|
|
|
|
|
// Check if we have a forward. In other words, was the
|
|
|
|
// statusable posted to our inbox by at least one actor
|
|
|
|
// who actually created it, or are they forwarding it?
|
|
|
|
forward := true
|
|
|
|
for iter := attrToProp.Begin(); iter != attrToProp.End(); iter = iter.Next() {
|
|
|
|
actorURI, err := pub.ToId(iter)
|
|
|
|
if err != nil {
|
|
|
|
return gtserror.Newf("error extracting id from attributedTo entry: %w", err)
|
2021-10-10 10:39:25 +00:00
|
|
|
}
|
2023-08-08 11:26:34 +00:00
|
|
|
|
|
|
|
if requestingAccount.URI == actorURI.String() {
|
|
|
|
// The actor who posted this statusable to our inbox is
|
|
|
|
// (one of) its creator(s), so this is not a forward.
|
2021-10-10 10:39:25 +00:00
|
|
|
forward = false
|
2023-08-08 11:26:34 +00:00
|
|
|
break
|
2021-05-27 14:06:24 +00:00
|
|
|
}
|
2021-10-10 10:39:25 +00:00
|
|
|
}
|
2021-05-27 14:06:24 +00:00
|
|
|
|
2023-08-08 11:26:34 +00:00
|
|
|
// Check if we already have a status entry
|
|
|
|
// for this statusable, based on the ID/URI.
|
|
|
|
statusableURIStr := statusableURI.String()
|
|
|
|
status, err := f.state.DB.GetStatusByURI(ctx, statusableURIStr)
|
|
|
|
if err != nil && !errors.Is(err, db.ErrNoEntries) {
|
|
|
|
return gtserror.Newf("db error checking existence of status %s: %w", statusableURIStr, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if status != nil {
|
|
|
|
// We already had this status in the db, no need for further action.
|
|
|
|
log.Trace(ctx, "status already exists: %s", statusableURIStr)
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// If we do have a forward, we should ignore the content
|
|
|
|
// and instead deref based on the URI of the statusable.
|
|
|
|
//
|
|
|
|
// In other words, don't automatically trust whoever sent
|
|
|
|
// this status to us, but fetch the authentic article from
|
|
|
|
// the server it originated from.
|
2021-10-10 10:39:25 +00:00
|
|
|
if forward {
|
2023-08-08 11:26:34 +00:00
|
|
|
// Pass the statusable URI (APIri) into the processor worker
|
|
|
|
// and do the rest of the processing asynchronously.
|
2023-08-09 17:14:33 +00:00
|
|
|
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
2021-10-10 10:39:25 +00:00
|
|
|
APObjectType: ap.ObjectNote,
|
2021-08-31 13:59:12 +00:00
|
|
|
APActivityType: ap.ActivityCreate,
|
2023-08-08 11:26:34 +00:00
|
|
|
APIri: statusableURI,
|
2023-05-12 09:15:54 +00:00
|
|
|
APObjectModel: nil,
|
2021-10-10 10:39:25 +00:00
|
|
|
GTSModel: nil,
|
|
|
|
ReceivingAccount: receivingAccount,
|
2022-04-28 12:23:11 +00:00
|
|
|
})
|
2021-10-10 10:39:25 +00:00
|
|
|
return nil
|
|
|
|
}
|
2021-07-11 14:22:21 +00:00
|
|
|
|
2023-08-08 11:26:34 +00:00
|
|
|
// This is a non-forwarded status we can trust the requester on,
|
|
|
|
// convert this provided statusable data to a useable gtsmodel status.
|
2023-09-23 16:44:11 +00:00
|
|
|
status, err = f.converter.ASStatusToStatus(ctx, statusable)
|
2023-08-08 11:26:34 +00:00
|
|
|
if err != nil {
|
|
|
|
return gtserror.Newf("error converting statusable to status: %w", err)
|
|
|
|
}
|
2021-07-11 14:22:21 +00:00
|
|
|
|
2023-08-08 11:26:34 +00:00
|
|
|
// Check whether we should accept this new status.
|
|
|
|
accept, err := f.shouldAcceptStatusable(ctx,
|
|
|
|
receivingAccount,
|
|
|
|
requestingAccount,
|
|
|
|
status,
|
|
|
|
)
|
2021-10-10 10:39:25 +00:00
|
|
|
if err != nil {
|
2023-08-08 11:26:34 +00:00
|
|
|
return gtserror.Newf("error checking status acceptibility: %w", err)
|
2021-10-10 10:39:25 +00:00
|
|
|
}
|
2021-07-11 14:22:21 +00:00
|
|
|
|
2023-08-08 11:26:34 +00:00
|
|
|
if !accept {
|
|
|
|
// This is a status sent with no relation to receiver, i.e.
|
|
|
|
// - receiving account does not follow requesting account
|
|
|
|
// - received status does not mention receiving account
|
|
|
|
//
|
|
|
|
// We just pretend that all is fine (dog with cuppa, flames everywhere)
|
|
|
|
log.Trace(ctx, "status failed acceptability check")
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// ID the new status based on the time it was created.
|
|
|
|
status.ID, err = id.NewULIDFromTime(status.CreatedAt)
|
2021-10-10 10:39:25 +00:00
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
2021-07-11 14:22:21 +00:00
|
|
|
|
2023-08-08 11:26:34 +00:00
|
|
|
// Put this newly parsed status in the database.
|
2023-03-01 18:26:53 +00:00
|
|
|
if err := f.state.DB.PutStatus(ctx, status); err != nil {
|
2022-09-12 11:03:23 +00:00
|
|
|
if errors.Is(err, db.ErrAlreadyExists) {
|
2023-08-08 11:26:34 +00:00
|
|
|
// The status already exists in the database, which
|
|
|
|
// means we've already processed it and some race
|
|
|
|
// condition means we didn't catch it yet. We can
|
|
|
|
// just return nil here and be done with it.
|
2021-10-10 10:39:25 +00:00
|
|
|
return nil
|
2021-07-11 14:22:21 +00:00
|
|
|
}
|
2023-08-08 11:26:34 +00:00
|
|
|
return gtserror.Newf("db error inserting status: %w", err)
|
2021-05-27 14:06:24 +00:00
|
|
|
}
|
2021-10-10 10:39:25 +00:00
|
|
|
|
2023-08-08 11:26:34 +00:00
|
|
|
// Do the rest of the processing asynchronously. The processor
|
|
|
|
// will handle inserting/updating + further dereferencing the status.
|
2023-08-09 17:14:33 +00:00
|
|
|
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
2021-10-10 10:39:25 +00:00
|
|
|
APObjectType: ap.ObjectNote,
|
|
|
|
APActivityType: ap.ActivityCreate,
|
2023-08-08 11:26:34 +00:00
|
|
|
APIri: nil,
|
|
|
|
APObjectModel: statusable,
|
2021-10-10 10:39:25 +00:00
|
|
|
GTSModel: status,
|
|
|
|
ReceivingAccount: receivingAccount,
|
2022-04-28 12:23:11 +00:00
|
|
|
})
|
2021-10-10 10:39:25 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
2023-08-08 11:26:34 +00:00
|
|
|
func (f *federatingDB) shouldAcceptStatusable(ctx context.Context, receiver *gtsmodel.Account, requester *gtsmodel.Account, status *gtsmodel.Status) (bool, error) {
|
2023-10-31 11:05:17 +00:00
|
|
|
host := config.GetHost()
|
|
|
|
accountDomain := config.GetAccountDomain()
|
|
|
|
|
2023-08-08 11:26:34 +00:00
|
|
|
// Check whether status mentions the receiver,
|
|
|
|
// this is the quickest check so perform it first.
|
2023-10-31 11:05:17 +00:00
|
|
|
// Prefer checking using mention Href, fall back to Name.
|
2023-08-08 11:26:34 +00:00
|
|
|
for _, mention := range status.Mentions {
|
2023-10-31 11:05:17 +00:00
|
|
|
targetURI := mention.TargetAccountURI
|
|
|
|
nameString := mention.NameString
|
|
|
|
|
|
|
|
if targetURI != "" {
|
|
|
|
if targetURI == receiver.URI || targetURI == receiver.URL {
|
|
|
|
// Target URI or URL match;
|
|
|
|
// receiver is mentioned.
|
|
|
|
return true, nil
|
|
|
|
}
|
|
|
|
} else if nameString != "" {
|
|
|
|
username, domain, err := util.ExtractNamestringParts(nameString)
|
|
|
|
if err != nil {
|
|
|
|
return false, gtserror.Newf("error checking if mentioned: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if (domain == host || domain == accountDomain) &&
|
|
|
|
strings.EqualFold(username, receiver.Username) {
|
|
|
|
// Username + domain match;
|
|
|
|
// receiver is mentioned.
|
|
|
|
return true, nil
|
|
|
|
}
|
2023-08-08 11:26:34 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// Check whether receiving account follows the requesting account.
|
|
|
|
follows, err := f.state.DB.IsFollowing(ctx, receiver.ID, requester.ID)
|
|
|
|
if err != nil {
|
|
|
|
return false, gtserror.Newf("error checking follow status: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
// Status will only be acceptable
|
|
|
|
// if receiver follows requester.
|
|
|
|
return follows, nil
|
|
|
|
}
|
|
|
|
|
2021-10-10 10:39:25 +00:00
|
|
|
/*
|
|
|
|
FOLLOW HANDLERS
|
|
|
|
*/
|
|
|
|
|
2022-04-28 12:23:11 +00:00
|
|
|
func (f *federatingDB) activityFollow(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error {
|
2021-10-10 10:39:25 +00:00
|
|
|
follow, ok := asType.(vocab.ActivityStreamsFollow)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("activityFollow: could not convert type to follow")
|
|
|
|
}
|
|
|
|
|
2023-09-23 16:44:11 +00:00
|
|
|
followRequest, err := f.converter.ASFollowToFollowRequest(ctx, follow)
|
2021-10-10 10:39:25 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("activityFollow: could not convert Follow to follow request: %s", err)
|
|
|
|
}
|
|
|
|
|
2023-02-03 20:03:05 +00:00
|
|
|
followRequest.ID = id.NewULID()
|
2021-10-10 10:39:25 +00:00
|
|
|
|
[performance] refactoring + add fave / follow / request / visibility caching (#1607)
* refactor visibility checking, add caching for visibility
* invalidate visibility cache items on account / status deletes
* fix requester ID passed to visibility cache nil ptr
* de-interface caches, fix home / public timeline caching + visibility
* finish adding code comments for visibility filter
* fix angry goconst linter warnings
* actually finish adding filter visibility code comments for timeline functions
* move home timeline status author check to after visibility
* remove now-unused code
* add more code comments
* add TODO code comment, update printed cache start names
* update printed cache names on stop
* start adding separate follow(request) delete db functions, add specific visibility cache tests
* add relationship type caching
* fix getting local account follows / followed-bys, other small codebase improvements
* simplify invalidation using cache hooks, add more GetAccountBy___() functions
* fix boosting to return 404 if not boostable but no error (to not leak status ID)
* remove dead code
* improved placement of cache invalidation
* update license headers
* add example follow, follow-request config entries
* add example visibility cache configuration to config file
* use specific PutFollowRequest() instead of just Put()
* add tests for all GetAccountBy()
* add GetBlockBy() tests
* update block to check primitive fields
* update and finish adding Get{Account,Block,Follow,FollowRequest}By() tests
* fix copy-pasted code
* update envparsing test
* whitespace
* fix bun struct tag
* add license header to gtscontext
* fix old license header
* improved error creation to not use fmt.Errorf() when not needed
* fix various rebase conflicts, fix account test
* remove commented-out code, fix-up mention caching
* fix mention select bun statement
* ensure mention target account populated, pass in context to customrenderer logging
* remove more uncommented code, fix typeutil test
* add statusfave database model caching
* add status fave cache configuration
* add status fave cache example config
* woops, catch missed error. nice catch linter!
* add back testrig panic on nil db
* update example configuration to match defaults, slight tweak to cache configuration defaults
* update envparsing test with new defaults
* fetch followingget to use the follow target account
* use accounnt.IsLocal() instead of empty domain check
* use constants for the cache visibility type check
* use bun.In() for notification type restriction in db query
* include replies when fetching PublicTimeline() (to account for single-author threads in Visibility{}.StatusPublicTimelineable())
* use bun query building for nested select statements to ensure working with postgres
* update public timeline future status checks to match visibility filter
* same as previous, for home timeline
* update public timeline tests to dynamically check for appropriate statuses
* migrate accounts to allow unique constraint on public_key
* provide minimal account with publicKey
---------
Signed-off-by: kim <grufwub@gmail.com>
Co-authored-by: tsmethurst <tobi.smethurst@protonmail.com>
2023-03-28 13:03:14 +00:00
|
|
|
if err := f.state.DB.PutFollowRequest(ctx, followRequest); err != nil {
|
2021-10-10 10:39:25 +00:00
|
|
|
return fmt.Errorf("activityFollow: database error inserting follow request: %s", err)
|
|
|
|
}
|
|
|
|
|
2023-08-09 17:14:33 +00:00
|
|
|
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
2021-10-10 10:39:25 +00:00
|
|
|
APObjectType: ap.ActivityFollow,
|
|
|
|
APActivityType: ap.ActivityCreate,
|
|
|
|
GTSModel: followRequest,
|
|
|
|
ReceivingAccount: receivingAccount,
|
2022-04-28 12:23:11 +00:00
|
|
|
})
|
2021-10-10 10:39:25 +00:00
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
LIKE HANDLERS
|
|
|
|
*/
|
|
|
|
|
2022-04-28 12:23:11 +00:00
|
|
|
func (f *federatingDB) activityLike(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error {
|
2021-10-10 10:39:25 +00:00
|
|
|
like, ok := asType.(vocab.ActivityStreamsLike)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("activityLike: could not convert type to like")
|
|
|
|
}
|
|
|
|
|
2023-09-23 16:44:11 +00:00
|
|
|
fave, err := f.converter.ASLikeToFave(ctx, like)
|
2021-10-10 10:39:25 +00:00
|
|
|
if err != nil {
|
2023-04-05 18:10:05 +00:00
|
|
|
return fmt.Errorf("activityLike: could not convert Like to fave: %w", err)
|
2021-10-10 10:39:25 +00:00
|
|
|
}
|
|
|
|
|
2023-02-03 20:03:05 +00:00
|
|
|
fave.ID = id.NewULID()
|
2021-10-10 10:39:25 +00:00
|
|
|
|
2023-04-05 18:10:05 +00:00
|
|
|
if err := f.state.DB.PutStatusFave(ctx, fave); err != nil {
|
|
|
|
if errors.Is(err, db.ErrAlreadyExists) {
|
|
|
|
// The Like already exists in the database, which
|
|
|
|
// means we've already handled side effects. We can
|
|
|
|
// just return nil here and be done with it.
|
|
|
|
return nil
|
|
|
|
}
|
|
|
|
return fmt.Errorf("activityLike: database error inserting fave: %w", err)
|
2021-10-10 10:39:25 +00:00
|
|
|
}
|
|
|
|
|
2023-08-09 17:14:33 +00:00
|
|
|
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
2021-10-10 10:39:25 +00:00
|
|
|
APObjectType: ap.ActivityLike,
|
|
|
|
APActivityType: ap.ActivityCreate,
|
|
|
|
GTSModel: fave,
|
|
|
|
ReceivingAccount: receivingAccount,
|
2022-04-28 12:23:11 +00:00
|
|
|
})
|
2021-10-10 10:39:25 +00:00
|
|
|
|
2021-05-27 14:06:24 +00:00
|
|
|
return nil
|
|
|
|
}
|
2023-01-25 10:12:27 +00:00
|
|
|
|
|
|
|
/*
|
|
|
|
FLAG HANDLERS
|
|
|
|
*/
|
|
|
|
|
|
|
|
func (f *federatingDB) activityFlag(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error {
|
|
|
|
flag, ok := asType.(vocab.ActivityStreamsFlag)
|
|
|
|
if !ok {
|
|
|
|
return errors.New("activityFlag: could not convert type to flag")
|
|
|
|
}
|
|
|
|
|
2023-09-23 16:44:11 +00:00
|
|
|
report, err := f.converter.ASFlagToReport(ctx, flag)
|
2023-01-25 10:12:27 +00:00
|
|
|
if err != nil {
|
|
|
|
return fmt.Errorf("activityFlag: could not convert Flag to report: %w", err)
|
|
|
|
}
|
|
|
|
|
2023-02-03 20:03:05 +00:00
|
|
|
report.ID = id.NewULID()
|
2023-01-25 10:12:27 +00:00
|
|
|
|
2023-03-01 18:26:53 +00:00
|
|
|
if err := f.state.DB.PutReport(ctx, report); err != nil {
|
2023-01-25 10:12:27 +00:00
|
|
|
return fmt.Errorf("activityFlag: database error inserting report: %w", err)
|
|
|
|
}
|
|
|
|
|
2023-08-09 17:14:33 +00:00
|
|
|
f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{
|
2023-01-25 10:12:27 +00:00
|
|
|
APObjectType: ap.ActivityFlag,
|
|
|
|
APActivityType: ap.ActivityCreate,
|
|
|
|
GTSModel: report,
|
|
|
|
ReceivingAccount: receivingAccount,
|
|
|
|
})
|
|
|
|
|
|
|
|
return nil
|
|
|
|
}
|