2025-09-13 17:39:17 +03:00
|
|
|
<?php
|
|
|
|
|
|
|
|
|
|
namespace App\Http\Controllers;
|
|
|
|
|
|
|
|
|
|
use App\Models\Book;
|
|
|
|
|
use App\Models\Category;
|
2025-09-20 16:47:41 +03:00
|
|
|
use App\Models\Author;
|
2025-09-13 17:39:17 +03:00
|
|
|
use Illuminate\Http\Request;
|
|
|
|
|
use Illuminate\Support\Str;
|
|
|
|
|
|
|
|
|
|
class BookController extends Controller
|
|
|
|
|
{
|
|
|
|
|
/**
|
|
|
|
|
* Display a listing of the resource.
|
|
|
|
|
*/
|
2025-09-20 16:47:41 +03:00
|
|
|
public function index(Request $request)
|
2025-09-13 17:39:17 +03:00
|
|
|
{
|
2025-09-20 16:47:41 +03:00
|
|
|
$query = Book::query()->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'),
|
|
|
|
|
],
|
|
|
|
|
]);
|
2025-09-13 17:39:17 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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',
|
|
|
|
|
]);
|
|
|
|
|
|
2025-09-20 16:47:41 +03:00
|
|
|
// 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([
|
2025-09-13 17:39:17 +03:00
|
|
|
'title' => $request->title,
|
2025-09-20 16:47:41 +03:00
|
|
|
'slug' => $slug,
|
2025-09-13 17:39:17 +03:00
|
|
|
'author' => $request->author,
|
|
|
|
|
'description' => $request->description,
|
|
|
|
|
'publication_date' => $request->publication_date,
|
|
|
|
|
'cover_image' => $request->cover_image,
|
|
|
|
|
'category_id' => $request->category_id,
|
|
|
|
|
]);
|
|
|
|
|
|
2025-09-20 16:47:41 +03:00
|
|
|
// 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]);
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-13 17:39:17 +03:00
|
|
|
return redirect()->route('books.index')
|
|
|
|
|
->with('success', 'Book created successfully.');
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* Display the specified resource.
|
|
|
|
|
*/
|
|
|
|
|
public function show(Book $book)
|
|
|
|
|
{
|
2025-09-20 16:47:41 +03:00
|
|
|
$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,
|
|
|
|
|
]);
|
2025-09-13 17:39:17 +03:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 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',
|
|
|
|
|
]);
|
|
|
|
|
|
2025-09-20 16:47:41 +03:00
|
|
|
// 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;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-13 17:39:17 +03:00
|
|
|
$book->update([
|
|
|
|
|
'title' => $request->title,
|
2025-09-20 16:47:41 +03:00
|
|
|
'slug' => $slug,
|
2025-09-13 17:39:17 +03:00
|
|
|
'author' => $request->author,
|
|
|
|
|
'description' => $request->description,
|
|
|
|
|
'publication_date' => $request->publication_date,
|
|
|
|
|
'cover_image' => $request->cover_image,
|
|
|
|
|
'category_id' => $request->category_id,
|
|
|
|
|
]);
|
|
|
|
|
|
2025-09-20 16:47:41 +03:00
|
|
|
// 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();
|
|
|
|
|
}
|
|
|
|
|
|
2025-09-13 17:39:17 +03:00
|
|
|
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.');
|
|
|
|
|
}
|
|
|
|
|
}
|