getStatistics(); $recentImports = $this->getRecentImports(); $recentFailures = $this->getRecentFailures(); return view('admin.gutendex.dashboard', compact('stats', 'recentImports', 'recentFailures')); } /** * Display processing status. */ public function status() { $stats = $this->getStatistics(); $processing = Summary::processing()->with('book')->get(); $pending = Summary::pending()->with('book')->limit(20)->get(); $failed = Summary::failed()->with('book')->limit(20)->get(); $queueStats = $this->getQueueStatistics(); return view('admin.gutendex.status', compact('stats', 'processing', 'pending', 'failed', 'queueStats')); } /** * Trigger a new import. */ public function import(Request $request) { $request->validate([ 'language' => 'required|string|max:10', 'limit' => 'required|integer|min:1|max:500', 'page' => 'required|integer|min:1', 'generate_summaries' => 'boolean', ]); $language = $request->input('language', 'en'); $limit = (int) $request->input('limit', 50); $page = (int) $request->input('page', 1); $generateSummaries = $request->boolean('generate_summaries', true); $filters = [ 'languages' => $language, 'copyright' => 'false', ]; // Calculate end page $booksPerPage = 32; $endPage = $page + (int) ceil($limit / $booksPerPage) - 1; // Dispatch batch import job BatchImportFromGutendex::dispatch( $filters, $page, $endPage, $limit, $generateSummaries ); return response()->json([ 'success' => true, 'message' => "Import job dispatched for up to {$limit} books", 'details' => [ 'language' => $language, 'limit' => $limit, 'start_page' => $page, 'end_page' => $endPage, 'generate_summaries' => $generateSummaries, ], ]); } /** * Retry a failed book summary. */ public function retry(Book $book) { if (!$book->summary) { return response()->json([ 'success' => false, 'message' => 'Book does not have a summary record', ], 404); } if ($book->summary->processing_status === 'completed') { return response()->json([ 'success' => false, 'message' => 'Summary is already completed', ], 400); } // Dispatch job to regenerate summary GenerateSummary::dispatch($book->id); return response()->json([ 'success' => true, 'message' => "Retry job dispatched for: {$book->title}", 'book_id' => $book->id, ]); } /** * Get overall statistics. */ protected function getStatistics(): array { $totalBooks = Book::count(); $gutendexBooks = Book::gutendex()->count(); $booksWithText = Book::whereNotNull('full_text_url')->count(); $booksWithoutSummaries = Book::gutendex() ->whereNotNull('full_text_url') ->doesntHave('summary') ->count(); $totalSummaries = Summary::count(); $completedSummaries = Summary::completed()->count(); $pendingSummaries = Summary::pending()->count(); $processingSummaries = Summary::processing()->count(); $failedSummaries = Summary::failed()->count(); $completionRate = $totalSummaries > 0 ? round(($completedSummaries / $totalSummaries) * 100, 2) : 0; return [ 'total_books' => $totalBooks, 'gutendex_books' => $gutendexBooks, 'books_with_text' => $booksWithText, 'books_without_summaries' => $booksWithoutSummaries, 'total_summaries' => $totalSummaries, 'completed_summaries' => $completedSummaries, 'pending_summaries' => $pendingSummaries, 'processing_summaries' => $processingSummaries, 'failed_summaries' => $failedSummaries, 'completion_rate' => $completionRate, ]; } /** * Get recent imports. */ protected function getRecentImports(int $limit = 10): array { return Book::gutendex() ->with('category') ->orderByDesc('created_at') ->limit($limit) ->get() ->toArray(); } /** * Get recent failures. */ protected function getRecentFailures(int $limit = 10): array { return Summary::failed() ->with('book') ->orderByDesc('updated_at') ->limit($limit) ->get() ->toArray(); } /** * Get queue statistics. */ protected function getQueueStatistics(): array { try { $pendingJobs = DB::table('jobs')->count(); $failedJobs = DB::table('failed_jobs')->count(); return [ 'pending_jobs' => $pendingJobs, 'failed_jobs' => $failedJobs, ]; } catch (\Exception $e) { return [ 'pending_jobs' => 0, 'failed_jobs' => 0, ]; } } }