Add a public profile followers tab.
Expose follower lists on public profiles with a dedicated tab and API path instead of only showing counts.
This commit is contained in:
@@ -7,6 +7,7 @@ use App\Services\Activity\UserActivityService;
|
||||
use App\Events\Achievements\AchievementCheckRequested;
|
||||
use App\Services\FollowAnalyticsService;
|
||||
use App\Support\AvatarUrl;
|
||||
use Illuminate\Contracts\Pagination\LengthAwarePaginator;
|
||||
use Illuminate\Support\Collection;
|
||||
use Illuminate\Support\Facades\DB;
|
||||
|
||||
@@ -160,6 +161,38 @@ final class FollowService
|
||||
->value('followers_count');
|
||||
}
|
||||
|
||||
/**
|
||||
* People who follow $userId (user_id = the profile owner being followed).
|
||||
*
|
||||
* @return LengthAwarePaginator<int, array<string, mixed>>
|
||||
*/
|
||||
public function paginatedFollowers(int $userId, int $perPage = 24, int $page = 1): LengthAwarePaginator
|
||||
{
|
||||
return $this->paginateFollowRelationship(
|
||||
ownerColumn: 'uf.user_id',
|
||||
relatedUserColumn: 'uf.follower_id',
|
||||
ownerId: $userId,
|
||||
perPage: $perPage,
|
||||
page: $page,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* People $userId follows (follower_id = the profile owner).
|
||||
*
|
||||
* @return LengthAwarePaginator<int, array<string, mixed>>
|
||||
*/
|
||||
public function paginatedFollowing(int $userId, int $perPage = 24, int $page = 1): LengthAwarePaginator
|
||||
{
|
||||
return $this->paginateFollowRelationship(
|
||||
ownerColumn: 'uf.follower_id',
|
||||
relatedUserColumn: 'uf.user_id',
|
||||
ownerId: $userId,
|
||||
perPage: $perPage,
|
||||
page: $page,
|
||||
);
|
||||
}
|
||||
|
||||
public function followingCount(int $userId): int
|
||||
{
|
||||
return (int) DB::table('user_statistics')
|
||||
@@ -216,6 +249,58 @@ final class FollowService
|
||||
|
||||
// ─── Private helpers ─────────────────────────────────────────────────────
|
||||
|
||||
/**
|
||||
* @return LengthAwarePaginator<int, array<string, mixed>>
|
||||
*/
|
||||
private function paginateFollowRelationship(
|
||||
string $ownerColumn,
|
||||
string $relatedUserColumn,
|
||||
int $ownerId,
|
||||
int $perPage,
|
||||
int $page,
|
||||
): LengthAwarePaginator {
|
||||
$perPage = max(1, min($perPage, 100));
|
||||
$page = max(1, $page);
|
||||
|
||||
return DB::table('user_followers as uf')
|
||||
->join('users as u', 'u.id', '=', $relatedUserColumn)
|
||||
->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id')
|
||||
->where($ownerColumn, $ownerId)
|
||||
->whereNull('u.deleted_at')
|
||||
->orderByDesc('uf.created_at')
|
||||
->select([
|
||||
'u.id',
|
||||
'u.username',
|
||||
'u.name',
|
||||
'u.level',
|
||||
'u.rank',
|
||||
'up.avatar_hash',
|
||||
'uf.created_at as followed_at',
|
||||
])
|
||||
->paginate($perPage, ['*'], 'page', $page)
|
||||
->through(fn ($row) => $this->mapPublicFollowUser($row));
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, mixed>
|
||||
*/
|
||||
private function mapPublicFollowUser(object $row): array
|
||||
{
|
||||
$username = (string) ($row->username ?? '');
|
||||
|
||||
return [
|
||||
'id' => (int) $row->id,
|
||||
'username' => $username,
|
||||
'name' => (string) ($row->name ?? $username),
|
||||
'display_name' => $username !== '' ? $username : (string) ($row->name ?? ''),
|
||||
'avatar_url' => AvatarUrl::forUser((int) $row->id, $row->avatar_hash ?? null, 50),
|
||||
'profile_url' => '/@' . strtolower($username !== '' ? $username : (string) $row->id),
|
||||
'followed_at' => $row->followed_at,
|
||||
'level' => (int) ($row->level ?? 1),
|
||||
'rank' => (string) ($row->rank ?? 'Newbie'),
|
||||
];
|
||||
}
|
||||
|
||||
private function incrementCounter(int $userId, string $column): void
|
||||
{
|
||||
DB::table('user_statistics')->updateOrInsert(
|
||||
|
||||
Reference in New Issue
Block a user