Files
SkinbaseNova/app/Http/Controllers/Api/FollowController.php
T
klevze bf9ca12469 Add a public profile followers tab.
Expose follower lists on public profiles with a dedicated tab and API path instead of only showing counts.
2026-08-29 12:25:49 +02:00

122 lines
4.8 KiB
PHP

<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\User;
use App\Services\FollowService;
use App\Support\UsernamePolicy;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
/**
* API endpoints for the follow system.
*
* POST /api/user/{username}/follow → follow a user
* DELETE /api/user/{username}/follow → unfollow a user
* GET /api/user/{username}/followers → paginated followers list
* GET /api/user/{username}/following → paginated following list
*/
final class FollowController extends Controller
{
public function __construct(private readonly FollowService $followService) {}
// ─── POST /api/user/{username}/follow ────────────────────────────────────
public function follow(Request $request, string $username): JsonResponse
{
$target = $this->resolveUser($username);
$actor = Auth::user();
if ($actor->id === $target->id) {
return response()->json(['error' => 'Cannot follow yourself.'], 422);
}
try {
$this->followService->follow((int) $actor->id, (int) $target->id);
} catch (\InvalidArgumentException $e) {
return response()->json(['error' => $e->getMessage()], 422);
}
return response()->json([
'following' => true,
'followers_count' => $this->followService->followersCount((int) $target->id),
'following_count' => $this->followService->followingCount((int) $actor->id),
'context' => $this->followService->relationshipContext((int) $actor->id, (int) $target->id),
]);
}
// ─── DELETE /api/user/{username}/follow ──────────────────────────────────
public function unfollow(Request $request, string $username): JsonResponse
{
$target = $this->resolveUser($username);
$actor = Auth::user();
$this->followService->unfollow((int) $actor->id, (int) $target->id);
return response()->json([
'following' => false,
'followers_count' => $this->followService->followersCount((int) $target->id),
'following_count' => $this->followService->followingCount((int) $actor->id),
'context' => $this->followService->relationshipContext((int) $actor->id, (int) $target->id),
]);
}
// ─── GET /api/user/{username}/followers ──────────────────────────────────
public function followers(Request $request, string $username): JsonResponse
{
$target = $this->resolveUser($username);
$perPage = min((int) $request->query('per_page', 24), 100);
$page = max(1, (int) $request->query('page', 1));
$rows = $this->followService
->paginatedFollowers((int) $target->id, $perPage, $page)
->through(fn (array $row) => [
'id' => $row['id'],
'username' => $row['username'],
'display_name' => $row['display_name'],
'avatar_url' => $row['avatar_url'],
'profile_url' => $row['profile_url'],
'followed_at' => $row['followed_at'],
]);
return response()->json($rows);
}
// ─── GET /api/user/{username}/following ──────────────────────────────────
public function following(Request $request, string $username): JsonResponse
{
$target = $this->resolveUser($username);
$perPage = min((int) $request->query('per_page', 24), 100);
$page = max(1, (int) $request->query('page', 1));
$rows = $this->followService
->paginatedFollowing((int) $target->id, $perPage, $page)
->through(fn (array $row) => [
'id' => $row['id'],
'username' => $row['username'],
'display_name' => $row['display_name'],
'avatar_url' => $row['avatar_url'],
'profile_url' => $row['profile_url'],
'followed_at' => $row['followed_at'],
]);
return response()->json($rows);
}
// ─── Private helpers ─────────────────────────────────────────────────────
private function resolveUser(string $username): User
{
$normalized = UsernamePolicy::normalize($username);
return User::query()
->whereRaw('LOWER(username) = ?', [$normalized])
->firstOrFail();
}
}