202 lines
5.7 KiB
PHP
202 lines
5.7 KiB
PHP
<?php
|
|||
|
|
|
||
|
|
namespace App\Http\Controllers;
|
||
|
|
|
||
|
|
use App\Jobs\BatchImportFromGutendex;
|
||
|
|
use App\Jobs\GenerateSummary;
|
||
|
|
use App\Models\Book;
|
||
|
|
use App\Models\Summary;
|
||
|
|
use Illuminate\Http\Request;
|
||
|
|
use Illuminate\Support\Facades\DB;
|
||
|
|
|
||
|
|
class GutendexController extends Controller
|
||
|
|
{
|
||
|
|
/**
|
||
|
|
* Display the Gutendex admin dashboard.
|
||
|
|
*/
|
||
|
|
public function dashboard()
|
||
|
|
{
|
||
|
|
$stats = $this->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,
|
||
|
|
];
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|