| 
									
										
										
										
											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-03-01 15:41:43 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-30 13:12:00 +02:00
										 |  |  | package processing | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | import ( | 
					
						
							| 
									
										
										
										
											2021-08-25 15:34:33 +02:00
										 |  |  | 	"context" | 
					
						
							| 
									
										
										
										
											2023-03-19 13:11:46 +01:00
										 |  |  | 	"errors" | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	"fmt" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-03-19 13:11:46 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/config" | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/db" | 
					
						
							| 
									
										
										
										
											2023-03-19 13:11:46 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/email" | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtscontext" | 
					
						
							| 
									
										
										
										
											2023-03-20 19:10:08 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtserror" | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/gtsmodel" | 
					
						
							| 
									
										
										
										
											2021-06-13 18:42:28 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/id" | 
					
						
							| 
									
										
										
										
											2023-06-11 11:18:44 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/log" | 
					
						
							| 
									
										
										
										
											2021-11-22 19:03:21 +01:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/stream" | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | 	"github.com/superseriousbusiness/gotosocial/internal/timeline" | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | ) | 
					
						
							| 
									
										
										
										
											2021-04-01 20:46:45 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | // timelineAndNotifyStatus processes the given new status and inserts it into | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | // the HOME and LIST timelines of accounts that follow the status author. | 
					
						
							|  |  |  | // | 
					
						
							|  |  |  | // It will also handle notifications for any mentions attached to the account, and | 
					
						
							|  |  |  | // also notifications for any local accounts that want to know when this account posts. | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | func (p *Processor) timelineAndNotifyStatus(ctx context.Context, status *gtsmodel.Status) error { | 
					
						
							|  |  |  | 	// Ensure status fully populated; including account, mentions, etc. | 
					
						
							|  |  |  | 	if err := p.state.DB.PopulateStatus(ctx, status); err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("timelineAndNotifyStatus: error populating status with id %s: %w", status.ID, err) | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// Get local followers of the account that posted the status. | 
					
						
							|  |  |  | 	follows, err := p.state.DB.GetAccountLocalFollowers(ctx, status.AccountID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("timelineAndNotifyStatus: error getting local followers for account id %s: %w", status.AccountID, err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
											  
											
												[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
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// If the poster is also local, add a fake entry for them | 
					
						
							|  |  |  | 	// so they can see their own status in their timeline. | 
					
						
							|  |  |  | 	if status.Account.IsLocal() { | 
					
						
							|  |  |  | 		follows = append(follows, >smodel.Follow{ | 
					
						
							|  |  |  | 			AccountID:   status.AccountID, | 
					
						
							|  |  |  | 			Account:     status.Account, | 
					
						
							|  |  |  | 			Notify:      func() *bool { b := false; return &b }(), // Account shouldn't notify itself. | 
					
						
							|  |  |  | 			ShowReblogs: func() *bool { b := true; return &b }(),  // Account should show own reblogs. | 
					
						
							|  |  |  | 		}) | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// Timeline the status for each local follower of this account. | 
					
						
							|  |  |  | 	// This will also handle notifying any followers with notify | 
					
						
							|  |  |  | 	// set to true on their follow. | 
					
						
							|  |  |  | 	if err := p.timelineAndNotifyStatusForFollowers(ctx, status, follows); err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("timelineAndNotifyStatus: error timelining status %s for followers: %w", status.ID, err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	// Notify each local account that's mentioned by this status. | 
					
						
							|  |  |  | 	if err := p.notifyStatusMentions(ctx, status); err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("timelineAndNotifyStatus: error notifying status mentions for status %s: %w", status.ID, err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (p *Processor) timelineAndNotifyStatusForFollowers(ctx context.Context, status *gtsmodel.Status, follows []*gtsmodel.Follow) error { | 
					
						
							|  |  |  | 	var ( | 
					
						
							|  |  |  | 		errs  = make(gtserror.MultiError, 0, len(follows)) | 
					
						
							|  |  |  | 		boost = status.BoostOfID != "" | 
					
						
							|  |  |  | 		reply = status.InReplyToURI != "" | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, follow := range follows { | 
					
						
							|  |  |  | 		if sr := follow.ShowReblogs; boost && (sr == nil || !*sr) { | 
					
						
							|  |  |  | 			// This is a boost, but this follower | 
					
						
							|  |  |  | 			// doesn't want to see those from this | 
					
						
							|  |  |  | 			// account, so just skip everything. | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | 		// Add status to each list that this follow | 
					
						
							|  |  |  | 		// is included in, and stream it if applicable. | 
					
						
							|  |  |  | 		listEntries, err := p.state.DB.GetListEntriesForFollowID( | 
					
						
							|  |  |  | 			// We only need the list IDs. | 
					
						
							|  |  |  | 			gtscontext.SetBarebones(ctx), | 
					
						
							|  |  |  | 			follow.ID, | 
					
						
							|  |  |  | 		) | 
					
						
							|  |  |  | 		if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 			errs.Append(fmt.Errorf("timelineAndNotifyStatusForFollowers: error list timelining status: %w", err)) | 
					
						
							|  |  |  | 			continue | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 		for _, listEntry := range listEntries { | 
					
						
							|  |  |  | 			if _, err := p.timelineStatus( | 
					
						
							|  |  |  | 				ctx, | 
					
						
							|  |  |  | 				p.state.Timelines.List.IngestOne, | 
					
						
							|  |  |  | 				listEntry.ListID, // list timelines are keyed by list ID | 
					
						
							|  |  |  | 				follow.Account, | 
					
						
							|  |  |  | 				status, | 
					
						
							|  |  |  | 				stream.TimelineList+":"+listEntry.ListID, // key streamType to this specific list | 
					
						
							|  |  |  | 			); err != nil { | 
					
						
							|  |  |  | 				errs.Append(fmt.Errorf("timelineAndNotifyStatusForFollowers: error list timelining status: %w", err)) | 
					
						
							|  |  |  | 				continue | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		// Add status to home timeline for this | 
					
						
							|  |  |  | 		// follower, and stream it if applicable. | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | 		if timelined, err := p.timelineStatus( | 
					
						
							|  |  |  | 			ctx, | 
					
						
							|  |  |  | 			p.state.Timelines.Home.IngestOne, | 
					
						
							|  |  |  | 			follow.AccountID, // home timelines are keyed by account ID | 
					
						
							|  |  |  | 			follow.Account, | 
					
						
							|  |  |  | 			status, | 
					
						
							|  |  |  | 			stream.TimelineHome, | 
					
						
							|  |  |  | 		); err != nil { | 
					
						
							|  |  |  | 			errs.Append(fmt.Errorf("timelineAndNotifyStatusForFollowers: error home timelining status: %w", err)) | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 			continue | 
					
						
							|  |  |  | 		} else if !timelined { | 
					
						
							|  |  |  | 			// Status wasn't added to home tomeline, | 
					
						
							|  |  |  | 			// so we shouldn't notify it either. | 
					
						
							|  |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		if n := follow.Notify; n == nil || !*n { | 
					
						
							|  |  |  | 			// This follower doesn't have notifications | 
					
						
							|  |  |  | 			// set for this account's new posts, so bail. | 
					
						
							|  |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-06-19 11:18:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		if boost || reply { | 
					
						
							|  |  |  | 			// Don't notify for boosts or replies. | 
					
						
							|  |  |  | 			continue | 
					
						
							| 
									
										
										
										
											2021-06-19 11:18:55 +02:00
										 |  |  | 		} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		// If we reach here, we know: | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		//   - This follower wants to be notified when this account posts. | 
					
						
							|  |  |  | 		//   - This is a top-level post (not a reply). | 
					
						
							|  |  |  | 		//   - This is not a boost of another post. | 
					
						
							|  |  |  | 		//   - The post is visible in this follower's home timeline. | 
					
						
							|  |  |  | 		// | 
					
						
							|  |  |  | 		// That means we can officially notify this one. | 
					
						
							|  |  |  | 		if err := p.notify( | 
					
						
							|  |  |  | 			ctx, | 
					
						
							|  |  |  | 			gtsmodel.NotificationStatus, | 
					
						
							|  |  |  | 			follow.AccountID, | 
					
						
							|  |  |  | 			status.AccountID, | 
					
						
							|  |  |  | 			status.ID, | 
					
						
							|  |  |  | 		); err != nil { | 
					
						
							|  |  |  | 			errs.Append(fmt.Errorf("timelineAndNotifyStatusForFollowers: error notifying account %s about new status: %w", follow.AccountID, err)) | 
					
						
							| 
									
										
										
										
											2021-06-19 11:18:55 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	return errs.Combine() | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | // timelineStatus uses the provided ingest function to put the given | 
					
						
							|  |  |  | // status in a timeline with the given ID, if it's timelineable. | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | // | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | // If the status was inserted into the timeline, true will be returned | 
					
						
							|  |  |  | // + it will also be streamed to the user using the given streamType. | 
					
						
							|  |  |  | func (p *Processor) timelineStatus( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	ingest func(context.Context, string, timeline.Timelineable) (bool, error), | 
					
						
							|  |  |  | 	timelineID string, | 
					
						
							|  |  |  | 	account *gtsmodel.Account, | 
					
						
							|  |  |  | 	status *gtsmodel.Status, | 
					
						
							|  |  |  | 	streamType string, | 
					
						
							|  |  |  | ) (bool, error) { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// Make sure the status is timelineable. | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | 	// This works for both home and list timelines. | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	if timelineable, err := p.filter.StatusHomeTimelineable(ctx, account, status); err != nil { | 
					
						
							|  |  |  | 		err = fmt.Errorf("timelineStatusForAccount: error getting timelineability for status for timeline with id %s: %w", account.ID, err) | 
					
						
							|  |  |  | 		return false, err | 
					
						
							|  |  |  | 	} else if !timelineable { | 
					
						
							|  |  |  | 		// Nothing to do. | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							| 
									
										
										
										
											2021-10-01 19:08:50 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | 	// Ingest status into given timeline using provided function. | 
					
						
							|  |  |  | 	if inserted, err := ingest(ctx, timelineID, status); err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		err = fmt.Errorf("timelineStatusForAccount: error ingesting status %s: %w", status.ID, err) | 
					
						
							|  |  |  | 		return false, err | 
					
						
							|  |  |  | 	} else if !inserted { | 
					
						
							|  |  |  | 		// Nothing more to do. | 
					
						
							|  |  |  | 		return false, nil | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// The status was inserted so stream it to the user. | 
					
						
							|  |  |  | 	apiStatus, err := p.tc.StatusToAPIStatus(ctx, status, account) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		err = fmt.Errorf("timelineStatusForAccount: error converting status %s to frontend representation: %w", status.ID, err) | 
					
						
							|  |  |  | 		return true, err | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-05-25 10:37:38 +02:00
										 |  |  | 	if err := p.stream.Update(apiStatus, account, []string{streamType}); err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		err = fmt.Errorf("timelineStatusForAccount: error streaming update for status %s: %w", status.ID, err) | 
					
						
							|  |  |  | 		return true, err | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	return true, nil | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-06-19 11:18:55 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | func (p *Processor) notifyStatusMentions(ctx context.Context, status *gtsmodel.Status) error { | 
					
						
							|  |  |  | 	errs := make(gtserror.MultiError, 0, len(status.Mentions)) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	for _, m := range status.Mentions { | 
					
						
							|  |  |  | 		if err := p.notify( | 
					
						
							|  |  |  | 			ctx, | 
					
						
							|  |  |  | 			gtsmodel.NotificationMention, | 
					
						
							|  |  |  | 			m.TargetAccountID, | 
					
						
							|  |  |  | 			m.OriginAccountID, | 
					
						
							|  |  |  | 			m.StatusID, | 
					
						
							|  |  |  | 		); err != nil { | 
					
						
							|  |  |  | 			errs.Append(err) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-06-19 11:18:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	return errs.Combine() | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (p *Processor) notifyFollowRequest(ctx context.Context, followRequest *gtsmodel.FollowRequest) error { | 
					
						
							|  |  |  | 	return p.notify( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							|  |  |  | 		gtsmodel.NotificationFollowRequest, | 
					
						
							|  |  |  | 		followRequest.TargetAccountID, | 
					
						
							|  |  |  | 		followRequest.AccountID, | 
					
						
							|  |  |  | 		"", | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2021-03-22 22:26:54 +01:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-05-21 15:48:26 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | func (p *Processor) notifyFollow(ctx context.Context, follow *gtsmodel.Follow, targetAccount *gtsmodel.Account) error { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// Remove previous follow request notification, if it exists. | 
					
						
							|  |  |  | 	prevNotif, err := p.state.DB.GetNotification( | 
					
						
							|  |  |  | 		gtscontext.SetBarebones(ctx), | 
					
						
							|  |  |  | 		gtsmodel.NotificationFollowRequest, | 
					
						
							|  |  |  | 		targetAccount.ID, | 
					
						
							|  |  |  | 		follow.AccountID, | 
					
						
							|  |  |  | 		"", | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | 	if err != nil && !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 		// Proper error while checking. | 
					
						
							|  |  |  | 		return fmt.Errorf("notifyFollow: db error checking for previous follow request notification: %w", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if prevNotif != nil { | 
					
						
							|  |  |  | 		// Previous notification existed, delete. | 
					
						
							|  |  |  | 		if err := p.state.DB.DeleteNotificationByID(ctx, prevNotif.ID); err != nil { | 
					
						
							|  |  |  | 			return fmt.Errorf("notifyFollow: db error removing previous follow request notification %s: %w", prevNotif.ID, err) | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// Now notify the follow itself. | 
					
						
							|  |  |  | 	return p.notify( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							|  |  |  | 		gtsmodel.NotificationFollow, | 
					
						
							|  |  |  | 		targetAccount.ID, | 
					
						
							|  |  |  | 		follow.AccountID, | 
					
						
							|  |  |  | 		"", | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | func (p *Processor) notifyFave(ctx context.Context, fave *gtsmodel.StatusFave) error { | 
					
						
							|  |  |  | 	if fave.TargetAccountID == fave.AccountID { | 
					
						
							|  |  |  | 		// Self-fave, nothing to do. | 
					
						
							|  |  |  | 		return nil | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	return p.notify( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							|  |  |  | 		gtsmodel.NotificationFave, | 
					
						
							|  |  |  | 		fave.TargetAccountID, | 
					
						
							|  |  |  | 		fave.AccountID, | 
					
						
							|  |  |  | 		fave.StatusID, | 
					
						
							|  |  |  | 	) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (p *Processor) notifyAnnounce(ctx context.Context, status *gtsmodel.Status) error { | 
					
						
							|  |  |  | 	if status.BoostOfID == "" { | 
					
						
							|  |  |  | 		// Not a boost, nothing to do. | 
					
						
							|  |  |  | 		return nil | 
					
						
							| 
									
										
										
										
											2021-06-19 11:18:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	if status.BoostOfAccountID == status.AccountID { | 
					
						
							|  |  |  | 		// Self-boost, nothing to do. | 
					
						
							|  |  |  | 		return nil | 
					
						
							| 
									
										
										
										
											2021-06-19 11:18:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	return p.notify( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							|  |  |  | 		gtsmodel.NotificationReblog, | 
					
						
							|  |  |  | 		status.BoostOfAccountID, | 
					
						
							|  |  |  | 		status.AccountID, | 
					
						
							|  |  |  | 		status.ID, | 
					
						
							|  |  |  | 	) | 
					
						
							| 
									
										
										
										
											2021-05-21 15:48:26 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-05-24 18:49:48 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | func (p *Processor) notify( | 
					
						
							|  |  |  | 	ctx context.Context, | 
					
						
							|  |  |  | 	notificationType gtsmodel.NotificationType, | 
					
						
							|  |  |  | 	targetAccountID string, | 
					
						
							|  |  |  | 	originAccountID string, | 
					
						
							|  |  |  | 	statusID string, | 
					
						
							|  |  |  | ) error { | 
					
						
							|  |  |  | 	targetAccount, err := p.state.DB.GetAccountByID(ctx, targetAccountID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		return fmt.Errorf("notify: error getting target account %s: %w", targetAccountID, err) | 
					
						
							| 
									
										
										
										
											2022-08-30 05:43:29 -04:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	if !targetAccount.IsLocal() { | 
					
						
							|  |  |  | 		// Nothing to do. | 
					
						
							|  |  |  | 		return nil | 
					
						
							| 
									
										
										
										
											2021-09-27 17:42:20 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// Make sure a notification doesn't | 
					
						
							|  |  |  | 	// already exist with these params. | 
					
						
							|  |  |  | 	if _, err := p.state.DB.GetNotification( | 
					
						
							|  |  |  | 		ctx, | 
					
						
							|  |  |  | 		notificationType, | 
					
						
							|  |  |  | 		targetAccountID, | 
					
						
							|  |  |  | 		originAccountID, | 
					
						
							|  |  |  | 		statusID, | 
					
						
							|  |  |  | 	); err == nil { | 
					
						
							|  |  |  | 		// Notification exists, nothing to do. | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 		return nil | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	} else if !errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 		// Real error. | 
					
						
							|  |  |  | 		return fmt.Errorf("notify: error checking existence of notification: %w", err) | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// Notification doesn't yet exist, so | 
					
						
							|  |  |  | 	// we need to create + store one. | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	notif := >smodel.Notification{ | 
					
						
							| 
									
										
										
										
											2023-02-03 20:03:05 +00:00
										 |  |  | 		ID:               id.NewULID(), | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		NotificationType: notificationType, | 
					
						
							|  |  |  | 		TargetAccountID:  targetAccountID, | 
					
						
							|  |  |  | 		OriginAccountID:  originAccountID, | 
					
						
							|  |  |  | 		StatusID:         statusID, | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +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 := p.state.DB.PutNotification(ctx, notif); err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		return fmt.Errorf("notify: error putting notification in database: %w", err) | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// Stream notification to the user. | 
					
						
							| 
									
										
										
										
											2021-10-04 15:24:19 +02:00
										 |  |  | 	apiNotif, err := p.tc.NotificationToAPINotification(ctx, notif) | 
					
						
							| 
									
										
										
										
											2021-06-19 11:18:55 +02:00
										 |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		return fmt.Errorf("notify: error converting notification to api representation: %w", err) | 
					
						
							| 
									
										
										
										
											2021-06-19 11:18:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-02-22 16:05:26 +01:00
										 |  |  | 	if err := p.stream.Notify(apiNotif, targetAccount); err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		return fmt.Errorf("notify: error streaming notification to account: %w", err) | 
					
						
							| 
									
										
										
										
											2021-06-19 11:18:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2021-05-27 16:06:24 +02:00
										 |  |  | 	return nil | 
					
						
							| 
									
										
										
										
											2021-05-24 18:49:48 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2021-05-28 19:57:04 +02:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | // wipeStatus contains common logic used to totally delete a status | 
					
						
							|  |  |  | // + all its attachments, notifications, boosts, and timeline entries. | 
					
						
							|  |  |  | func (p *Processor) wipeStatus(ctx context.Context, statusToDelete *gtsmodel.Status, deleteAttachments bool) error { | 
					
						
							|  |  |  | 	// either delete all attachments for this status, or simply | 
					
						
							|  |  |  | 	// unattach all attachments for this status, so they'll be | 
					
						
							|  |  |  | 	// cleaned later by a separate process; reason to unattach rather | 
					
						
							|  |  |  | 	// than delete is that the poster might want to reattach them | 
					
						
							|  |  |  | 	// to another status immediately (in case of delete + redraft) | 
					
						
							|  |  |  | 	if deleteAttachments { | 
					
						
							|  |  |  | 		// todo: p.state.DB.DeleteAttachmentsForStatus | 
					
						
							|  |  |  | 		for _, a := range statusToDelete.AttachmentIDs { | 
					
						
							|  |  |  | 			if err := p.media.Delete(ctx, a); err != nil { | 
					
						
							|  |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							|  |  |  | 	} else { | 
					
						
							|  |  |  | 		// todo: p.state.DB.UnattachAttachmentsForStatus | 
					
						
							|  |  |  | 		for _, a := range statusToDelete.AttachmentIDs { | 
					
						
							|  |  |  | 			if _, err := p.media.Unattach(ctx, statusToDelete.Account, a); err != nil { | 
					
						
							|  |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-05-31 17:36:35 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// delete all mention entries generated by this status | 
					
						
							|  |  |  | 	// todo: p.state.DB.DeleteMentionsForStatus | 
					
						
							|  |  |  | 	for _, id := range statusToDelete.MentionIDs { | 
					
						
							|  |  |  | 		if err := p.state.DB.DeleteMentionByID(ctx, id); err != nil { | 
					
						
							|  |  |  | 			return err | 
					
						
							| 
									
										
										
										
											2021-08-20 12:26:56 +02:00
										 |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-05-31 17:36:35 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// delete all notification entries generated by this status | 
					
						
							|  |  |  | 	if err := p.state.DB.DeleteNotificationsForStatus(ctx, statusToDelete.ID); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							| 
									
										
										
										
											2021-05-31 17:36:35 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// delete all bookmarks that point to this status | 
					
						
							|  |  |  | 	if err := p.state.DB.DeleteStatusBookmarksForStatus(ctx, statusToDelete.ID); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							| 
									
										
										
										
											2021-05-31 17:36:35 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// delete all faves of this status | 
					
						
							|  |  |  | 	if err := p.state.DB.DeleteStatusFavesForStatus(ctx, statusToDelete.ID); err != nil { | 
					
						
							|  |  |  | 		return err | 
					
						
							| 
									
										
										
										
											2021-05-31 17:36:35 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// delete all boosts for this status + remove them from timelines | 
					
						
							|  |  |  | 	if boosts, err := p.state.DB.GetStatusReblogs(ctx, statusToDelete); err == nil { | 
					
						
							|  |  |  | 		for _, b := range boosts { | 
					
						
							| 
									
										
										
										
											2023-06-11 11:18:44 +02:00
										 |  |  | 			if err := p.deleteStatusFromTimelines(ctx, b.ID); err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 			if err := p.state.DB.DeleteStatusByID(ctx, b.ID); err != nil { | 
					
						
							|  |  |  | 				return err | 
					
						
							|  |  |  | 			} | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2021-05-31 17:36:35 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// delete this status from any and all timelines | 
					
						
							| 
									
										
										
										
											2023-06-11 11:18:44 +02:00
										 |  |  | 	if err := p.deleteStatusFromTimelines(ctx, statusToDelete.ID); err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		return err | 
					
						
							| 
									
										
										
										
											2021-05-31 17:36:35 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 	// delete the status itself | 
					
						
							| 
									
										
										
										
											2023-06-03 13:58:57 +02:00
										 |  |  | 	return p.state.DB.DeleteStatusByID(ctx, statusToDelete.ID) | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // deleteStatusFromTimelines completely removes the given status from all timelines. | 
					
						
							|  |  |  | // It will also stream deletion of the status to all open streams. | 
					
						
							| 
									
										
										
										
											2023-06-11 11:18:44 +02:00
										 |  |  | func (p *Processor) deleteStatusFromTimelines(ctx context.Context, statusID string) error { | 
					
						
							|  |  |  | 	if err := p.state.Timelines.Home.WipeItemFromAllTimelines(ctx, statusID); err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		return err | 
					
						
							| 
									
										
										
										
											2021-06-19 11:18:55 +02:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-11 11:18:44 +02:00
										 |  |  | 	if err := p.state.Timelines.List.WipeItemFromAllTimelines(ctx, statusID); err != nil { | 
					
						
							| 
									
										
										
										
											2023-06-10 11:11:54 +02:00
										 |  |  | 		return err | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-06-11 11:18:44 +02:00
										 |  |  | 	return p.stream.Delete(statusID) | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | // invalidateStatusFromTimelines does cache invalidation on the given status by | 
					
						
							|  |  |  | // unpreparing it from all timelines, forcing it to be prepared again (with updated | 
					
						
							|  |  |  | // stats, boost counts, etc) next time it's fetched by the timeline owner. This goes | 
					
						
							|  |  |  | // both for the status itself, and for any boosts of the status. | 
					
						
							|  |  |  | func (p *Processor) invalidateStatusFromTimelines(ctx context.Context, statusID string) { | 
					
						
							|  |  |  | 	if err := p.state.Timelines.Home.UnprepareItemFromAllTimelines(ctx, statusID); err != nil { | 
					
						
							|  |  |  | 		log. | 
					
						
							|  |  |  | 			WithContext(ctx). | 
					
						
							|  |  |  | 			WithField("statusID", statusID). | 
					
						
							|  |  |  | 			Errorf("error unpreparing status from home timelines: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := p.state.Timelines.List.UnprepareItemFromAllTimelines(ctx, statusID); err != nil { | 
					
						
							|  |  |  | 		log. | 
					
						
							|  |  |  | 			WithContext(ctx). | 
					
						
							|  |  |  | 			WithField("statusID", statusID). | 
					
						
							|  |  |  | 			Errorf("error unpreparing status from list timelines: %v", err) | 
					
						
							|  |  |  | 	} | 
					
						
							| 
									
										
										
										
											2021-05-28 19:57:04 +02:00
										 |  |  | } | 
					
						
							| 
									
										
										
										
											2023-03-19 13:11:46 +01:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | /* | 
					
						
							|  |  |  | 	EMAIL FUNCTIONS | 
					
						
							|  |  |  | */ | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | func (p *Processor) emailReport(ctx context.Context, report *gtsmodel.Report) error { | 
					
						
							| 
									
										
										
										
											2023-03-19 13:11:46 +01:00
										 |  |  | 	instance, err := p.state.DB.GetInstance(ctx, config.GetHost()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		return fmt.Errorf("emailReport: error getting instance: %w", err) | 
					
						
							| 
									
										
										
										
											2023-03-19 13:11:46 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	toAddresses, err := p.state.DB.GetInstanceModeratorAddresses(ctx) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							|  |  |  | 		if errors.Is(err, db.ErrNoEntries) { | 
					
						
							|  |  |  | 			// No registered moderator addresses. | 
					
						
							|  |  |  | 			return nil | 
					
						
							|  |  |  | 		} | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		return fmt.Errorf("emailReport: error getting instance moderator addresses: %w", err) | 
					
						
							| 
									
										
										
										
											2023-03-19 13:11:46 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if report.Account == nil { | 
					
						
							|  |  |  | 		report.Account, err = p.state.DB.GetAccountByID(ctx, report.AccountID) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 			return fmt.Errorf("emailReport: error getting report account: %w", err) | 
					
						
							| 
									
										
										
										
											2023-03-19 13:11:46 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if report.TargetAccount == nil { | 
					
						
							|  |  |  | 		report.TargetAccount, err = p.state.DB.GetAccountByID(ctx, report.TargetAccountID) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 			return fmt.Errorf("emailReport: error getting report target account: %w", err) | 
					
						
							| 
									
										
										
										
											2023-03-19 13:11:46 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	reportData := email.NewReportData{ | 
					
						
							|  |  |  | 		InstanceURL:        instance.URI, | 
					
						
							|  |  |  | 		InstanceName:       instance.Title, | 
					
						
							|  |  |  | 		ReportURL:          instance.URI + "/settings/admin/reports/" + report.ID, | 
					
						
							|  |  |  | 		ReportDomain:       report.Account.Domain, | 
					
						
							|  |  |  | 		ReportTargetDomain: report.TargetAccount.Domain, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if err := p.emailSender.SendNewReportEmail(toAddresses, reportData); err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		return fmt.Errorf("emailReport: error emailing instance moderators: %w", err) | 
					
						
							| 
									
										
										
										
											2023-03-19 13:11:46 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return nil | 
					
						
							|  |  |  | } | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | func (p *Processor) emailReportClosed(ctx context.Context, report *gtsmodel.Report) error { | 
					
						
							| 
									
										
										
										
											2023-03-19 13:11:46 +01:00
										 |  |  | 	user, err := p.state.DB.GetUserByAccountID(ctx, report.Account.ID) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		return fmt.Errorf("emailReportClosed: db error getting user: %w", err) | 
					
						
							| 
									
										
										
										
											2023-03-19 13:11:46 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if user.ConfirmedAt.IsZero() || !*user.Approved || *user.Disabled || user.Email == "" { | 
					
						
							|  |  |  | 		// Only email users who: | 
					
						
							|  |  |  | 		// - are confirmed | 
					
						
							|  |  |  | 		// - are approved | 
					
						
							|  |  |  | 		// - are not disabled | 
					
						
							|  |  |  | 		// - have an email address | 
					
						
							|  |  |  | 		return nil | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	instance, err := p.state.DB.GetInstance(ctx, config.GetHost()) | 
					
						
							|  |  |  | 	if err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 		return fmt.Errorf("emailReportClosed: db error getting instance: %w", err) | 
					
						
							| 
									
										
										
										
											2023-03-19 13:11:46 +01:00
										 |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if report.Account == nil { | 
					
						
							|  |  |  | 		report.Account, err = p.state.DB.GetAccountByID(ctx, report.AccountID) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 			return fmt.Errorf("emailReportClosed: error getting report account: %w", err) | 
					
						
							| 
									
										
										
										
											2023-03-19 13:11:46 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	if report.TargetAccount == nil { | 
					
						
							|  |  |  | 		report.TargetAccount, err = p.state.DB.GetAccountByID(ctx, report.TargetAccountID) | 
					
						
							|  |  |  | 		if err != nil { | 
					
						
							| 
									
										
										
										
											2023-04-10 21:56:02 +02:00
										 |  |  | 			return fmt.Errorf("emailReportClosed: error getting report target account: %w", err) | 
					
						
							| 
									
										
										
										
											2023-03-19 13:11:46 +01:00
										 |  |  | 		} | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	reportClosedData := email.ReportClosedData{ | 
					
						
							|  |  |  | 		Username:             report.Account.Username, | 
					
						
							|  |  |  | 		InstanceURL:          instance.URI, | 
					
						
							|  |  |  | 		InstanceName:         instance.Title, | 
					
						
							|  |  |  | 		ReportTargetUsername: report.TargetAccount.Username, | 
					
						
							|  |  |  | 		ReportTargetDomain:   report.TargetAccount.Domain, | 
					
						
							|  |  |  | 		ActionTakenComment:   report.ActionTaken, | 
					
						
							|  |  |  | 	} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | 	return p.emailSender.SendReportClosedEmail(user.Email, reportClosedData) | 
					
						
							|  |  |  | } |