From 18089253fe848bbfdd4c8952fdb004e350d0ebeb Mon Sep 17 00:00:00 2001 From: Kovah Date: Thu, 9 Jun 2022 22:20:32 +0200 Subject: [PATCH] Add audit log to lists and tags (#467) --- .../Controllers/Models/ListController.php | 1 + app/Http/Controllers/Models/TagController.php | 1 + app/Models/Link.php | 2 +- app/Models/LinkList.php | 11 ++- app/Models/Tag.php | 11 ++- app/View/Components/History/ListEntry.php | 86 +++++++++++++++++++ app/View/Components/History/TagEntry.php | 86 +++++++++++++++++++ lang/en_US/list.php | 5 ++ lang/en_US/tag.php | 5 ++ resources/views/models/links/show.blade.php | 2 +- resources/views/models/lists/show.blade.php | 24 +++++- resources/views/models/tags/show.blade.php | 22 +++++ .../LinkEntryTest.php} | 10 +-- tests/Components/History/ListEntryTest.php | 86 +++++++++++++++++++ tests/Components/History/TagEntryTest.php | 58 +++++++++++++ 15 files changed, 398 insertions(+), 12 deletions(-) create mode 100644 app/View/Components/History/ListEntry.php create mode 100644 app/View/Components/History/TagEntry.php rename tests/Components/{HistoryEntryTest.php => History/LinkEntryTest.php} (97%) create mode 100644 tests/Components/History/ListEntryTest.php create mode 100644 tests/Components/History/TagEntryTest.php diff --git a/app/Http/Controllers/Models/ListController.php b/app/Http/Controllers/Models/ListController.php index 59ce5722..b8837df6 100644 --- a/app/Http/Controllers/Models/ListController.php +++ b/app/Http/Controllers/Models/ListController.php @@ -96,6 +96,7 @@ class ListController extends Controller return view('models.lists.show', [ 'list' => $list, + 'history' => $list->audits()->latest()->get(), 'listLinks' => $links, 'route' => $request->getBaseUrl(), 'orderBy' => $request->input('orderBy', 'created_at'), diff --git a/app/Http/Controllers/Models/TagController.php b/app/Http/Controllers/Models/TagController.php index ce439604..6de59563 100644 --- a/app/Http/Controllers/Models/TagController.php +++ b/app/Http/Controllers/Models/TagController.php @@ -97,6 +97,7 @@ class TagController extends Controller return view('models.tags.show', [ 'tag' => $tag, + 'history' => $tag->audits()->latest()->get(), 'tagLinks' => $links, 'route' => $request->getBaseUrl(), 'orderBy' => $request->input('orderBy', 'created_at'), diff --git a/app/Models/Link.php b/app/Models/Link.php index 748f195a..99746bee 100644 --- a/app/Models/Link.php +++ b/app/Models/Link.php @@ -89,7 +89,7 @@ class Link extends Model implements Auditable 'icon', ]; - public $auditModifiers = [ + public array $auditModifiers = [ 'is_private' => BooleanModifier::class, 'check_disabled' => BooleanModifier::class, 'status' => LinkStatusModifier::class, diff --git a/app/Models/LinkList.php b/app/Models/LinkList.php index 4c03de0b..a9bb3f71 100644 --- a/app/Models/LinkList.php +++ b/app/Models/LinkList.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Audits\Modifiers\BooleanModifier; use App\Scopes\OrderNameScope; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; @@ -12,6 +13,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Carbon; use Illuminate\Support\Str; +use OwenIt\Auditing\Auditable as AuditableTrait; +use OwenIt\Auditing\Contracts\Auditable; /** * Class LinkList @@ -31,8 +34,9 @@ use Illuminate\Support\Str; * @method static Builder|Tag privateOnly() * @method static Builder|Tag publicOnly() */ -class LinkList extends Model +class LinkList extends Model implements Auditable { + use AuditableTrait; use SoftDeletes; use HasFactory; @@ -50,6 +54,11 @@ class LinkList extends Model 'is_private' => 'boolean', ]; + // Audit settings + public array $auditModifiers = [ + 'is_private' => BooleanModifier::class, + ]; + /** * Add the OrderNameScope to the Tag model */ diff --git a/app/Models/Tag.php b/app/Models/Tag.php index 8b2a930f..91b589e6 100644 --- a/app/Models/Tag.php +++ b/app/Models/Tag.php @@ -2,6 +2,7 @@ namespace App\Models; +use App\Audits\Modifiers\BooleanModifier; use App\Scopes\OrderNameScope; use Illuminate\Database\Eloquent\Builder; use Illuminate\Database\Eloquent\Collection; @@ -11,6 +12,8 @@ use Illuminate\Database\Eloquent\Relations\BelongsTo; use Illuminate\Database\Eloquent\Relations\BelongsToMany; use Illuminate\Database\Eloquent\SoftDeletes; use Illuminate\Support\Carbon; +use OwenIt\Auditing\Auditable as AuditableTrait; +use OwenIt\Auditing\Contracts\Auditable; /** * Class Tag @@ -29,8 +32,9 @@ use Illuminate\Support\Carbon; * @method static Builder|Tag publicOnly() * @method static Builder|Tag privateOnly() */ -class Tag extends Model +class Tag extends Model implements Auditable { + use AuditableTrait; use SoftDeletes; use HasFactory; @@ -45,6 +49,11 @@ class Tag extends Model 'is_private' => 'boolean', ]; + // Audit settings + public array $auditModifiers = [ + 'is_private' => BooleanModifier::class, + ]; + /** * Add the OrderNameScope to the Tag model */ diff --git a/app/View/Components/History/ListEntry.php b/app/View/Components/History/ListEntry.php new file mode 100644 index 00000000..565075a7 --- /dev/null +++ b/app/View/Components/History/ListEntry.php @@ -0,0 +1,86 @@ +entry->created_at); + + if ($this->entry->event === 'deleted') { + $this->changes[] = trans('list.history_deleted'); + } elseif ($this->entry->event === 'restored') { + $this->changes[] = trans('list.history_restored'); + } else { + foreach ($this->entry->getModified() as $field => $change) { + $this->processChange($field, $change); + } + } + + return view('components.history-entry', [ + 'timestamp' => $timestamp, + 'changes' => $this->changes, + ]); + } + + protected function processChange(string $field, array $changeData): void + { + $fieldName = trans('list.' . $field); + [$oldValue, $newValue] = $this->processValues($field, $changeData); + + if ($oldValue === null) { + $change = trans('linkace.history_added', [ + 'fieldname' => $fieldName, + 'newvalue' => htmlspecialchars($newValue), + ]); + } elseif ($newValue === null) { + $change = trans('linkace.history_removed', [ + 'fieldname' => $fieldName, + 'oldvalue' => htmlspecialchars($oldValue), + ]); + } else { + $change = trans('linkace.history_changed', [ + 'fieldname' => $fieldName, + 'oldvalue' => htmlspecialchars($oldValue), + 'newvalue' => htmlspecialchars($newValue), + ]); + } + + $this->changes[] = $change; + } + + /** + * Apply specialized methods for different fields to handle particular + * formatting needs of these fields. + * + * @param string $field + * @param array $changeData + * @return array + */ + protected function processValues(string $field, array $changeData): array + { + $oldValue = $changeData['old'] ?? null; + $newValue = $changeData['new'] ?? null; + + /** @var Link $model */ + $model = app($this->entry->auditable_type); + + if (isset($model->auditModifiers[$field])) { + $modifier = app($model->auditModifiers[$field]); + $oldValue = $modifier->modify($oldValue); + $newValue = $modifier->modify($newValue); + return [$oldValue, $newValue]; + } + + return [$oldValue, $newValue]; + } +} diff --git a/app/View/Components/History/TagEntry.php b/app/View/Components/History/TagEntry.php new file mode 100644 index 00000000..379787af --- /dev/null +++ b/app/View/Components/History/TagEntry.php @@ -0,0 +1,86 @@ +entry->created_at); + + if ($this->entry->event === 'deleted') { + $this->changes[] = trans('tag.history_deleted'); + } elseif ($this->entry->event === 'restored') { + $this->changes[] = trans('tag.history_restored'); + } else { + foreach ($this->entry->getModified() as $field => $change) { + $this->processChange($field, $change); + } + } + + return view('components.history-entry', [ + 'timestamp' => $timestamp, + 'changes' => $this->changes, + ]); + } + + protected function processChange(string $field, array $changeData): void + { + $fieldName = trans('tag.' . $field); + [$oldValue, $newValue] = $this->processValues($field, $changeData); + + if ($oldValue === null) { + $change = trans('linkace.history_added', [ + 'fieldname' => $fieldName, + 'newvalue' => htmlspecialchars($newValue), + ]); + } elseif ($newValue === null) { + $change = trans('linkace.history_removed', [ + 'fieldname' => $fieldName, + 'oldvalue' => htmlspecialchars($oldValue), + ]); + } else { + $change = trans('linkace.history_changed', [ + 'fieldname' => $fieldName, + 'oldvalue' => htmlspecialchars($oldValue), + 'newvalue' => htmlspecialchars($newValue), + ]); + } + + $this->changes[] = $change; + } + + /** + * Apply specialized methods for different fields to handle particular + * formatting needs of these fields. + * + * @param string $field + * @param array $changeData + * @return array + */ + protected function processValues(string $field, array $changeData): array + { + $oldValue = $changeData['old'] ?? null; + $newValue = $changeData['new'] ?? null; + + /** @var Link $model */ + $model = app($this->entry->auditable_type); + + if (isset($model->auditModifiers[$field])) { + $modifier = app($model->auditModifiers[$field]); + $oldValue = $modifier->modify($oldValue); + $newValue = $modifier->modify($newValue); + return [$oldValue, $newValue]; + } + + return [$oldValue, $newValue]; + } +} diff --git a/lang/en_US/list.php b/lang/en_US/list.php index eeee4b88..7e303566 100644 --- a/lang/en_US/list.php +++ b/lang/en_US/list.php @@ -17,6 +17,11 @@ return [ 'name' => 'List Name', 'description' => 'List Description', + 'is_private' => 'Private Status', + + 'history_deleted' => 'List was deleted', + 'history_restored' => 'List was restored', + 'history_created' => 'List was created', 'author' => 'by :user', diff --git a/lang/en_US/tag.php b/lang/en_US/tag.php index 89d43d9a..f7ac81aa 100644 --- a/lang/en_US/tag.php +++ b/lang/en_US/tag.php @@ -16,6 +16,11 @@ return [ 'private' => 'Private Tag', 'name' => 'Tag Name', + 'is_private' => 'Private Status', + + 'history_deleted' => 'Tag was deleted', + 'history_restored' => 'Tag was restored', + 'history_created' => 'Tag was created', 'author' => 'by :user', diff --git a/resources/views/models/links/show.blade.php b/resources/views/models/links/show.blade.php index 9b81fcf6..0fd811b7 100644 --- a/resources/views/models/links/show.blade.php +++ b/resources/views/models/links/show.blade.php @@ -168,7 +168,7 @@