all(); // Normalize to array of items $items = []; if (isset($payload['books']) && is_array($payload['books'])) { $items = $payload['books']; } elseif ($this->isAssocArray($payload)) { $items = [$payload]; } elseif (is_array($payload)) { $items = $payload; } if (empty($items)) { return response()->json([ 'message' => 'No books provided.', ], 422); } $results = []; foreach ($items as $index => $item) { // Basic validation (minimal to satisfy requirement) $title = $item['title'] ?? null; $author = $item['author'] ?? null; $authorsArr = $item['authors'] ?? null; // optional array of names $hasAuthorInfo = $author || (is_array($authorsArr) && count($authorsArr) > 0); if (!$title || !$hasAuthorInfo) { $results[] = [ 'index' => $index, 'status' => 'error', 'message' => 'title and author/authors are required.', ]; continue; } $result = DB::transaction(function () use ($item) { // Handle category upsert $categoryData = $item['category'] ?? null; $category = null; if ($categoryData) { $catSlug = $categoryData['slug'] ?? null; $catName = $categoryData['name'] ?? null; if (!$catSlug && $catName) { $catSlug = Str::slug($catName); } if ($catSlug) { $category = Category::where('slug', $catSlug)->first(); } if (!$category && $catName) { // Try by name $category = Category::where('name', $catName)->first(); } if ($category) { // Update existing $category->fill([ 'name' => $catName ?? $category->name, 'slug' => $catSlug ?? ($catName ? Str::slug($catName) : $category->slug), 'description' => $categoryData['description'] ?? $category->description, ])->save(); $categoryStatus = 'updated'; } else { // Create new if enough data $category = Category::create([ 'name' => $catName ?? 'Uncategorized', 'slug' => $catSlug ?? ($catName ? Str::slug($catName) : Str::slug('Uncategorized')), 'description' => $categoryData['description'] ?? null, ]); $categoryStatus = 'created'; } } // Handle book upsert $bookSlug = $item['slug'] ?? null; if (!$bookSlug && !empty($item['title'])) { $bookSlug = Str::slug($item['title']); } // Determine a primary author name for backward-compatible matching $primaryAuthorName = $item['author'] ?? (is_array($item['authors'] ?? null) && count($item['authors']) > 0 ? ($item['authors'][0] ?? null) : null); $bookQuery = Book::query(); if ($bookSlug) { $bookQuery->where('slug', $bookSlug); } else { // Fallback: match by title+author (string). If authors[] provided, use first one. $bookQuery->where('title', $item['title'])->where('author', $primaryAuthorName); } $book = $bookQuery->first(); $bookData = [ 'title' => $item['title'] ?? ($book->title ?? null), 'slug' => $bookSlug ?? ($book->slug ?? null), 'author' => $primaryAuthorName ?? ($book->author ?? null), 'description' => $item['description'] ?? ($book->description ?? null), 'publication_date' => $item['publication_date'] ?? ($book->publication_date ?? null), 'cover_image' => $item['cover_image'] ?? ($book->cover_image ?? null), ]; if ($category) { $bookData['category_id'] = $category->id; } elseif (!empty($item['category_id'])) { $bookData['category_id'] = $item['category_id']; } if ($book) { $book->fill($bookData)->save(); $bookStatus = 'updated'; } else { $book = Book::create($bookData); $bookStatus = 'created'; } // Handle authors upsert and pivot sync $names = []; if (!empty($item['author'])) { $names[] = $item['author']; } if (!empty($item['authors']) && is_array($item['authors'])) { $names = array_merge($names, $item['authors']); } $names = array_values(array_unique(array_filter(array_map(function ($n) { return is_string($n) ? trim($n) : null; }, $names)))); if (count($names) > 0) { $authorIds = []; foreach ($names as $name) { $slug = Str::slug($name); $author = Author::where('slug', $slug)->first(); if (!$author) { $author = Author::where('name', $name)->first(); } if ($author) { // ensure slug and name are up to date $author->fill([ 'name' => $name, 'slug' => $author->slug ?: $slug, ])->save(); } else { $author = Author::create([ 'name' => $name, 'slug' => $slug, ]); } $authorIds[] = $author->id; } // Replace current authors with provided set $book->authors()->sync($authorIds); } // Handle summary upsert $summaryData = $item['summary'] ?? null; $summaryResult = null; if ($summaryData && is_array($summaryData)) { $summary = Summary::where('book_id', $book->id)->first(); $sumData = [ 'content' => $summaryData['content'] ?? ($summary->content ?? null), 'key_insights' => $summaryData['key_insights'] ?? ($summary->key_insights ?? null), 'book_id' => $book->id, ]; if ($summary) { $summary->fill($sumData)->save(); $summaryResult = ['status' => 'updated', 'id' => $summary->id]; } else { $summary = Summary::create($sumData); $summaryResult = ['status' => 'created', 'id' => $summary->id]; } } return [ 'book' => [ 'status' => $bookStatus, 'id' => $book->id, 'slug' => $book->slug, ], 'category' => $category ? [ 'status' => $categoryStatus, 'id' => $category->id, 'slug' => $category->slug, ] : null, 'summary' => $summaryResult, ]; }); $results[] = $result; } return response()->json([ 'results' => $results, ]); } private function isAssocArray($arr): bool { if (!is_array($arr)) return false; return array_keys($arr) !== range(0, count($arr) - 1); } }