with(['category', 'authors', 'summary'])->orderByDesc('created_at'); // Text search if ($search = trim((string) $request->get('q'))) { $query->where(function ($q) use ($search) { $q->where('title', 'like', "%{$search}%") ->orWhere('description', 'like', "%{$search}%"); }); } // Category filter (by id or slug) if ($cat = $request->get('category')) { if (is_numeric($cat)) { $query->where('category_id', (int) $cat); } else { $query->whereHas('category', function ($q) use ($cat) { $q->where('slug', $cat)->orWhere('name', 'like', "%{$cat}%"); }); } } // Author filter (by id, slug, or name) if ($author = $request->get('author')) { $query->whereHas('authors', function ($q) use ($author) { if (is_numeric($author)) { $q->where('authors.id', (int) $author); } else { $q->where('authors.slug', $author) ->orWhere('authors.name', 'like', "%{$author}%"); } }); } // Publication date range if ($from = $request->get('date_from')) { $query->whereDate('publication_date', '>=', $from); } if ($to = $request->get('date_to')) { $query->whereDate('publication_date', '<=', $to); } $books = $query->paginate(12)->withQueryString(); // For filter dropdowns $categories = Category::orderBy('name')->get(); $authors = Author::orderBy('name')->get(); return view('books.index', [ 'books' => $books, 'categories' => $categories, 'authors' => $authors, 'filters' => [ 'q' => $request->get('q'), 'category' => $request->get('category'), 'author' => $request->get('author'), 'date_from' => $request->get('date_from'), 'date_to' => $request->get('date_to'), ], ]); } /** * Show the form for creating a new resource. */ public function create() { $categories = Category::all(); return view('books.create', compact('categories')); } /** * Store a newly created resource in storage. */ public function store(Request $request) { $request->validate([ 'title' => 'required|string|max:255', 'author' => 'required|string|max:255', 'description' => 'nullable|string', 'publication_date' => 'nullable|date', 'cover_image' => 'nullable|string|max:255', 'category_id' => 'required|exists:categories,id', ]); // Generate a unique slug for the title $baseSlug = Str::slug($request->title); $slug = $baseSlug; $i = 2; while (Book::where('slug', $slug)->exists()) { $slug = $baseSlug . '-' . $i; $i++; } $book = Book::create([ 'title' => $request->title, 'slug' => $slug, 'author' => $request->author, 'description' => $request->description, 'publication_date' => $request->publication_date, 'cover_image' => $request->cover_image, 'category_id' => $request->category_id, ]); // Normalize author: link to authors table via pivot if (!empty($request->author)) { $name = trim($request->author); $slug = Str::slug($name); $author = Author::where('slug', $slug)->first(); if (!$author) { $author = Author::where('name', $name)->first(); } if ($author) { $author->fill(['name' => $name, 'slug' => $author->slug ?: $slug])->save(); } else { $author = Author::create(['name' => $name, 'slug' => $slug]); } $book->authors()->sync([$author->id]); } return redirect()->route('books.index') ->with('success', 'Book created successfully.'); } /** * Display the specified resource. */ public function show(Book $book) { $book->load(['summary', 'category', 'authors', 'reviews.user']); $avgRating = (float) round($book->reviews()->avg('rating') ?? 0, 1); $reviewsCount = (int) $book->reviews()->count(); $userReview = auth()->check() ? $book->reviews()->where('user_id', auth()->id())->first() : null; return view('books.show', [ 'book' => $book, 'avgRating' => $avgRating, 'reviewsCount' => $reviewsCount, 'userReview' => $userReview, ]); } /** * Show the form for editing the specified resource. */ public function edit(Book $book) { $categories = Category::all(); return view('books.edit', compact('book', 'categories')); } /** * Update the specified resource in storage. */ public function update(Request $request, Book $book) { $request->validate([ 'title' => 'required|string|max:255', 'author' => 'required|string|max:255', 'description' => 'nullable|string', 'publication_date' => 'nullable|date', 'cover_image' => 'nullable|string|max:255', 'category_id' => 'required|exists:categories,id', ]); // If title changed, compute a unique slug (preserving current slug if still matching) $baseSlug = Str::slug($request->title); $slug = $baseSlug; if ($book->slug !== $baseSlug) { $i = 2; while (Book::where('slug', $slug)->where('id', '!=', $book->id)->exists()) { $slug = $baseSlug . '-' . $i; $i++; } } else { // Ensure no collision with other records even if same base if (Book::where('slug', $baseSlug)->where('id', '!=', $book->id)->exists()) { $i = 2; $slug = $baseSlug . '-' . $i; while (Book::where('slug', $slug)->where('id', '!=', $book->id)->exists()) { $i++; $slug = $baseSlug . '-' . $i; } } } $book->update([ 'title' => $request->title, 'slug' => $slug, 'author' => $request->author, 'description' => $request->description, 'publication_date' => $request->publication_date, 'cover_image' => $request->cover_image, 'category_id' => $request->category_id, ]); // Normalize author: sync pivot to the single provided author if (!empty($request->author)) { $name = trim($request->author); $slug = Str::slug($name); $author = Author::where('slug', $slug)->first(); if (!$author) { $author = Author::where('name', $name)->first(); } if ($author) { $author->fill(['name' => $name, 'slug' => $author->slug ?: $slug])->save(); } else { $author = Author::create(['name' => $name, 'slug' => $slug]); } $book->authors()->sync([$author->id]); } else { // If author field is cleared, detach all author links $book->authors()->detach(); } return redirect()->route('books.index') ->with('success', 'Book updated successfully.'); } /** * Remove the specified resource from storage. */ public function destroy(Book $book) { $book->delete(); return redirect()->route('books.index') ->with('success', 'Book deleted successfully.'); } }