138 lines
5.3 KiB
PHP
138 lines
5.3 KiB
PHP
<?php
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Models\User;
|
|
use App\Services\FollowService;
|
|
use App\Support\AvatarUrl;
|
|
use App\Support\UsernamePolicy;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Support\Facades\Auth;
|
|
use Illuminate\Support\Facades\DB;
|
|
|
|
/**
|
|
* 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),
|
|
]);
|
|
}
|
|
|
|
// ─── 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),
|
|
]);
|
|
}
|
|
|
|
// ─── 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);
|
|
|
|
$rows = DB::table('user_followers as uf')
|
|
->join('users as u', 'u.id', '=', 'uf.follower_id')
|
|
->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id')
|
|
->where('uf.user_id', $target->id)
|
|
->whereNull('u.deleted_at')
|
|
->orderByDesc('uf.created_at')
|
|
->select([
|
|
'u.id', 'u.username', 'u.name',
|
|
'up.avatar_hash',
|
|
'uf.created_at as followed_at',
|
|
])
|
|
->paginate($perPage)
|
|
->through(fn ($row) => [
|
|
'id' => $row->id,
|
|
'username' => $row->username,
|
|
'display_name'=> $row->username ?? $row->name,
|
|
'avatar_url' => AvatarUrl::forUser((int) $row->id, $row->avatar_hash, 50),
|
|
'profile_url' => '/@' . strtolower((string) ($row->username ?? $row->id)),
|
|
'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);
|
|
|
|
$rows = DB::table('user_followers as uf')
|
|
->join('users as u', 'u.id', '=', 'uf.user_id')
|
|
->leftJoin('user_profiles as up', 'up.user_id', '=', 'u.id')
|
|
->where('uf.follower_id', $target->id)
|
|
->whereNull('u.deleted_at')
|
|
->orderByDesc('uf.created_at')
|
|
->select([
|
|
'u.id', 'u.username', 'u.name',
|
|
'up.avatar_hash',
|
|
'uf.created_at as followed_at',
|
|
])
|
|
->paginate($perPage)
|
|
->through(fn ($row) => [
|
|
'id' => $row->id,
|
|
'username' => $row->username,
|
|
'display_name'=> $row->username ?? $row->name,
|
|
'avatar_url' => AvatarUrl::forUser((int) $row->id, $row->avatar_hash, 50),
|
|
'profile_url' => '/@' . strtolower((string) ($row->username ?? $row->id)),
|
|
'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();
|
|
}
|
|
}
|