Files
ChatbotAI/app/Services/KnowledgeBase/AuditService.php
2026-05-18 08:56:23 +08:00

230 lines
7.0 KiB
PHP

<?php
namespace App\Services\KnowledgeBase;
use App\Models\AuditLog;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Request;
/**
* AuditService
*
* Simpan audit trail untuk semua tindakan penting dalam sistem.
* Append-only — tiada delete atau update audit log.
*/
class AuditService
{
/**
* Log satu event.
*
* @param string $event Nama event (e.g. 'document.uploaded')
* @param mixed $model Model yang terlibat (optional)
* @param array $oldValues Data sebelum perubahan
* @param array $newValues Data selepas perubahan
* @param ?string $description Huraian untuk manusia
*/
public function log(
string $event,
mixed $model = null,
array $oldValues = [],
array $newValues = [],
?string $description = null
): AuditLog {
return AuditLog::create([
'user_id' => Auth::id(),
'event' => $event,
'auditable_type' => $model ? get_class($model) : null,
'auditable_id' => $model?->getKey(),
'old_values' => empty($oldValues) ? null : $oldValues,
'new_values' => empty($newValues) ? null : $newValues,
'description' => $description,
'ip_address' => Request::ip(),
'user_agent' => Request::userAgent(),
]);
}
// Shortcut methods untuk event biasa
public function documentUploaded($document, $version): void
{
$this->log(
'document.uploaded',
$document,
[],
[
'document_id' => $document->id,
'version_number' => $version->version_number,
'filename' => $version->original_filename,
],
"Dokumen '{$document->title}' versi {$version->version_number} diupload."
);
}
public function documentActivated($document): void
{
$this->log(
'document.activated',
$document,
['is_active' => false],
['is_active' => true],
"Dokumen '{$document->title}' diaktifkan."
);
}
public function documentDeactivated($document): void
{
$this->log(
'document.deactivated',
$document,
['is_active' => true],
['is_active' => false],
"Dokumen '{$document->title}' dinyahaktifkan."
);
}
public function documentReindexed($document, $version): void
{
$this->log(
'document.reindexed',
$version,
[],
['document_id' => $document->id, 'version_id' => $version->id],
"Dokumen '{$document->title}' versi {$version->version_number} diindeks semula."
);
}
public function knowledgeItemCreated($item): void
{
$this->log(
'knowledge_item.created',
$item,
[],
['title' => $item->title, 'type' => $item->item_type],
"Knowledge item '{$item->title}' ({$item->item_type}) dicipta."
);
}
public function knowledgeItemUpdated($item, array $oldValues): void
{
$this->log(
'knowledge_item.updated',
$item,
$oldValues,
$item->getAttributes(),
"Knowledge item '{$item->title}' dikemaskini."
);
}
public function knowledgeItemDeactivated($item): void
{
$this->log(
'knowledge_item.deactivated',
$item,
['is_active' => true],
['is_active' => false],
"Knowledge item '{$item->title}' dinyahaktifkan."
);
}
public function faqConvertedFromFeedback($feedback, $knowledgeItem): void
{
$this->log(
'faq.converted_from_feedback',
$knowledgeItem,
[],
['feedback_id' => $feedback->id, 'knowledge_item_id' => $knowledgeItem->id],
"FAQ baru '{$knowledgeItem->title}' dicipta dari feedback chat."
);
}
public function categoryCreated($category): void
{
$this->log(
'category.created',
$category,
[],
['name' => $category->name, 'slug' => $category->slug],
"Kategori '{$category->name}' dicipta."
);
}
public function systemReindexStarted(string $scope): void
{
$this->log(
'system.reindex_started',
null,
[],
['scope' => $scope],
"Reindeks sistem dimulakan untuk: {$scope}"
);
}
// =========================================================================
// CHUNK REVIEW & EDITING EVENTS
// =========================================================================
public function chunkFinalTextEdited($chunk, ?string $oldText, string $newText): void
{
$this->log(
'chunk.final_text_edited',
$chunk,
['final_text' => mb_substr($oldText ?? '[content asal]', 0, 200)],
['final_text' => mb_substr($newText, 0, 200)],
"final_text chunk #{$chunk->chunk_index} (ID: {$chunk->id}) diedit. Reindex diantrikan."
);
}
public function chunkExcluded($chunk, string $oldStatus): void
{
$this->log(
'chunk.excluded',
$chunk,
['chunk_status' => $oldStatus, 'is_active' => true],
['chunk_status' => 'excluded', 'is_active' => false],
"Chunk #{$chunk->chunk_index} (ID: {$chunk->id}) dikecualikan dari indexing."
);
}
public function chunkIncluded($chunk, string $oldStatus): void
{
$this->log(
'chunk.included',
$chunk,
['chunk_status' => $oldStatus, 'is_active' => false],
['chunk_status' => $chunk->chunk_status, 'is_active' => true],
"Chunk #{$chunk->chunk_index} (ID: {$chunk->id}) dikembalikan ke indexing."
);
}
public function chunkReindexTriggered($chunk): void
{
$this->log(
'chunk.reindex_triggered',
$chunk,
[],
['chunk_status' => 'needs_reindex'],
"Reindex manual dicetuskan untuk chunk #{$chunk->chunk_index} (ID: {$chunk->id})."
);
}
public function chunkSplit($parentChunk, array $children, string $splitGroupId): void
{
$childIds = array_map(fn($c) => $c->id, $children);
$this->log(
'chunk.split',
$parentChunk,
['chunk_status' => 'indexed', 'is_active' => true],
[
'chunk_status' => 'superseded',
'is_active' => false,
'split_group_id' => $splitGroupId,
'child_count' => count($children),
'child_chunk_ids' => $childIds,
],
"Chunk #{$parentChunk->chunk_index} (ID: {$parentChunk->id}) di-split kepada "
. count($children) . " chunk baharu. Split group: {$splitGroupId}"
);
}
}