| 
									
										
										
										
											2023-03-12 16:00:57 +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/>. | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | package federatingdb | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							|  |  |  | 	"fmt" | 
					
						
							| 
									
										
										
										
											2023-10-31 12:05:17 +01:00
										 |  |  | 	"strings" | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-07-19 09:47:55 +01:00
										 |  |  | 	"codeberg.org/gruf/go-logger/v2/level" | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01:00
										 |  |  | 	"github.com/superseriousbusiness/activity/pub" | 
					
						
							| 
									
										
										
										
											2021-11-13 17:29:43 +01:00
										 |  |  | 	"github.com/superseriousbusiness/activity/streams/vocab" | 
					
						
							| 
									
										
										
										
											2021-08-31 15:59:12 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/ap" | 
					
						
							| 
									
										
										
										
											2023-10-31 12:05:17 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/config" | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/id" | 
					
						
							| 
									
										
										
										
											2022-07-19 09:47:55 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/log" | 
					
						
							| 
									
										
										
										
											2021-08-31 15:59:12 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/messages" | 
					
						
							| 
									
										
										
										
											2023-10-31 12:05:17 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/util" | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02: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 12:26:34 +01:00
										 |  |  | 	if log.Level() >= level.TRACE { | 
					
						
							| 
									
										
										
										
											2021-10-04 15:24:19 +02:00
										 |  |  | 		i, err := marshalItem(asType) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01:00
										 |  |  | 
 | 
					
						
							|  |  |  | 		log. | 
					
						
							|  |  |  | 			WithContext(ctx). | 
					
						
							|  |  |  | 			WithField("create", i). | 
					
						
							|  |  |  | 			Trace("entering Create") | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-10-04 15:24:19 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-13 16:47:56 +02:00
										 |  |  | 	receivingAccount, requestingAccount, internal := extractFromCtx(ctx) | 
					
						
							|  |  |  | 	if internal { | 
					
						
							|  |  |  | 		return nil // Already processed. | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch asType.GetTypeName() { | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	case ap.ActivityBlock: | 
					
						
							|  |  |  | 		// BLOCK SOMETHING | 
					
						
							| 
									
										
										
										
											2022-04-28 13:23:11 +01:00
										 |  |  | 		return f.activityBlock(ctx, asType, receivingAccount, requestingAccount) | 
					
						
							| 
									
										
										
										
											2021-08-31 15:59:12 +02:00
										 |  |  | 	case ap.ActivityCreate: | 
					
						
							| 
									
										
										
										
											2021-06-17 18:02:33 +02:00
										 |  |  | 		// CREATE SOMETHING | 
					
						
							| 
									
										
										
										
											2022-04-28 13:23:11 +01:00
										 |  |  | 		return f.activityCreate(ctx, asType, receivingAccount, requestingAccount) | 
					
						
							| 
									
										
										
										
											2021-08-31 15:59:12 +02:00
										 |  |  | 	case ap.ActivityFollow: | 
					
						
							| 
									
										
										
										
											2021-07-11 16:22:21 +02:00
										 |  |  | 		// FOLLOW SOMETHING | 
					
						
							| 
									
										
										
										
											2022-04-28 13:23:11 +01:00
										 |  |  | 		return f.activityFollow(ctx, asType, receivingAccount, requestingAccount) | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	case ap.ActivityLike: | 
					
						
							|  |  |  | 		// LIKE SOMETHING | 
					
						
							| 
									
										
										
										
											2022-04-28 13:23:11 +01:00
										 |  |  | 		return f.activityLike(ctx, asType, receivingAccount, requestingAccount) | 
					
						
							| 
									
										
										
										
											2023-01-25 11:12:27 +01:00
										 |  |  | 	case ap.ActivityFlag: | 
					
						
							|  |  |  | 		// FLAG / REPORT SOMETHING | 
					
						
							|  |  |  | 		return f.activityFlag(ctx, asType, receivingAccount, requestingAccount) | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2023-10-04 13:09:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | /* | 
					
						
							|  |  |  | 	BLOCK HANDLERS | 
					
						
							|  |  |  | */ | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 13:23:11 +01:00
										 |  |  | func (f *federatingDB) activityBlock(ctx context.Context, asType vocab.Type, receiving *gtsmodel.Account, requestingAccount *gtsmodel.Account) error { | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	blockable, ok := asType.(vocab.ActivityStreamsBlock) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return errors.New("activityBlock: could not convert type to block") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-23 17:44:11 +01:00
										 |  |  | 	block, err := f.converter.ASBlockToBlock(ctx, blockable) | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("activityBlock: could not convert Block to gts model block") | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-03 20:03:05 +00:00
										 |  |  | 	block.ID = id.NewULID() | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-01 18:26:53 +00:00
										 |  |  | 	if err := f.state.DB.PutBlock(ctx, block); err != nil { | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 		return fmt.Errorf("activityBlock: database error inserting block: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{ | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 		APObjectType:     ap.ActivityBlock, | 
					
						
							|  |  |  | 		APActivityType:   ap.ActivityCreate, | 
					
						
							|  |  |  | 		GTSModel:         block, | 
					
						
							|  |  |  | 		ReceivingAccount: receiving, | 
					
						
							| 
									
										
										
										
											2022-04-28 13:23:11 +01:00
										 |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2023-10-04 13:09:42 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* | 
					
						
							|  |  |  | 	CREATE HANDLERS | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01: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 12:39:25 +02:00
										 |  |  | 	create, ok := asType.(vocab.ActivityStreamsCreate) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01:00
										 |  |  | 		return gtserror.Newf("could not convert asType %T to ActivityStreamsCreate", asType) | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-04 13:09:42 +01: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 12:39:25 +02:00
										 |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-04 13:09:42 +01:00
										 |  |  | 		if statusable, ok := ap.ToStatusable(objType); ok { | 
					
						
							|  |  |  | 			return f.createStatusable(ctx, statusable, receivingAccount, requestingAccount) | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-10-04 13:09:42 +01:00
										 |  |  | 		// TODO: handle CREATE of other types? | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01: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 12:39:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01: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 12:39:25 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01: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 12:39:25 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01: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 12:39:25 +02:00
										 |  |  | 			forward = false | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01:00
										 |  |  | 			break | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01: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 12:39:25 +02:00
										 |  |  | 	if forward { | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01:00
										 |  |  | 		// Pass the statusable URI (APIri) into the processor worker | 
					
						
							|  |  |  | 		// and do the rest of the processing asynchronously. | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 		f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{ | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 			APObjectType:     ap.ObjectNote, | 
					
						
							| 
									
										
										
										
											2021-08-31 15:59:12 +02:00
										 |  |  | 			APActivityType:   ap.ActivityCreate, | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01:00
										 |  |  | 			APIri:            statusableURI, | 
					
						
							| 
									
										
										
										
											2023-05-12 10:15:54 +01:00
										 |  |  | 			APObjectModel:    nil, | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 			GTSModel:         nil, | 
					
						
							|  |  |  | 			ReceivingAccount: receivingAccount, | 
					
						
							| 
									
										
										
										
											2022-04-28 13:23:11 +01:00
										 |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-07-11 16:22:21 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01: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 17:44:11 +01:00
										 |  |  | 	status, err = f.converter.ASStatusToStatus(ctx, statusable) | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return gtserror.Newf("error converting statusable to status: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-07-11 16:22:21 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01:00
										 |  |  | 	// Check whether we should accept this new status. | 
					
						
							|  |  |  | 	accept, err := f.shouldAcceptStatusable(ctx, | 
					
						
							|  |  |  | 		receivingAccount, | 
					
						
							|  |  |  | 		requestingAccount, | 
					
						
							|  |  |  | 		status, | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01:00
										 |  |  | 		return gtserror.Newf("error checking status acceptibility: %w", err) | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-07-11 16:22:21 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01: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 12:39:25 +02:00
										 |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-07-11 16:22:21 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01: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 13:03:23 +02:00
										 |  |  | 		if errors.Is(err, db.ErrAlreadyExists) { | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01: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 12:39:25 +02:00
										 |  |  | 			return nil | 
					
						
							| 
									
										
										
										
											2021-07-11 16:22:21 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01:00
										 |  |  | 		return gtserror.Newf("db error inserting status: %w", err) | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01:00
										 |  |  | 	// Do the rest of the processing asynchronously. The processor | 
					
						
							|  |  |  | 	// will handle inserting/updating + further dereferencing the status. | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{ | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 		APObjectType:     ap.ObjectNote, | 
					
						
							|  |  |  | 		APActivityType:   ap.ActivityCreate, | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01:00
										 |  |  | 		APIri:            nil, | 
					
						
							|  |  |  | 		APObjectModel:    statusable, | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 		GTSModel:         status, | 
					
						
							|  |  |  | 		ReceivingAccount: receivingAccount, | 
					
						
							| 
									
										
										
										
											2022-04-28 13:23:11 +01:00
										 |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01:00
										 |  |  | func (f *federatingDB) shouldAcceptStatusable(ctx context.Context, receiver *gtsmodel.Account, requester *gtsmodel.Account, status *gtsmodel.Status) (bool, error) { | 
					
						
							| 
									
										
										
										
											2023-10-31 12:05:17 +01:00
										 |  |  | 	host := config.GetHost() | 
					
						
							|  |  |  | 	accountDomain := config.GetAccountDomain() | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01:00
										 |  |  | 	// Check whether status mentions the receiver, | 
					
						
							|  |  |  | 	// this is the quickest check so perform it first. | 
					
						
							| 
									
										
										
										
											2023-10-31 12:05:17 +01:00
										 |  |  | 	// Prefer checking using mention Href, fall back to Name. | 
					
						
							| 
									
										
										
										
											2023-08-08 12:26:34 +01:00
										 |  |  | 	for _, mention := range status.Mentions { | 
					
						
							| 
									
										
										
										
											2023-10-31 12:05:17 +01: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 12:26:34 +01: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 12:39:25 +02:00
										 |  |  | /* | 
					
						
							|  |  |  | 	FOLLOW HANDLERS | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 13:23:11 +01:00
										 |  |  | func (f *federatingDB) activityFollow(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error { | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	follow, ok := asType.(vocab.ActivityStreamsFollow) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return errors.New("activityFollow: could not convert type to follow") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-23 17:44:11 +01:00
										 |  |  | 	followRequest, err := f.converter.ASFollowToFollowRequest(ctx, follow) | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02: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 12:39:25 +02: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 14:03:14 +01:00
										 |  |  | 	if err := f.state.DB.PutFollowRequest(ctx, followRequest); err != nil { | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 		return fmt.Errorf("activityFollow: database error inserting follow request: %s", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{ | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 		APObjectType:     ap.ActivityFollow, | 
					
						
							|  |  |  | 		APActivityType:   ap.ActivityCreate, | 
					
						
							|  |  |  | 		GTSModel:         followRequest, | 
					
						
							|  |  |  | 		ReceivingAccount: receivingAccount, | 
					
						
							| 
									
										
										
										
											2022-04-28 13:23:11 +01:00
										 |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | /* | 
					
						
							|  |  |  | 	LIKE HANDLERS | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-04-28 13:23:11 +01:00
										 |  |  | func (f *federatingDB) activityLike(ctx context.Context, asType vocab.Type, receivingAccount *gtsmodel.Account, requestingAccount *gtsmodel.Account) error { | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	like, ok := asType.(vocab.ActivityStreamsLike) | 
					
						
							|  |  |  | 	if !ok { | 
					
						
							|  |  |  | 		return errors.New("activityLike: could not convert type to like") | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-09-23 17:44:11 +01:00
										 |  |  | 	fave, err := f.converter.ASLikeToFave(ctx, like) | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-05 20:10:05 +02:00
										 |  |  | 		return fmt.Errorf("activityLike: could not convert Like to fave: %w", err) | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-03 20:03:05 +00:00
										 |  |  | 	fave.ID = id.NewULID() | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-05 20:10:05 +02: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 12:39:25 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{ | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 		APObjectType:     ap.ActivityLike, | 
					
						
							|  |  |  | 		APActivityType:   ap.ActivityCreate, | 
					
						
							|  |  |  | 		GTSModel:         fave, | 
					
						
							|  |  |  | 		ReceivingAccount: receivingAccount, | 
					
						
							| 
									
										
										
										
											2022-04-28 13:23:11 +01:00
										 |  |  | 	}) | 
					
						
							| 
									
										
										
										
											2021-10-10 12:39:25 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-01-25 11:12:27 +01: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 17:44:11 +01:00
										 |  |  | 	report, err := f.converter.ASFlagToReport(ctx, flag) | 
					
						
							| 
									
										
										
										
											2023-01-25 11:12:27 +01: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 11:12:27 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-01 18:26:53 +00:00
										 |  |  | 	if err := f.state.DB.PutReport(ctx, report); err != nil { | 
					
						
							| 
									
										
										
										
											2023-01-25 11:12:27 +01:00
										 |  |  | 		return fmt.Errorf("activityFlag: database error inserting report: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-08-09 19:14:33 +02:00
										 |  |  | 	f.state.Workers.EnqueueFediAPI(ctx, messages.FromFediAPI{ | 
					
						
							| 
									
										
										
										
											2023-01-25 11:12:27 +01:00
										 |  |  | 		APObjectType:     ap.ActivityFlag, | 
					
						
							|  |  |  | 		APActivityType:   ap.ActivityCreate, | 
					
						
							|  |  |  | 		GTSModel:         report, | 
					
						
							|  |  |  | 		ReceivingAccount: receivingAccount, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } |