From 80d9b9399aabab22d83953a1634c08f0681d0003 Mon Sep 17 00:00:00 2001 From: Daniel Supernault Date: Thu, 7 Oct 2021 03:27:13 -0600 Subject: [PATCH] Refactor following & relationship logic. Replace FollowerObserver with FollowerService and added RelationshipService to cache results. Removed NotificationTransformer includes and replaced with cached services to improve performance and reduce database queries. --- app/Http/Controllers/Api/ApiV1Controller.php | 10 +- app/Http/Controllers/FollowerController.php | 7 +- app/Observers/FollowerObserver.php | 64 ---------- app/Providers/AppServiceProvider.php | 3 - app/Services/FollowerService.php | 2 + app/Services/RelationshipService.php | 86 +++++++++++++ .../Api/NotificationTransformer.php | 115 ++++++++---------- app/Util/ActivityPub/Inbox.php | 3 + 8 files changed, 151 insertions(+), 139 deletions(-) delete mode 100644 app/Observers/FollowerObserver.php create mode 100644 app/Services/RelationshipService.php diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index b6702c696..5e737e9cd 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -55,6 +55,7 @@ use App\Services\{ MediaPathService, PublicTimelineService, ProfileService, + RelationshipService, SearchApiV2Service, StatusService, MediaBlocklistService @@ -551,7 +552,7 @@ class ApiV1Controller extends Controller * * @param array|integer $id * - * @return \App\Transformer\Api\RelationshipTransformer + * @return \App\Services\RelationshipService */ public function accountRelationshipsById(Request $request) { @@ -563,12 +564,9 @@ class ApiV1Controller extends Controller ]); $pid = $request->user()->profile_id ?? $request->user()->profile->id; $ids = collect($request->input('id')); - $filtered = $ids->filter(function($v) use($pid) { - return $v != $pid; + $res = $ids->map(function($id) use($pid) { + return RelationshipService::get($pid, $id); }); - $relations = Profile::whereNull('status')->findOrFail($filtered->values()); - $fractal = new Fractal\Resource\Collection($relations, new RelationshipTransformer()); - $res = $this->fractal->createData($fractal)->toArray(); return response()->json($res); } diff --git a/app/Http/Controllers/FollowerController.php b/app/Http/Controllers/FollowerController.php index abc729197..97b96af75 100644 --- a/app/Http/Controllers/FollowerController.php +++ b/app/Http/Controllers/FollowerController.php @@ -12,6 +12,7 @@ use Auth, Cache; use Illuminate\Http\Request; use App\Jobs\FollowPipeline\FollowPipeline; use App\Util\ActivityPub\Helpers; +use App\Services\FollowerService; class FollowerController extends Controller { @@ -70,7 +71,9 @@ class FollowerController extends Controller ]); if($remote == true && config('federation.activitypub.remoteFollow') == true) { $this->sendFollow($user, $target); - } + } + + FollowerService::add($user->id, $target->id); } elseif ($private == false && $isFollowing == 0) { if($user->following()->count() >= Follower::MAX_FOLLOWING) { abort(400, 'You cannot follow more than ' . Follower::MAX_FOLLOWING . ' accounts'); @@ -87,6 +90,7 @@ class FollowerController extends Controller if($remote == true && config('federation.activitypub.remoteFollow') == true) { $this->sendFollow($user, $target); } + FollowerService::add($user->id, $target->id); FollowPipeline::dispatch($follower); } else { if($force == true) { @@ -101,6 +105,7 @@ class FollowerController extends Controller Follower::whereProfileId($user->id) ->whereFollowingId($target->id) ->delete(); + FollowerService::remove($user->id, $target->id); } } diff --git a/app/Observers/FollowerObserver.php b/app/Observers/FollowerObserver.php deleted file mode 100644 index afc476eeb..000000000 --- a/app/Observers/FollowerObserver.php +++ /dev/null @@ -1,64 +0,0 @@ -profile_id, $follower->following_id); - } - - /** - * Handle the Follower "updated" event. - * - * @param \App\Models\Follower $follower - * @return void - */ - public function updated(Follower $follower) - { - FollowerService::add($follower->profile_id, $follower->following_id); - } - - /** - * Handle the Follower "deleted" event. - * - * @param \App\Models\Follower $follower - * @return void - */ - public function deleted(Follower $follower) - { - FollowerService::remove($follower->profile_id, $follower->following_id); - } - - /** - * Handle the Follower "restored" event. - * - * @param \App\Models\Follower $follower - * @return void - */ - public function restored(Follower $follower) - { - FollowerService::add($follower->profile_id, $follower->following_id); - } - - /** - * Handle the Follower "force deleted" event. - * - * @param \App\Models\Follower $follower - * @return void - */ - public function forceDeleted(Follower $follower) - { - FollowerService::remove($follower->profile_id, $follower->following_id); - } -} diff --git a/app/Providers/AppServiceProvider.php b/app/Providers/AppServiceProvider.php index f2baf4c88..a4dfbe27b 100644 --- a/app/Providers/AppServiceProvider.php +++ b/app/Providers/AppServiceProvider.php @@ -4,7 +4,6 @@ namespace App\Providers; use App\Observers\{ AvatarObserver, - FollowerObserver, LikeObserver, NotificationObserver, ModLogObserver, @@ -15,7 +14,6 @@ use App\Observers\{ }; use App\{ Avatar, - Follower, Like, Notification, ModLog, @@ -50,7 +48,6 @@ class AppServiceProvider extends ServiceProvider StatusHashtag::observe(StatusHashtagObserver::class); User::observe(UserObserver::class); UserFilter::observe(UserFilterObserver::class); - Follower::observe(FollowerObserver::class); Horizon::auth(function ($request) { return Auth::check() && $request->user()->is_admin; }); diff --git a/app/Services/FollowerService.php b/app/Services/FollowerService.php index eeede53ec..a96773cae 100644 --- a/app/Services/FollowerService.php +++ b/app/Services/FollowerService.php @@ -17,12 +17,14 @@ class FollowerService public static function add($actor, $target) { + RelationshipService::refresh($actor, $target); Redis::zadd(self::FOLLOWING_KEY . $actor, $target, $target); Redis::zadd(self::FOLLOWERS_KEY . $target, $actor, $actor); } public static function remove($actor, $target) { + RelationshipService::refresh($actor, $target); Redis::zrem(self::FOLLOWING_KEY . $actor, $target); Redis::zrem(self::FOLLOWERS_KEY . $target, $actor); Cache::forget('pf:services:follow:audience:' . $actor); diff --git a/app/Services/RelationshipService.php b/app/Services/RelationshipService.php new file mode 100644 index 000000000..1237c0eb8 --- /dev/null +++ b/app/Services/RelationshipService.php @@ -0,0 +1,86 @@ + (string) $tid, + 'following' => Follower::whereProfileId($aid)->whereFollowingId($tid)->exists(), + 'followed_by' => Follower::whereProfileId($tid)->whereFollowingId($aid)->exists(), + 'blocking' => UserFilter::whereUserId($aid) + ->whereFilterableType('App\Profile') + ->whereFilterableId($tid) + ->whereFilterType('block') + ->exists(), + 'muting' => UserFilter::whereUserId($aid) + ->whereFilterableType('App\Profile') + ->whereFilterableId($tid) + ->whereFilterType('mute') + ->exists(), + 'muting_notifications' => null, + 'requested' => FollowRequest::whereFollowerId($aid) + ->whereFollowingId($tid) + ->exists(), + 'domain_blocking' => null, + 'showing_reblogs' => null, + 'endorsed' => false + ]; + }); + } + + public static function delete($aid, $tid) + { + return Cache::forget(self::key("a_{$aid}:t_{$tid}")); + } + + public static function refresh($aid, $tid) + { + self::delete($tid, $aid); + self::delete($aid, $tid); + self::get($tid, $aid); + return self::get($aid, $tid); + } + + public static function defaultRelation($tid) + { + return [ + 'id' => (string) $tid, + 'following' => false, + 'followed_by' => false, + 'blocking' => false, + 'muting' => false, + 'muting_notifications' => null, + 'requested' => false, + 'domain_blocking' => null, + 'showing_reblogs' => null, + 'endorsed' => false + ]; + } + + protected static function key($suffix) + { + return self::CACHE_KEY . $suffix; + } +} diff --git a/app/Transformer/Api/NotificationTransformer.php b/app/Transformer/Api/NotificationTransformer.php index 8a7870e7b..d9e4e5152 100644 --- a/app/Transformer/Api/NotificationTransformer.php +++ b/app/Transformer/Api/NotificationTransformer.php @@ -2,50 +2,65 @@ namespace App\Transformer\Api; -use App\{ - Notification, - Status -}; +use App\Notification; +use App\Services\AccountService; use App\Services\HashidService; +use App\Services\StatusService; use League\Fractal; class NotificationTransformer extends Fractal\TransformerAbstract { protected $defaultIncludes = [ - 'account', - 'status', - 'relationship', - 'modlog', - 'tagged' + // 'relationship', ]; public function transform(Notification $notification) { - return [ + $res = [ 'id' => (string) $notification->id, 'type' => $this->replaceTypeVerb($notification->action), 'created_at' => (string) $notification->created_at->format('c'), ]; - } - public function includeAccount(Notification $notification) - { - return $this->item($notification->actor, new AccountTransformer()); - } + $n = $notification; + if($n->item_id && $n->item_type == 'App\Status' && in_array($n->action, ['group:comment'])) { + $status = $n->status; + $res['group_id'] = $status->group_id; - public function includeStatus(Notification $notification) - { - $item = $notification; - if($item->item_id && $item->item_type == 'App\Status') { - $status = Status::with('media')->find($item->item_id); - if($status) { - return $this->item($status, new StatusTransformer()); - } else { - return null; + if($n->action == 'group:comment') { + $res['group_post_url'] = GroupPost::whereStatusId($status->id)->first()->url(); } - } else { - return null; } + + if(in_array($n->action, ['group.join.approved', 'group.join.rejected', 'group.like'])) { + $res['group'] = GroupService::get($n->item_id); + } + + if($n->actor_id) { + $res['account'] = AccountService::get($n->actor_id); + } + + if($n->item_id && $n->item_type == 'App\Status') { + $res['status'] = StatusService::get($n->item_id, false); + } + + if($n->item_id && $n->item_type == 'App\ModLog') { + $ml = $n->item; + $res['modlog'] = [ + 'id' => $ml->object_uid, + 'url' => url('/i/admin/users/modlogs/' . $ml->object_uid) + ]; + } + + if($n->item_id && $n->item_type == 'App\MediaTag') { + $ml = $n->item; + $res['tagged'] = [ + 'username' => $ml->tagged_username, + 'post_url' => '/p/'.HashidService::encode($ml->status_id) + ]; + } + + return $res; } public function replaceTypeVerb($verb) @@ -57,13 +72,21 @@ class NotificationTransformer extends Fractal\TransformerAbstract 'reblog' => 'share', 'share' => 'share', 'like' => 'favourite', + 'group:like' => 'favourite', 'comment' => 'comment', 'admin.user.modlog.comment' => 'modlog', 'tagged' => 'tagged', 'group:comment' => 'group:comment', 'story:react' => 'story:react', - 'story:comment' => 'story:comment' + 'story:comment' => 'story:comment', + 'group:join:approved' => 'group:join:approved', + 'group:join:rejected' => 'group:join:rejected' ]; + + if(!isset($verbs[$verb])) { + return $verb; + } + return $verbs[$verb]; } @@ -71,42 +94,4 @@ class NotificationTransformer extends Fractal\TransformerAbstract { return $this->item($notification->actor, new RelationshipTransformer()); } - - public function includeModlog(Notification $notification) - { - $n = $notification; - if($n->item_id && $n->item_type == 'App\ModLog') { - $ml = $n->item; - if(!empty($ml)) { - $res = $this->item($ml, function($ml) { - return [ - 'id' => $ml->object_uid, - 'url' => url('/i/admin/users/modlogs/' . $ml->object_uid) - ]; - }); - return $res; - } else { - return null; - } - } else { - return null; - } - } - - public function includeTagged(Notification $notification) - { - $n = $notification; - if($n->item_id && $n->item_type == 'App\MediaTag') { - $ml = $n->item; - $res = $this->item($ml, function($ml) { - return [ - 'username' => $ml->tagged_username, - 'post_url' => '/p/'.HashidService::encode($ml->status_id) - ]; - }); - return $res; - } else { - return null; - } - } } diff --git a/app/Util/ActivityPub/Inbox.php b/app/Util/ActivityPub/Inbox.php index fda0a78c5..31b6e89f2 100644 --- a/app/Util/ActivityPub/Inbox.php +++ b/app/Util/ActivityPub/Inbox.php @@ -455,6 +455,7 @@ class Inbox Cache::forget('profile:follower_count:'.$actor->id); Cache::forget('profile:following_count:'.$target->id); Cache::forget('profile:following_count:'.$actor->id); + FollowerService::add($actor->id, $target->id); } else { $follower = new Follower; @@ -464,6 +465,7 @@ class Inbox $follower->save(); FollowPipeline::dispatch($follower); + FollowerService::add($actor->id, $target->id); // send Accept to remote profile $accept = [ @@ -722,6 +724,7 @@ class Inbox ->whereItemId($following->id) ->whereItemType('App\Profile') ->forceDelete(); + FollowerService::remove($profile->id, $following->id); break; case 'Like':