"} so the frontend can * trigger the actual browser download. * * The frontend fires this POST on click, then uses the returned URL to * trigger the file download (or falls back to the pre-resolved URL it * already has). */ final class ArtworkDownloadController extends Controller { public function __construct(private readonly ArtworkStatsService $stats) {} public function __invoke(Request $request, int $id): JsonResponse { $artwork = Artwork::public() ->published() ->with(['user:id']) ->where('id', $id) ->first(); if (! $artwork) { return response()->json(['error' => 'Not found'], 404); } // Record the download event — non-blocking, errors are swallowed. $this->recordDownload($request, $artwork); // Increment counters — deferred via Redis when available. $this->stats->incrementDownloads((int) $artwork->id, 1, defer: true); // Resolve the highest-resolution download URL available. $url = $this->resolveDownloadUrl($artwork); return response()->json(['ok' => true, 'url' => $url]); } /** * Insert a row in artwork_downloads. * Uses a raw insert for the binary(16) IP column. * Silently ignores failures (analytics should never break user flow). */ private function recordDownload(Request $request, Artwork $artwork): void { try { $ip = $request->ip() ?? '0.0.0.0'; $bin = @inet_pton($ip); DB::table('artwork_downloads')->insert([ 'artwork_id' => $artwork->id, 'user_id' => $request->user()?->id, 'ip' => $bin !== false ? $bin : null, 'user_agent' => mb_substr((string) $request->userAgent(), 0, 512), 'created_at' => now(), ]); } catch (\Throwable) { // Analytics failure must never interrupt the download. } } /** * Resolve the best available download URL: XL → LG → MD. * Returns an empty string if no thumbnail can be resolved. */ private function resolveDownloadUrl(Artwork $artwork): string { foreach (['xl', 'lg', 'md'] as $size) { $thumb = ThumbnailPresenter::present($artwork, $size); if (! empty($thumb['url'])) { return (string) $thumb['url']; } } return ''; } }