From 667f6e2fc9a5dbfbcc24b65771f25c2e83153eff Mon Sep 17 00:00:00 2001 From: Your Name Date: Wed, 2 Sep 2026 19:40:06 +0930 Subject: [PATCH] Extract following-ids lookup into FollowerService::getFollowingIds The Cache::remember('profile:following:'.$pid, ...) block that plucks following_id and appends the caller's own id was copy-pasted across four call sites, with inconsistent TTLs (1440 minutes vs 1209600 seconds). Add FollowerService::getFollowingIds($pid), which owns the cache key that add()/remove() already invalidate, and use it from InternalApiController, PublicApiController, ApiV1Controller and HashtagUnfollowPipeline. Removes the now-unused Follower/Cache imports left behind. Adds a test covering the followed-ids-plus-self result and the follows-nobody case. --- app/Http/Controllers/Api/ApiV1Controller.php | 6 +- .../Controllers/InternalApiController.php | 14 +---- app/Http/Controllers/PublicApiController.php | 7 +-- .../HashtagUnfollowPipeline.php | 9 +-- app/Services/FollowerService.php | 17 ++++++ .../FollowerServiceFollowingIdsTest.php | 55 +++++++++++++++++++ 6 files changed, 78 insertions(+), 30 deletions(-) create mode 100644 tests/Feature/FollowerServiceFollowingIdsTest.php diff --git a/app/Http/Controllers/Api/ApiV1Controller.php b/app/Http/Controllers/Api/ApiV1Controller.php index cdca682d6..491e2ff58 100644 --- a/app/Http/Controllers/Api/ApiV1Controller.php +++ b/app/Http/Controllers/Api/ApiV1Controller.php @@ -2802,11 +2802,7 @@ class ApiV1Controller extends Controller return $this->json($res->toArray(), 200, $headers); } - $following = Cache::remember('profile:following:'.$pid, 1209600, function () use ($pid) { - $following = Follower::whereProfileId($pid)->pluck('following_id'); - - return $following->push($pid)->toArray(); - }); + $following = FollowerService::getFollowingIds($pid); $muted = UserFilterService::mutes($pid); diff --git a/app/Http/Controllers/InternalApiController.php b/app/Http/Controllers/InternalApiController.php index d8a416ec4..59a345b57 100644 --- a/app/Http/Controllers/InternalApiController.php +++ b/app/Http/Controllers/InternalApiController.php @@ -7,7 +7,6 @@ use App\Models\AccountInterstitial; use App\Models\Bookmark; use App\Models\DirectMessage; use App\Models\DiscoverCategory; -use App\Models\Follower; use App\Models\Profile; use App\Models\Status; use App\Models\User; @@ -23,7 +22,6 @@ use Illuminate\Contracts\View\View; use Illuminate\Http\JsonResponse; use Illuminate\Http\RedirectResponse; use Illuminate\Http\Request; -use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Redis; use Illuminate\Validation\Rule; use League\Fractal; @@ -312,20 +310,12 @@ class InternalApiController extends Controller return response()->json([]); } $pid = $request->user()->profile->id; - $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function () use ($pid) { - $following = Follower::whereProfileId($pid)->pluck('following_id'); - - return $following->push($pid)->toArray(); - }); + $following = FollowerService::getFollowingIds($pid); $visibility = in_array($profile->id, $following) == true ? ['public', 'unlisted', 'private'] : []; } else { if ($request->user() !== null) { $pid = $request->user()->profile->id; - $following = Cache::remember('profile:following:'.$pid, now()->addMinutes(1440), function () use ($pid) { - $following = Follower::whereProfileId($pid)->pluck('following_id'); - - return $following->push($pid)->toArray(); - }); + $following = FollowerService::getFollowingIds($pid); $visibility = in_array($profile->id, $following) == true ? ['public', 'unlisted', 'private'] : ['public', 'unlisted']; } else { $visibility = ['public', 'unlisted']; diff --git a/app/Http/Controllers/PublicApiController.php b/app/Http/Controllers/PublicApiController.php index 71cb3bec7..769d89726 100644 --- a/app/Http/Controllers/PublicApiController.php +++ b/app/Http/Controllers/PublicApiController.php @@ -2,7 +2,6 @@ namespace App\Http\Controllers; -use App\Models\Follower; use App\Models\Profile; use App\Models\Status; use App\Services\AccountService; @@ -390,11 +389,7 @@ class PublicApiController extends Controller $pid = $user->profile_id; - $following = Cache::remember('profile:following:'.$pid, 1209600, function () use ($pid) { - $following = Follower::whereProfileId($pid)->pluck('following_id'); - - return $following->push($pid)->toArray(); - }); + $following = FollowerService::getFollowingIds($pid); $filtered = $user ? UserFilterService::filters($user->profile_id) : []; $types = ['photo', 'photo:album', 'video', 'video:album', 'photo:video:album']; diff --git a/app/Jobs/HomeFeedPipeline/HashtagUnfollowPipeline.php b/app/Jobs/HomeFeedPipeline/HashtagUnfollowPipeline.php index acae0a847..4d99fc937 100644 --- a/app/Jobs/HomeFeedPipeline/HashtagUnfollowPipeline.php +++ b/app/Jobs/HomeFeedPipeline/HashtagUnfollowPipeline.php @@ -2,8 +2,8 @@ namespace App\Jobs\HomeFeedPipeline; -use App\Models\Follower; use App\Models\Hashtag; +use App\Services\FollowerService; use App\Services\HomeTimelineService; use App\Services\StatusService; use Illuminate\Bus\Queueable; @@ -11,7 +11,6 @@ use Illuminate\Contracts\Queue\ShouldQueue; use Illuminate\Foundation\Bus\Dispatchable; use Illuminate\Queue\InteractsWithQueue; use Illuminate\Queue\SerializesModels; -use Illuminate\Support\Facades\Cache; use Illuminate\Support\Facades\Log; class HashtagUnfollowPipeline implements ShouldQueue @@ -76,11 +75,7 @@ class HashtagUnfollowPipeline implements ShouldQueue $statusIds = HomeTimelineService::get($pid, 0, -1); - $followingIds = Cache::remember('profile:following:'.$pid, 1209600, function () use ($pid) { - $following = Follower::whereProfileId($pid)->pluck('following_id'); - - return $following->push($pid)->toArray(); - }); + $followingIds = FollowerService::getFollowingIds($pid); foreach ($statusIds as $id) { $status = StatusService::get($id, false); diff --git a/app/Services/FollowerService.php b/app/Services/FollowerService.php index 41ae0dadf..f3987aedc 100644 --- a/app/Services/FollowerService.php +++ b/app/Services/FollowerService.php @@ -79,6 +79,23 @@ class FollowerService return Redis::zrevrange(self::FOLLOWING_KEY.$id, $start, $stop); } + /** + * Return the profile ids a profile follows, including the profile's own + * id, as an array. Cached under the 'profile:following:{pid}' key, which + * is invalidated by add()/remove(). + * + * @return array + */ + public static function getFollowingIds($pid) + { + return Cache::remember('profile:following:'.$pid, 1209600, function () use ($pid) { + return Follower::whereProfileId($pid) + ->pluck('following_id') + ->push($pid) + ->toArray(); + }); + } + public static function followersPaginate($id, $page = 1, $limit = 10) { $start = $page == 1 ? 0 : $page * $limit - $limit; diff --git a/tests/Feature/FollowerServiceFollowingIdsTest.php b/tests/Feature/FollowerServiceFollowingIdsTest.php new file mode 100644 index 000000000..02cb58723 --- /dev/null +++ b/tests/Feature/FollowerServiceFollowingIdsTest.php @@ -0,0 +1,55 @@ +create(); + $user->refresh(); + $a = User::factory()->create(); + $a->refresh(); + $b = User::factory()->create(); + $b->refresh(); + + foreach ([$a->profile_id, $b->profile_id] as $targetId) { + $f = new Follower; + $f->profile_id = $user->profile_id; + $f->following_id = $targetId; + $f->save(); + } + + $ids = FollowerService::getFollowingIds($user->profile_id); + + expect($ids)->toContain($a->profile_id) + ->toContain($b->profile_id) + ->toContain($user->profile_id); +}); + +it('returns only the callers own id when following nobody', function () { + $user = User::factory()->create(); + $user->refresh(); + + $ids = FollowerService::getFollowingIds($user->profile_id); + + expect($ids)->toBe([$user->profile_id]); +});