mirror of
https://github.com/Kovah/LinkAce.git
synced 2025-01-17 13:18:21 +01:00
Add audit logs for users (#467)
This commit is contained in:
parent
6c35874ecc
commit
6960ddf99e
@ -4,6 +4,7 @@ namespace App\Http\Controllers\App;
|
||||
|
||||
use App\Http\Controllers\Controller;
|
||||
use App\Models\Setting;
|
||||
use App\Models\User;
|
||||
use OwenIt\Auditing\Models\Audit;
|
||||
|
||||
class AuditController extends Controller
|
||||
@ -11,10 +12,14 @@ class AuditController extends Controller
|
||||
public function __invoke()
|
||||
{
|
||||
$settingsHistory = Audit::where('auditable_type', Setting::class)->with('auditable')
|
||||
->latest()->paginate(pageName: 'settings');
|
||||
->latest()->paginate(pageName: 'settings_page');
|
||||
|
||||
$userHistory = Audit::where('auditable_type', User::class)->with('auditable')
|
||||
->latest()->paginate(pageName: 'user_page');
|
||||
|
||||
return view('app.audit-logs', [
|
||||
'settings_history' => $settingsHistory,
|
||||
'user_history' => $userHistory,
|
||||
]);
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,8 @@ use Illuminate\Notifications\Notifiable;
|
||||
use Illuminate\Support\Carbon;
|
||||
use Illuminate\Support\Collection;
|
||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||
use OwenIt\Auditing\Auditable as AuditableTrait;
|
||||
use OwenIt\Auditing\Contracts\Auditable;
|
||||
|
||||
/**
|
||||
* Class User
|
||||
@ -25,8 +27,9 @@ use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||
* @property Carbon|null $created_at
|
||||
* @property Carbon|null $updated_at
|
||||
*/
|
||||
class User extends Authenticatable
|
||||
class User extends Authenticatable implements Auditable
|
||||
{
|
||||
use AuditableTrait;
|
||||
use Notifiable;
|
||||
use HasFactory;
|
||||
use TwoFactorAuthenticatable;
|
||||
@ -41,8 +44,28 @@ class User extends Authenticatable
|
||||
protected $hidden = [
|
||||
'password',
|
||||
'remember_token',
|
||||
'api_token',
|
||||
];
|
||||
|
||||
/*
|
||||
* ========================================================================
|
||||
* AUDIT SETTINGS
|
||||
*/
|
||||
|
||||
protected array $auditEvents = [
|
||||
'created',
|
||||
'updated',
|
||||
'deleted',
|
||||
'restored',
|
||||
];
|
||||
|
||||
protected array $auditInclude = [
|
||||
'name',
|
||||
'email',
|
||||
];
|
||||
|
||||
public static array $auditModifiers = [];
|
||||
|
||||
/*
|
||||
* ========================================================================
|
||||
* RELATIONSHIPS
|
||||
|
88
app/View/Components/History/UserEntry.php
Normal file
88
app/View/Components/History/UserEntry.php
Normal file
@ -0,0 +1,88 @@
|
||||
<?php
|
||||
|
||||
namespace App\View\Components\History;
|
||||
|
||||
use App\Models\User;
|
||||
use Illuminate\View\Component;
|
||||
use OwenIt\Auditing\Models\Audit;
|
||||
|
||||
class UserEntry extends Component
|
||||
{
|
||||
public function __construct(private Audit $entry, private array $changes = [])
|
||||
{
|
||||
}
|
||||
|
||||
public function render()
|
||||
{
|
||||
$timestamp = formatDateTime($this->entry->created_at);
|
||||
|
||||
if ($this->entry->event === 'deleted') {
|
||||
$this->changes[] = trans('user.history_deleted', ['name' => $this->entry->getModified()['name']['old']]);
|
||||
} elseif ($this->entry->event === 'restored') {
|
||||
$this->changes[] = trans('user.history_restored', ['name' => $this->entry->getModified()['name']['new']]);
|
||||
} elseif ($this->entry->event === 'created') {
|
||||
$this->changes[] = trans('user.history_created', ['name' => $this->entry->getModified()['name']['new']]);
|
||||
} 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('user.' . $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[] = trans('audit.user_history_entry', [
|
||||
'id' => $this->entry->auditable->id,
|
||||
'change' => $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;
|
||||
|
||||
if (isset(User::$auditModifiers[$field])) {
|
||||
$modifier = app(User::$auditModifiers[$field]);
|
||||
$oldValue = $modifier->modify($oldValue);
|
||||
$newValue = $modifier->modify($newValue);
|
||||
return [$oldValue, $newValue];
|
||||
}
|
||||
|
||||
return [$oldValue, $newValue];
|
||||
}
|
||||
}
|
@ -165,5 +165,5 @@ return [
|
||||
|
|
||||
*/
|
||||
|
||||
'console' => env('AUDIT_CONSOLE_EVENTS', false),
|
||||
'console' => env('AUDIT_CONSOLE_EVENTS', true),
|
||||
];
|
||||
|
@ -4,5 +4,7 @@ return [
|
||||
|
||||
'log' => 'Audit Log',
|
||||
'settings_history' => 'Settings History',
|
||||
'user_history' => 'User History',
|
||||
'user_history_entry' => 'User :id: :change',
|
||||
|
||||
];
|
||||
|
@ -4,8 +4,13 @@ return [
|
||||
'users' => 'Users',
|
||||
|
||||
'username' => 'Username',
|
||||
'name' => 'Username',
|
||||
'email' => 'Email',
|
||||
|
||||
'history_deleted' => 'User <code>:name</code> was deleted',
|
||||
'history_restored' => 'User <code>:name</code> was restored',
|
||||
'history_created' => 'User <code>:name</code> was created',
|
||||
|
||||
'hello' => 'Hello :user!',
|
||||
'for_user' => 'for User',
|
||||
];
|
||||
|
@ -8,7 +8,7 @@
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="history mb-6">
|
||||
<div class="history mb-4">
|
||||
@foreach($settings_history as $entry)
|
||||
<x-history.settings-entry :entry="$entry"/>
|
||||
@endforeach
|
||||
@ -19,4 +19,21 @@
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="card mt-4">
|
||||
<div class="card-header">
|
||||
@lang('audit.user_history')
|
||||
</div>
|
||||
<div class="card-body">
|
||||
|
||||
<div class="history mb-4">
|
||||
@foreach($user_history as $entry)
|
||||
<x-history.user-entry :entry="$entry"/>
|
||||
@endforeach
|
||||
</div>
|
||||
|
||||
{!! $user_history->onEachSide(1)->links() !!}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@endsection
|
||||
|
43
tests/Components/History/UserEntryTest.php
Normal file
43
tests/Components/History/UserEntryTest.php
Normal file
@ -0,0 +1,43 @@
|
||||
<?php
|
||||
|
||||
namespace Tests\Components\History;
|
||||
|
||||
use App\Models\User;
|
||||
use App\View\Components\History\UserEntry;
|
||||
use Illuminate\Foundation\Testing\RefreshDatabase;
|
||||
use Tests\TestCase;
|
||||
|
||||
class UserEntryTest extends TestCase
|
||||
{
|
||||
use RefreshDatabase;
|
||||
|
||||
public function testRegularChange(): void
|
||||
{
|
||||
$user = User::factory()->create(['name' => 'TestUser']);
|
||||
|
||||
$user->update(['name' => 'UserTest']);
|
||||
|
||||
$historyEntries = $user->audits()->latest()->get();
|
||||
|
||||
$output = (new UserEntry($historyEntries[0]))->render();
|
||||
$this->assertStringContainsString('User <code>TestUser</code> was created', $output);
|
||||
|
||||
$output = (new UserEntry($historyEntries[1]))->render();
|
||||
$this->assertStringContainsString(
|
||||
'User 1: Changed Username from <code>TestUser</code> to <code>UserTest</code>',
|
||||
$output
|
||||
);
|
||||
}
|
||||
|
||||
public function testModelDeletion(): void
|
||||
{
|
||||
$user = User::factory()->create(['name' => 'TestUser']);
|
||||
|
||||
$user->delete();
|
||||
|
||||
$historyEntries = $user->audits()->latest()->get();
|
||||
|
||||
$output = (new UserEntry($historyEntries[1]))->render();
|
||||
$this->assertStringContainsString('User <code>TestUser</code> was deleted', $output);
|
||||
}
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user