| 
									
										
										
										
											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-10-16 13:27:43 +02:00
										 |  |  | 
 | 
					
						
							|  |  |  | package federatingdb | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | import ( | 
					
						
							|  |  |  | 	"context" | 
					
						
							|  |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 	"time" | 
					
						
							| 
									
										
										
										
											2021-10-16 13:27:43 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-04-25 15:15:36 +02:00
										 |  |  | 	"code.superseriousbusiness.org/activity/streams/vocab" | 
					
						
							| 
									
										
										
										
											2025-04-26 15:34:10 +02:00
										 |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/ap" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/db" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/gtserror" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/gtsmodel" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/id" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/log" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/messages" | 
					
						
							|  |  |  | 	"code.superseriousbusiness.org/gotosocial/internal/uris" | 
					
						
							| 
									
										
										
										
											2021-10-16 13:27:43 +02:00
										 |  |  | ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 09:40:48 +00:00
										 |  |  | func (f *DB) Reject(ctx context.Context, reject vocab.ActivityStreamsReject) error { | 
					
						
							| 
									
										
										
										
											2024-09-17 19:35:47 +00:00
										 |  |  | 	log.DebugKV(ctx, "reject", serialize{reject}) | 
					
						
							| 
									
										
										
										
											2021-10-16 13:27:43 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-17 15:20:39 +01:00
										 |  |  | 	activityContext := getActivityContext(ctx) | 
					
						
							|  |  |  | 	if activityContext.internal { | 
					
						
							| 
									
										
										
										
											2023-06-13 16:47:56 +02:00
										 |  |  | 		return nil // Already processed. | 
					
						
							| 
									
										
										
										
											2021-10-16 13:27:43 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-02-17 15:20:39 +01:00
										 |  |  | 	requestingAcct := activityContext.requestingAcct | 
					
						
							|  |  |  | 	receivingAcct := activityContext.receivingAcct | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 	activityID := ap.GetJSONLDId(reject) | 
					
						
							|  |  |  | 	if activityID == nil { | 
					
						
							|  |  |  | 		// We need an ID. | 
					
						
							|  |  |  | 		const text = "Reject had no id property" | 
					
						
							|  |  |  | 		return gtserror.NewErrorBadRequest(errors.New(text), text) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, object := range ap.ExtractObjects(reject) { | 
					
						
							|  |  |  | 		if asType := object.GetType(); asType != nil { | 
					
						
							| 
									
										
										
										
											2024-09-17 19:35:47 +00:00
										 |  |  | 			// Check and handle any vocab.Type objects. | 
					
						
							|  |  |  | 			switch name := asType.GetTypeName(); name { | 
					
						
							| 
									
										
										
										
											2021-10-16 13:27:43 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 			// REJECT FOLLOW | 
					
						
							|  |  |  | 			case ap.ActivityFollow: | 
					
						
							|  |  |  | 				if err := f.rejectFollowType( | 
					
						
							|  |  |  | 					ctx, | 
					
						
							|  |  |  | 					asType, | 
					
						
							|  |  |  | 					receivingAcct, | 
					
						
							|  |  |  | 					requestingAcct, | 
					
						
							|  |  |  | 				); err != nil { | 
					
						
							|  |  |  | 					return err | 
					
						
							| 
									
										
										
										
											2021-10-16 13:27:43 +02:00
										 |  |  | 				} | 
					
						
							| 
									
										
										
										
											2024-09-17 19:35:47 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// UNHANDLED | 
					
						
							|  |  |  | 			default: | 
					
						
							|  |  |  | 				log.Debugf(ctx, "unhandled object type: %s", name) | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 			} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		} else if object.IsIRI() { | 
					
						
							|  |  |  | 			// Check and handle any | 
					
						
							|  |  |  | 			// IRI type objects. | 
					
						
							|  |  |  | 			switch objIRI := object.GetIRI(); { | 
					
						
							| 
									
										
										
										
											2021-10-16 13:27:43 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 			// REJECT FOLLOW | 
					
						
							|  |  |  | 			case uris.IsFollowPath(objIRI): | 
					
						
							|  |  |  | 				if err := f.rejectFollowIRI( | 
					
						
							|  |  |  | 					ctx, | 
					
						
							|  |  |  | 					objIRI.String(), | 
					
						
							|  |  |  | 					receivingAcct, | 
					
						
							|  |  |  | 					requestingAcct, | 
					
						
							|  |  |  | 				); err != nil { | 
					
						
							|  |  |  | 					return err | 
					
						
							| 
									
										
										
										
											2024-02-06 12:59:37 +01:00
										 |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 			// REJECT STATUS (reply/boost) | 
					
						
							|  |  |  | 			case uris.IsStatusesPath(objIRI): | 
					
						
							|  |  |  | 				if err := f.rejectStatusIRI( | 
					
						
							|  |  |  | 					ctx, | 
					
						
							|  |  |  | 					activityID.String(), | 
					
						
							|  |  |  | 					objIRI.String(), | 
					
						
							|  |  |  | 					receivingAcct, | 
					
						
							|  |  |  | 					requestingAcct, | 
					
						
							|  |  |  | 				); err != nil { | 
					
						
							|  |  |  | 					return err | 
					
						
							| 
									
										
										
										
											2021-10-16 13:27:43 +02:00
										 |  |  | 				} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 			// REJECT LIKE | 
					
						
							|  |  |  | 			case uris.IsLikePath(objIRI): | 
					
						
							|  |  |  | 				if err := f.rejectLikeIRI( | 
					
						
							|  |  |  | 					ctx, | 
					
						
							|  |  |  | 					activityID.String(), | 
					
						
							|  |  |  | 					objIRI.String(), | 
					
						
							|  |  |  | 					receivingAcct, | 
					
						
							|  |  |  | 					requestingAcct, | 
					
						
							|  |  |  | 				); err != nil { | 
					
						
							|  |  |  | 					return err | 
					
						
							|  |  |  | 				} | 
					
						
							| 
									
										
										
										
											2024-09-17 19:35:47 +00:00
										 |  |  | 
 | 
					
						
							|  |  |  | 			// UNHANDLED | 
					
						
							|  |  |  | 			default: | 
					
						
							|  |  |  | 				log.Debugf(ctx, "unhandled iri type: %s", objIRI) | 
					
						
							| 
									
										
										
										
											2021-10-16 13:27:43 +02:00
										 |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-10-16 13:27:43 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2023-06-03 13:58:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 09:40:48 +00:00
										 |  |  | func (f *DB) rejectFollowType( | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	asType vocab.Type, | 
					
						
							|  |  |  | 	receivingAcct *gtsmodel.Account, | 
					
						
							|  |  |  | 	requestingAcct *gtsmodel.Account, | 
					
						
							|  |  |  | ) error { | 
					
						
							|  |  |  | 	// Cast the vocab.Type object to known AS type. | 
					
						
							|  |  |  | 	asFollow := asType.(vocab.ActivityStreamsFollow) | 
					
						
							| 
									
										
										
										
											2023-06-03 13:58:57 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 	// Reconstruct the follow. | 
					
						
							|  |  |  | 	follow, err := f.converter.ASFollowToFollow(ctx, asFollow) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		err := gtserror.Newf("error converting Follow to *gtsmodel.Follow: %w", err) | 
					
						
							|  |  |  | 		return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2024-02-06 12:59:37 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 	// Make sure the creator of the original follow | 
					
						
							|  |  |  | 	// is the same as whatever inbox this landed in. | 
					
						
							|  |  |  | 	if follow.AccountID != receivingAcct.ID { | 
					
						
							|  |  |  | 		const text = "Follow account and inbox account were not the same" | 
					
						
							|  |  |  | 		return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Make sure the target of the original follow | 
					
						
							|  |  |  | 	// is the same as the account making the request. | 
					
						
							|  |  |  | 	if follow.TargetAccountID != requestingAcct.ID { | 
					
						
							|  |  |  | 		const text = "Follow target account and requesting account were not the same" | 
					
						
							|  |  |  | 		return gtserror.NewErrorForbidden(errors.New(text), text) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Reject the follow. | 
					
						
							| 
									
										
										
										
											2025-05-15 09:40:48 +00:00
										 |  |  | 	err = f.state.DB.RejectFollowRequest(ctx, | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 		follow.AccountID, | 
					
						
							|  |  |  | 		follow.TargetAccountID, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 		err := gtserror.Newf("db error rejecting follow request: %w", err) | 
					
						
							|  |  |  | 		return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 09:40:48 +00:00
										 |  |  | func (f *DB) rejectFollowIRI( | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	objectIRI string, | 
					
						
							|  |  |  | 	receivingAcct *gtsmodel.Account, | 
					
						
							|  |  |  | 	requestingAcct *gtsmodel.Account, | 
					
						
							|  |  |  | ) error { | 
					
						
							|  |  |  | 	// Get the follow req from the db. | 
					
						
							|  |  |  | 	followReq, err := f.state.DB.GetFollowRequestByURI(ctx, objectIRI) | 
					
						
							|  |  |  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 		err := gtserror.Newf("db error getting follow request: %w", err) | 
					
						
							|  |  |  | 		return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if followReq == nil { | 
					
						
							|  |  |  | 		// We didn't have a follow request | 
					
						
							|  |  |  | 		// with this URI, so nothing to do. | 
					
						
							|  |  |  | 		// Just return. | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		// TODO: Handle Reject Follow to remove | 
					
						
							|  |  |  | 		// an already-accepted follow relationship. | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Make sure the creator of the original follow | 
					
						
							|  |  |  | 	// is the same as whatever inbox this landed in. | 
					
						
							|  |  |  | 	if followReq.AccountID != receivingAcct.ID { | 
					
						
							|  |  |  | 		const text = "Follow account and inbox account were not the same" | 
					
						
							|  |  |  | 		return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Make sure the target of the original follow | 
					
						
							|  |  |  | 	// is the same as the account making the request. | 
					
						
							|  |  |  | 	if followReq.TargetAccountID != requestingAcct.ID { | 
					
						
							|  |  |  | 		const text = "Follow target account and requesting account were not the same" | 
					
						
							|  |  |  | 		return gtserror.NewErrorForbidden(errors.New(text), text) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Reject the follow. | 
					
						
							| 
									
										
										
										
											2025-05-15 09:40:48 +00:00
										 |  |  | 	err = f.state.DB.RejectFollowRequest(ctx, | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 		followReq.AccountID, | 
					
						
							|  |  |  | 		followReq.TargetAccountID, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 		err := gtserror.Newf("db error rejecting follow request: %w", err) | 
					
						
							|  |  |  | 		return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 09:40:48 +00:00
										 |  |  | func (f *DB) rejectStatusIRI( | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	activityID string, | 
					
						
							|  |  |  | 	objectIRI string, | 
					
						
							|  |  |  | 	receivingAcct *gtsmodel.Account, | 
					
						
							|  |  |  | 	requestingAcct *gtsmodel.Account, | 
					
						
							|  |  |  | ) error { | 
					
						
							|  |  |  | 	// Lock on this potential status URI. | 
					
						
							|  |  |  | 	unlock := f.state.FedLocks.Lock(objectIRI) | 
					
						
							|  |  |  | 	defer unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get the status from the db. | 
					
						
							|  |  |  | 	status, err := f.state.DB.GetStatusByURI(ctx, objectIRI) | 
					
						
							|  |  |  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 		err := gtserror.Newf("db error getting status: %w", err) | 
					
						
							|  |  |  | 		return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if status == nil { | 
					
						
							|  |  |  | 		// We didn't have a status with | 
					
						
							|  |  |  | 		// this URI, so nothing to do. | 
					
						
							|  |  |  | 		// Just return. | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !status.IsLocal() { | 
					
						
							|  |  |  | 		// We don't process Rejects of statuses | 
					
						
							|  |  |  | 		// that weren't created on our instance. | 
					
						
							|  |  |  | 		// Just return. | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		// TODO: Handle Reject to remove *remote* | 
					
						
							|  |  |  | 		// posts replying-to or boosting the | 
					
						
							|  |  |  | 		// Rejecting account. | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-10-16 13:27:43 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 	// Make sure the creator of the original status | 
					
						
							|  |  |  | 	// is the same as the inbox processing the Reject; | 
					
						
							|  |  |  | 	// this also ensures the status is local. | 
					
						
							|  |  |  | 	if status.AccountID != receivingAcct.ID { | 
					
						
							|  |  |  | 		const text = "status author account and inbox account were not the same" | 
					
						
							|  |  |  | 		return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if we're dealing with a reply | 
					
						
							|  |  |  | 	// or an announce, and make sure the | 
					
						
							|  |  |  | 	// requester is permitted to Reject. | 
					
						
							|  |  |  | 	var apObjectType string | 
					
						
							|  |  |  | 	if status.InReplyToID != "" { | 
					
						
							|  |  |  | 		// Rejecting a Reply. | 
					
						
							|  |  |  | 		apObjectType = ap.ObjectNote | 
					
						
							|  |  |  | 		if status.InReplyToAccountID != requestingAcct.ID { | 
					
						
							|  |  |  | 			const text = "status reply to account and requesting account were not the same" | 
					
						
							|  |  |  | 			return gtserror.NewErrorForbidden(errors.New(text), text) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		// You can't mention an account and then Reject replies from that | 
					
						
							|  |  |  | 		// same account (harassment vector); don't process these Rejects. | 
					
						
							|  |  |  | 		if status.InReplyTo != nil && status.InReplyTo.MentionsAccount(status.AccountID) { | 
					
						
							|  |  |  | 			const text = "refusing to process Reject of a reply from a mentioned account" | 
					
						
							|  |  |  | 			return gtserror.NewErrorForbidden(errors.New(text), text) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		// Rejecting an Announce. | 
					
						
							|  |  |  | 		apObjectType = ap.ActivityAnnounce | 
					
						
							|  |  |  | 		if status.BoostOfAccountID != requestingAcct.ID { | 
					
						
							|  |  |  | 			const text = "status boost of account and requesting account were not the same" | 
					
						
							|  |  |  | 			return gtserror.NewErrorForbidden(errors.New(text), text) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if there's an interaction request in the db for this status. | 
					
						
							|  |  |  | 	req, err := f.state.DB.GetInteractionRequestByInteractionURI(ctx, status.URI) | 
					
						
							|  |  |  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 		err := gtserror.Newf("db error getting interaction request: %w", err) | 
					
						
							|  |  |  | 		return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch { | 
					
						
							|  |  |  | 	case req == nil: | 
					
						
							|  |  |  | 		// No interaction request existed yet for this | 
					
						
							|  |  |  | 		// status, create a pre-rejected request now. | 
					
						
							|  |  |  | 		req = >smodel.InteractionRequest{ | 
					
						
							|  |  |  | 			ID:                   id.NewULID(), | 
					
						
							|  |  |  | 			TargetAccountID:      requestingAcct.ID, | 
					
						
							|  |  |  | 			TargetAccount:        requestingAcct, | 
					
						
							|  |  |  | 			InteractingAccountID: receivingAcct.ID, | 
					
						
							|  |  |  | 			InteractingAccount:   receivingAcct, | 
					
						
							|  |  |  | 			InteractionURI:       status.URI, | 
					
						
							|  |  |  | 			URI:                  activityID, | 
					
						
							|  |  |  | 			RejectedAt:           time.Now(), | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if apObjectType == ap.ObjectNote { | 
					
						
							|  |  |  | 			// Reply. | 
					
						
							|  |  |  | 			req.InteractionType = gtsmodel.InteractionReply | 
					
						
							|  |  |  | 			req.StatusID = status.InReplyToID | 
					
						
							|  |  |  | 			req.Status = status.InReplyTo | 
					
						
							|  |  |  | 			req.Reply = status | 
					
						
							|  |  |  | 		} else { | 
					
						
							|  |  |  | 			// Announce. | 
					
						
							|  |  |  | 			req.InteractionType = gtsmodel.InteractionAnnounce | 
					
						
							|  |  |  | 			req.StatusID = status.BoostOfID | 
					
						
							|  |  |  | 			req.Status = status.BoostOf | 
					
						
							|  |  |  | 			req.Announce = status | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err := f.state.DB.PutInteractionRequest(ctx, req); err != nil { | 
					
						
							|  |  |  | 			err := gtserror.Newf("db error inserting interaction request: %w", err) | 
					
						
							|  |  |  | 			return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case req.IsRejected(): | 
					
						
							|  |  |  | 		// Interaction has already been rejected. Just | 
					
						
							|  |  |  | 		// update to this Reject URI and then return early. | 
					
						
							|  |  |  | 		req.URI = activityID | 
					
						
							|  |  |  | 		if err := f.state.DB.UpdateInteractionRequest(ctx, req, "uri"); err != nil { | 
					
						
							|  |  |  | 			err := gtserror.Newf("db error updating interaction request: %w", err) | 
					
						
							|  |  |  | 			return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		// Mark existing interaction request as | 
					
						
							|  |  |  | 		// Rejected, even if previously Accepted. | 
					
						
							|  |  |  | 		req.AcceptedAt = time.Time{} | 
					
						
							|  |  |  | 		req.RejectedAt = time.Now() | 
					
						
							|  |  |  | 		req.URI = activityID | 
					
						
							|  |  |  | 		if err := f.state.DB.UpdateInteractionRequest(ctx, req, | 
					
						
							|  |  |  | 			"accepted_at", | 
					
						
							|  |  |  | 			"rejected_at", | 
					
						
							|  |  |  | 			"uri", | 
					
						
							|  |  |  | 		); err != nil { | 
					
						
							|  |  |  | 			err := gtserror.Newf("db error updating interaction request: %w", err) | 
					
						
							|  |  |  | 			return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Send the rejected request through to | 
					
						
							|  |  |  | 	// the fedi worker to process side effects. | 
					
						
							|  |  |  | 	f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{ | 
					
						
							|  |  |  | 		APObjectType:   apObjectType, | 
					
						
							|  |  |  | 		APActivityType: ap.ActivityReject, | 
					
						
							|  |  |  | 		GTSModel:       req, | 
					
						
							|  |  |  | 		Receiving:      receivingAcct, | 
					
						
							|  |  |  | 		Requesting:     requestingAcct, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2025-05-15 09:40:48 +00:00
										 |  |  | func (f *DB) rejectLikeIRI( | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	activityID string, | 
					
						
							|  |  |  | 	objectIRI string, | 
					
						
							|  |  |  | 	receivingAcct *gtsmodel.Account, | 
					
						
							|  |  |  | 	requestingAcct *gtsmodel.Account, | 
					
						
							|  |  |  | ) error { | 
					
						
							|  |  |  | 	// Lock on this potential Like | 
					
						
							|  |  |  | 	// URI as we may be updating it. | 
					
						
							|  |  |  | 	unlock := f.state.FedLocks.Lock(objectIRI) | 
					
						
							|  |  |  | 	defer unlock() | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Get the fave from the db. | 
					
						
							|  |  |  | 	fave, err := f.state.DB.GetStatusFaveByURI(ctx, objectIRI) | 
					
						
							|  |  |  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 		err := gtserror.Newf("db error getting fave: %w", err) | 
					
						
							|  |  |  | 		return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if fave == nil { | 
					
						
							|  |  |  | 		// We didn't have a fave with | 
					
						
							|  |  |  | 		// this URI, so nothing to do. | 
					
						
							|  |  |  | 		// Just return. | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if !fave.Account.IsLocal() { | 
					
						
							|  |  |  | 		// We don't process Rejects of Likes | 
					
						
							|  |  |  | 		// that weren't created on our instance. | 
					
						
							|  |  |  | 		// Just return. | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		// TODO: Handle Reject to remove *remote* | 
					
						
							|  |  |  | 		// likes targeting the Rejecting account. | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Make sure the creator of the original Like | 
					
						
							|  |  |  | 	// is the same as the inbox processing the Reject; | 
					
						
							|  |  |  | 	// this also ensures the Like is local. | 
					
						
							|  |  |  | 	if fave.AccountID != receivingAcct.ID { | 
					
						
							|  |  |  | 		const text = "fave creator account and inbox account were not the same" | 
					
						
							|  |  |  | 		return gtserror.NewErrorUnprocessableEntity(errors.New(text), text) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Make sure the target of the Like is the | 
					
						
							|  |  |  | 	// same as the account doing the Reject. | 
					
						
							|  |  |  | 	if fave.TargetAccountID != requestingAcct.ID { | 
					
						
							|  |  |  | 		const text = "status fave target account and requesting account were not the same" | 
					
						
							|  |  |  | 		return gtserror.NewErrorForbidden(errors.New(text), text) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Check if there's an interaction request in the db for this like. | 
					
						
							|  |  |  | 	req, err := f.state.DB.GetInteractionRequestByInteractionURI(ctx, fave.URI) | 
					
						
							|  |  |  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 		err := gtserror.Newf("db error getting interaction request: %w", err) | 
					
						
							|  |  |  | 		return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	switch { | 
					
						
							|  |  |  | 	case req == nil: | 
					
						
							|  |  |  | 		// No interaction request existed yet for this | 
					
						
							|  |  |  | 		// fave, create a pre-rejected request now. | 
					
						
							|  |  |  | 		req = >smodel.InteractionRequest{ | 
					
						
							|  |  |  | 			ID:                   id.NewULID(), | 
					
						
							|  |  |  | 			TargetAccountID:      requestingAcct.ID, | 
					
						
							|  |  |  | 			TargetAccount:        requestingAcct, | 
					
						
							|  |  |  | 			InteractingAccountID: receivingAcct.ID, | 
					
						
							|  |  |  | 			InteractingAccount:   receivingAcct, | 
					
						
							|  |  |  | 			InteractionURI:       fave.URI, | 
					
						
							|  |  |  | 			InteractionType:      gtsmodel.InteractionLike, | 
					
						
							|  |  |  | 			Like:                 fave, | 
					
						
							|  |  |  | 			URI:                  activityID, | 
					
						
							|  |  |  | 			RejectedAt:           time.Now(), | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		if err := f.state.DB.PutInteractionRequest(ctx, req); err != nil { | 
					
						
							|  |  |  | 			err := gtserror.Newf("db error inserting interaction request: %w", err) | 
					
						
							|  |  |  | 			return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	case req.IsRejected(): | 
					
						
							|  |  |  | 		// Interaction has already been rejected. Just | 
					
						
							|  |  |  | 		// update to this Reject URI and then return early. | 
					
						
							|  |  |  | 		req.URI = activityID | 
					
						
							|  |  |  | 		if err := f.state.DB.UpdateInteractionRequest(ctx, req, "uri"); err != nil { | 
					
						
							|  |  |  | 			err := gtserror.Newf("db error updating interaction request: %w", err) | 
					
						
							|  |  |  | 			return gtserror.NewErrorInternalError(err) | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	default: | 
					
						
							|  |  |  | 		// Mark existing interaction request as | 
					
						
							|  |  |  | 		// Rejected, even if previously Accepted. | 
					
						
							|  |  |  | 		req.AcceptedAt = time.Time{} | 
					
						
							|  |  |  | 		req.RejectedAt = time.Now() | 
					
						
							|  |  |  | 		req.URI = activityID | 
					
						
							|  |  |  | 		if err := f.state.DB.UpdateInteractionRequest(ctx, req, | 
					
						
							|  |  |  | 			"accepted_at", | 
					
						
							|  |  |  | 			"rejected_at", | 
					
						
							|  |  |  | 			"uri", | 
					
						
							|  |  |  | 		); err != nil { | 
					
						
							|  |  |  | 			err := gtserror.Newf("db error updating interaction request: %w", err) | 
					
						
							|  |  |  | 			return gtserror.NewErrorInternalError(err) | 
					
						
							| 
									
										
										
										
											2021-10-16 13:27:43 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-09-10 14:34:49 +02:00
										 |  |  | 	// Send the rejected request through to | 
					
						
							|  |  |  | 	// the fedi worker to process side effects. | 
					
						
							|  |  |  | 	f.state.Workers.Federator.Queue.Push(&messages.FromFediAPI{ | 
					
						
							|  |  |  | 		APObjectType:   ap.ActivityLike, | 
					
						
							|  |  |  | 		APActivityType: ap.ActivityReject, | 
					
						
							|  |  |  | 		GTSModel:       req, | 
					
						
							|  |  |  | 		Receiving:      receivingAcct, | 
					
						
							|  |  |  | 		Requesting:     requestingAcct, | 
					
						
							|  |  |  | 	}) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-10-16 13:27:43 +02:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } |