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(); } }