Files
ChatbotAI/app/Http/Controllers/Admin/DocumentController.php
2026-05-18 08:56:23 +08:00

215 lines
6.9 KiB
PHP

<?php
namespace App\Http\Controllers\Admin;
use App\Actions\Document\CreateDocumentAction;
use App\Actions\Document\UploadNewVersionAction;
use App\Http\Controllers\Controller;
use App\Http\Requests\Admin\StoreDocumentRequest;
use App\Jobs\ReindexDocumentJob;
use App\Models\Category;
use App\Models\Document;
use App\Models\DocumentVersion;
use App\Services\KnowledgeBase\AuditService;
use App\Services\KnowledgeBase\IngestionService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
use Illuminate\View\View;
use RuntimeException;
class DocumentController extends Controller
{
public function __construct(
private readonly AuditService $auditService,
private readonly IngestionService $ingestionService,
) {}
public function index(Request $request): View
{
$query = Document::with(['category', 'currentVersion'])
->withCount('versions');
if ($request->filled('category_id')) {
$query->where('category_id', $request->category_id);
}
if ($request->filled('status')) {
$query->where('status', $request->status);
}
if ($request->filled('search')) {
$query->where('title', 'like', '%' . $request->search . '%');
}
$documents = $query->latest()->paginate(15)->withQueryString();
$categories = Category::active()->ordered()->get();
return view('admin.documents.index', compact('documents', 'categories'));
}
public function create(): View
{
$categories = Category::active()->ordered()->get();
return view('admin.documents.create', compact('categories'));
}
public function store(StoreDocumentRequest $request, CreateDocumentAction $action): RedirectResponse
{
try {
$document = $action->execute(
$request->validated(),
$request->file('file')
);
return redirect()
->route('admin.documents.show', $document)
->with('success', "Dokumen '{$document->title}' berjaya diupload dan sedang diproses.");
} catch (RuntimeException $e) {
return back()
->withInput()
->with('error', $e->getMessage());
}
}
public function show(Document $document): View
{
$document->load([
'category',
'versions.uploader',
'currentVersion.chunks' => fn($q) => $q->limit(20),
]);
return view('admin.documents.show', compact('document'));
}
public function edit(Document $document): View
{
$categories = Category::active()->ordered()->get();
return view('admin.documents.edit', compact('document', 'categories'));
}
public function update(Request $request, Document $document): RedirectResponse
{
$validated = $request->validate([
'title' => ['required', 'string', 'max:255'],
'description' => ['nullable', 'string', 'max:1000'],
'category_id' => ['required', 'exists:categories,id'],
'effective_date' => ['nullable', 'date'],
'expiry_date' => ['nullable', 'date'],
'tags' => ['nullable', 'array'],
'language' => ['nullable', 'in:ms,en'],
]);
$validated['updated_by'] = auth()->id();
$document->update($validated);
return redirect()
->route('admin.documents.show', $document)
->with('success', 'Maklumat dokumen berjaya dikemaskini.');
}
/**
* Upload versi baru untuk dokumen yang sedia ada.
*/
public function uploadVersion(
Request $request,
Document $document,
UploadNewVersionAction $action
): RedirectResponse {
$request->validate([
'file' => ['required', 'file', 'mimes:pdf', 'max:' . config('knowledgebase.upload.max_file_size', 20480)],
'change_notes' => ['nullable', 'string', 'max:500'],
]);
try {
$action->execute($document, $request->file('file'), $request->only('change_notes'));
return redirect()
->route('admin.documents.show', $document)
->with('success', 'Versi baru berjaya diupload dan sedang diproses.');
} catch (RuntimeException $e) {
return back()->with('error', $e->getMessage());
}
}
/**
* Toggle status aktif/tidak aktif dokumen.
* Apabila dinyahaktifkan, chunk dalam Qdrant juga dimatikan.
*/
public function toggleStatus(Document $document): RedirectResponse
{
$newStatus = !$document->is_active;
if ($newStatus) {
// Aktifkan — versi semasa mesti sudah indexed
$currentVersion = $document->currentVersion;
if (!$currentVersion || !$currentVersion->isProcessed()) {
return back()->with('error',
'Dokumen tidak boleh diaktifkan kerana pemprosesan belum selesai.'
);
}
$document->update([
'is_active' => true,
'status' => Document::STATUS_ACTIVE,
]);
$this->auditService->documentActivated($document);
} else {
// Deactivate — matikan juga dalam Qdrant
$document->update([
'is_active' => false,
'status' => Document::STATUS_INACTIVE,
]);
if ($currentVersion = $document->currentVersion) {
$this->ingestionService->deactivateVersionInQdrant($currentVersion);
}
$this->auditService->documentDeactivated($document);
}
$status = $newStatus ? 'diaktifkan' : 'dinyahaktifkan';
return back()->with('success', "Dokumen '{$document->title}' telah {$status}.");
}
/**
* Trigger reindex untuk versi tertentu.
*/
public function reindex(Document $document): RedirectResponse
{
$currentVersion = $document->currentVersion;
if (!$currentVersion) {
return back()->with('error', 'Tiada versi semasa untuk diindeks semula.');
}
ReindexDocumentJob::dispatch($currentVersion->id);
$this->auditService->documentReindexed($document, $currentVersion);
return back()->with('success', 'Reindeks telah dimulakan. Sila semak semula sebentar lagi.');
}
/**
* Download PDF asal.
*/
public function download(Document $document, DocumentVersion $version)
{
abort_if($version->document_id !== $document->id, 404);
$disk = config('knowledgebase.upload.storage_disk', 'local');
if (!Storage::disk($disk)->exists($version->stored_path)) {
abort(404, 'Fail tidak dijumpai.');
}
return Storage::disk($disk)->download(
$version->stored_path,
$version->original_filename
);
}
}