Files
opentextsummaries/app/Http/Controllers/Api/IngestController.php
T

222 lines
8.9 KiB
PHP
Raw Normal View History

2025-09-13 17:39:17 +03:00
<?php
namespace App\Http\Controllers\Api;
use App\Http\Controllers\Controller;
use App\Models\Book;
use App\Models\Category;
use App\Models\Summary;
2025-09-20 16:47:41 +03:00
use App\Models\Author;
2025-09-13 17:39:17 +03:00
use Illuminate\Http\Request;
2025-09-27 16:33:31 +03:00
use Illuminate\Support\Facades\Log;
2025-09-13 17:39:17 +03:00
use Illuminate\Support\Str;
use Illuminate\Support\Facades\DB;
2025-09-27 16:33:31 +03:00
use Psr\Log\LogLevel;
2025-09-13 17:39:17 +03:00
class IngestController extends Controller
{
/**
* Upsert books with summaries and categories.
* Accepts either a single book object or an array of books under `books`.
*/
public function upsertBooks(Request $request)
{
$payload = $request->all();
2025-09-27 16:33:31 +03:00
Log::log(LogLevel::INFO, "Updating books...".json_encode($payload));
2025-09-13 17:39:17 +03:00
// 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;
2025-09-20 16:47:41 +03:00
$authorsArr = $item['authors'] ?? null; // optional array of names
$hasAuthorInfo = $author || (is_array($authorsArr) && count($authorsArr) > 0);
if (!$title || !$hasAuthorInfo) {
2025-09-13 17:39:17 +03:00
$results[] = [
'index' => $index,
'status' => 'error',
2025-09-20 16:47:41 +03:00
'message' => 'title and author/authors are required.',
2025-09-13 17:39:17 +03:00
];
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']);
}
2025-09-20 16:47:41 +03:00
// 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);
2025-09-13 17:39:17 +03:00
$bookQuery = Book::query();
if ($bookSlug) {
$bookQuery->where('slug', $bookSlug);
} else {
2025-09-20 16:47:41 +03:00
// Fallback: match by title+author (string). If authors[] provided, use first one.
$bookQuery->where('title', $item['title'])->where('author', $primaryAuthorName);
2025-09-13 17:39:17 +03:00
}
$book = $bookQuery->first();
$bookData = [
'title' => $item['title'] ?? ($book->title ?? null),
'slug' => $bookSlug ?? ($book->slug ?? null),
2025-09-20 16:47:41 +03:00
'author' => $primaryAuthorName ?? ($book->author ?? null),
2025-09-13 17:39:17 +03:00
'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';
}
2025-09-20 16:47:41 +03:00
// 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);
}
2025-09-13 17:39:17 +03:00
// 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);
}
}