diff --git a/app/Jobs/DeletePipeline/DeleteAccountPipeline.php b/app/Jobs/DeletePipeline/DeleteAccountPipeline.php index 24683e62..6b188aa8 100644 --- a/app/Jobs/DeletePipeline/DeleteAccountPipeline.php +++ b/app/Jobs/DeletePipeline/DeleteAccountPipeline.php @@ -138,6 +138,7 @@ class DeleteAccountPipeline implements ShouldQueue FollowerService::remove($follow->profile_id, $follow->following_id); $follow->delete(); }); + FollowerService::delCache($id); Like::whereProfileId($id)->forceDelete(); }); diff --git a/app/Jobs/FollowPipeline/FollowServiceWarmCache.php b/app/Jobs/FollowPipeline/FollowServiceWarmCache.php new file mode 100644 index 00000000..9fb53e4d --- /dev/null +++ b/app/Jobs/FollowPipeline/FollowServiceWarmCache.php @@ -0,0 +1,87 @@ +profileId = $profileId; + } + + /** + * Execute the job. + * + * @return void + */ + public function handle() + { + $id = $this->profileId; + + $account = AccountService::get($id, true); + + if(!$account) { + Cache::put(FollowerService::FOLLOWERS_SYNC_KEY . $id, 1); + Cache::put(FollowerService::FOLLOWING_SYNC_KEY . $id, 1); + return; + } + + DB::table('followers') + ->select('id', 'following_id', 'profile_id') + ->whereFollowingId($id) + ->orderBy('id') + ->chunk(200, function($followers) use($id) { + foreach($followers as $follow) { + FollowerService::add($follow->profile_id, $id); + } + }); + + DB::table('followers') + ->select('id', 'following_id', 'profile_id') + ->whereProfileId($id) + ->orderBy('id') + ->chunk(200, function($followers) use($id) { + foreach($followers as $follow) { + FollowerService::add($id, $follow->following_id); + } + }); + + Cache::put(FollowerService::FOLLOWERS_SYNC_KEY . $id, 1); + Cache::put(FollowerService::FOLLOWING_SYNC_KEY . $id, 1); + + $profile = Profile::find($id); + if($profile) { + $profile->following_count = DB::table('followers')->whereProfileId($id)->count(); + $profile->followers_count = DB::table('followers')->whereFollowingId($id)->count(); + $profile->save(); + } + + AccountService::del($id); + + return; + } +} diff --git a/app/Services/FollowerService.php b/app/Services/FollowerService.php index 45aeac24..b95af080 100644 --- a/app/Services/FollowerService.php +++ b/app/Services/FollowerService.php @@ -10,10 +10,12 @@ use App\{ Profile, User }; +use App\Jobs\FollowPipeline\FollowServiceWarmCache; class FollowerService { const CACHE_KEY = 'pf:services:followers:'; + const FOLLOWERS_SYNC_ACTIVE = 'pf:services:followers:sync-active:'; const FOLLOWERS_SYNC_KEY = 'pf:services:followers:sync-followers:'; const FOLLOWING_SYNC_KEY = 'pf:services:followers:sync-following:'; const FOLLOWING_KEY = 'pf:services:follow:following:id:'; @@ -38,19 +40,59 @@ class FollowerService public static function followers($id, $start = 0, $stop = 10) { self::cacheSyncCheck($id, 'followers'); - return Redis::zrange(self::FOLLOWERS_KEY . $id, $start, $stop); + return Redis::zrevrange(self::FOLLOWERS_KEY . $id, $start, $stop); } public static function following($id, $start = 0, $stop = 10) { self::cacheSyncCheck($id, 'following'); - return Redis::zrange(self::FOLLOWING_KEY . $id, $start, $stop); + return Redis::zrevrange(self::FOLLOWING_KEY . $id, $start, $stop); + } + + public static function followersPaginate($id, $page = 1, $limit = 10) + { + $start = $page == 1 ? 0 : $page * $limit - $limit; + $end = $start + ($limit - 1); + return self::followers($id, $start, $end); + } + + public static function followingPaginate($id, $page = 1, $limit = 10) + { + $start = $page == 1 ? 0 : $page * $limit - $limit; + $end = $start + ($limit - 1); + return self::following($id, $start, $end); + } + + public static function followerCount($id, $warmCache = true) + { + if($warmCache) { + self::cacheSyncCheck($id, 'followers'); + } + return Redis::zCard(self::FOLLOWERS_KEY . $id); + } + + public static function followingCount($id, $warmCache = true) + { + if($warmCache) { + self::cacheSyncCheck($id, 'following'); + } + return Redis::zCard(self::FOLLOWING_KEY . $id); } public static function follows(string $actor, string $target) { - self::cacheSyncCheck($target, 'followers'); - return (bool) Redis::zScore(self::FOLLOWERS_KEY . $target, $actor); + if($actor == $target) { + return false; + } + + if(self::followerCount($target, false) && self::followingCount($actor, false)) { + self::cacheSyncCheck($target, 'followers'); + return (bool) Redis::zScore(self::FOLLOWERS_KEY . $target, $actor); + } else { + self::cacheSyncCheck($target, 'followers'); + self::cacheSyncCheck($actor, 'following'); + return Follower::whereProfileId($actor)->whereFollowingId($target)->exists(); + } } public static function cacheSyncCheck($id, $scope = 'followers') @@ -59,21 +101,25 @@ class FollowerService if(Cache::get(self::FOLLOWERS_SYNC_KEY . $id) != null) { return; } - $followers = Follower::whereFollowingId($id)->pluck('profile_id'); - $followers->each(function($fid) use($id) { - self::add($fid, $id); - }); - Cache::put(self::FOLLOWERS_SYNC_KEY . $id, 1, 604800); + + if(Cache::get(self::FOLLOWERS_SYNC_ACTIVE . $id) != null) { + return; + } + + FollowServiceWarmCache::dispatch($id)->onQueue('low'); + Cache::put(self::FOLLOWERS_SYNC_ACTIVE . $id, 1, 604800); } if($scope === 'following') { if(Cache::get(self::FOLLOWING_SYNC_KEY . $id) != null) { return; } - $followers = Follower::whereProfileId($id)->pluck('following_id'); - $followers->each(function($fid) use($id) { - self::add($id, $fid); - }); - Cache::put(self::FOLLOWING_SYNC_KEY . $id, 1, 604800); + + if(Cache::get(self::FOLLOWERS_SYNC_ACTIVE . $id) != null) { + return; + } + + FollowServiceWarmCache::dispatch($id)->onQueue('low'); + Cache::put(self::FOLLOWERS_SYNC_ACTIVE . $id, 1, 604800); } return; } @@ -149,7 +195,8 @@ class FollowerService Redis::del(self::CACHE_KEY . $id); Redis::del(self::FOLLOWING_KEY . $id); Redis::del(self::FOLLOWERS_KEY . $id); - Redis::del(self::FOLLOWERS_SYNC_KEY . $id); - Redis::del(self::FOLLOWING_SYNC_KEY . $id); + Cache::forget(self::FOLLOWERS_SYNC_KEY . $id); + Cache::forget(self::FOLLOWING_SYNC_KEY . $id); + Cache::forget(self::FOLLOWERS_SYNC_ACTIVE . $id); } } diff --git a/app/Transformer/Api/AccountTransformer.php b/app/Transformer/Api/AccountTransformer.php index 1e8ab095..6c6fa17e 100644 --- a/app/Transformer/Api/AccountTransformer.php +++ b/app/Transformer/Api/AccountTransformer.php @@ -3,7 +3,9 @@ namespace App\Transformer\Api; use Auth; +use Cache; use App\Profile; +use App\User; use League\Fractal; use App\Services\PronounService; @@ -15,8 +17,16 @@ class AccountTransformer extends Fractal\TransformerAbstract public function transform(Profile $profile) { - $local = $profile->domain == null; - $is_admin = !$local ? false : $profile->user->is_admin; + if(!$profile) { + return []; + } + + $adminIds = Cache::remember('pf:admin-ids', 604800, function() { + return User::whereIsAdmin(true)->pluck('profile_id')->toArray(); + }); + + $local = $profile->private_key != null; + $is_admin = !$local ? false : in_array($profile->id, $adminIds); $acct = $local ? $profile->username : substr($profile->username, 1); $username = $local ? $profile->username : explode('@', $acct)[0]; return [ @@ -26,9 +36,9 @@ class AccountTransformer extends Fractal\TransformerAbstract 'display_name' => $profile->name, 'discoverable' => true, 'locked' => (bool) $profile->is_private, - 'followers_count' => (int) $profile->followerCount(), - 'following_count' => (int) $profile->followingCount(), - 'statuses_count' => (int) $profile->statusCount(), + 'followers_count' => (int) $profile->followers_count, + 'following_count' => (int) $profile->following_count, + 'statuses_count' => (int) $profile->status_count, 'note' => $profile->bio ?? '', 'note_text' => $profile->bio ? strip_tags($profile->bio) : null, 'url' => $profile->url(),