resolveListType($request); $result = $this->ranking->getList('global', null, $listType); return $this->buildResponse($result, $listType); } /** * GET /api/rank/category/{id} */ public function byCategory(Request $request, int $id): AnonymousResourceCollection|JsonResponse { if (! Category::where('id', $id)->where('is_active', true)->exists()) { return response()->json(['message' => 'Category not found.'], 404); } $listType = $this->resolveListType($request); $result = $this->ranking->getList('category', $id, $listType); return $this->buildResponse($result, $listType); } /** * GET /api/rank/type/{contentType} * * {contentType} is accepted as either a slug (string) or numeric id. */ public function byContentType(Request $request, string $contentType): AnonymousResourceCollection|JsonResponse { $ct = is_numeric($contentType) ? ContentType::find((int) $contentType) : ContentType::where('slug', $contentType)->first(); if ($ct === null) { return response()->json(['message' => 'Content type not found.'], 404); } $listType = $this->resolveListType($request); $result = $this->ranking->getList('content_type', $ct->id, $listType); return $this->buildResponse($result, $listType); } // ── Private helpers ──────────────────────────────────────────────────── /** * Validate and normalise the ?type query param. * Defaults to 'trending'. */ private function resolveListType(Request $request): string { $allowed = ['trending', 'new_hot', 'best']; $type = $request->query('type', 'trending'); return in_array($type, $allowed, true) ? $type : 'trending'; } /** * Hydrate artwork IDs into Eloquent models (no N+1) and wrap in resources. * * @param array{ids: int[], computed_at: string|null, model_version: string, fallback: bool} $result */ private function buildResponse(array $result, string $listType = 'trending'): AnonymousResourceCollection { $ids = $result['ids']; $artworks = collect(); if (! empty($ids)) { // Single whereIn query — no N+1 $keyed = Artwork::whereIn('id', $ids) ->with([ 'user:id,name', 'categories' => function ($q): void { $q->select( 'categories.id', 'categories.content_type_id', 'categories.parent_id', 'categories.name', 'categories.slug', 'categories.sort_order' )->with(['parent:id,parent_id,content_type_id,name,slug', 'contentType:id,slug,name']); }, ]) ->get() ->keyBy('id'); // Restore the ranked order $artworks = collect($ids) ->filter(fn ($id) => $keyed->has($id)) ->map(fn ($id) => $keyed[$id]); } $collection = ArtworkListResource::collection($artworks); // Attach ranking meta as additional data $collection->additional([ 'meta' => [ 'list_type' => $listType, 'computed_at' => $result['computed_at'], 'model_version' => $result['model_version'], 'fallback' => $result['fallback'], 'count' => $artworks->count(), ], ]); return $collection; } }