mirror of
https://github.com/Kovah/LinkAce.git
synced 2025-01-17 21:28:30 +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\Http\Controllers\Controller;
|
||||||
use App\Models\Setting;
|
use App\Models\Setting;
|
||||||
|
use App\Models\User;
|
||||||
use OwenIt\Auditing\Models\Audit;
|
use OwenIt\Auditing\Models\Audit;
|
||||||
|
|
||||||
class AuditController extends Controller
|
class AuditController extends Controller
|
||||||
@ -11,10 +12,14 @@ class AuditController extends Controller
|
|||||||
public function __invoke()
|
public function __invoke()
|
||||||
{
|
{
|
||||||
$settingsHistory = Audit::where('auditable_type', Setting::class)->with('auditable')
|
$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', [
|
return view('app.audit-logs', [
|
||||||
'settings_history' => $settingsHistory,
|
'settings_history' => $settingsHistory,
|
||||||
|
'user_history' => $userHistory,
|
||||||
]);
|
]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,8 @@ use Illuminate\Notifications\Notifiable;
|
|||||||
use Illuminate\Support\Carbon;
|
use Illuminate\Support\Carbon;
|
||||||
use Illuminate\Support\Collection;
|
use Illuminate\Support\Collection;
|
||||||
use Laravel\Fortify\TwoFactorAuthenticatable;
|
use Laravel\Fortify\TwoFactorAuthenticatable;
|
||||||
|
use OwenIt\Auditing\Auditable as AuditableTrait;
|
||||||
|
use OwenIt\Auditing\Contracts\Auditable;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Class User
|
* Class User
|
||||||
@ -25,8 +27,9 @@ use Laravel\Fortify\TwoFactorAuthenticatable;
|
|||||||
* @property Carbon|null $created_at
|
* @property Carbon|null $created_at
|
||||||
* @property Carbon|null $updated_at
|
* @property Carbon|null $updated_at
|
||||||
*/
|
*/
|
||||||
class User extends Authenticatable
|
class User extends Authenticatable implements Auditable
|
||||||
{
|
{
|
||||||
|
use AuditableTrait;
|
||||||
use Notifiable;
|
use Notifiable;
|
||||||
use HasFactory;
|
use HasFactory;
|
||||||
use TwoFactorAuthenticatable;
|
use TwoFactorAuthenticatable;
|
||||||
@ -41,8 +44,28 @@ class User extends Authenticatable
|
|||||||
protected $hidden = [
|
protected $hidden = [
|
||||||
'password',
|
'password',
|
||||||
'remember_token',
|
'remember_token',
|
||||||
|
'api_token',
|
||||||
];
|
];
|
||||||
|
|
||||||
|
/*
|
||||||
|
* ========================================================================
|
||||||
|
* AUDIT SETTINGS
|
||||||
|
*/
|
||||||
|
|
||||||
|
protected array $auditEvents = [
|
||||||
|
'created',
|
||||||
|
'updated',
|
||||||
|
'deleted',
|
||||||
|
'restored',
|
||||||
|
];
|
||||||
|
|
||||||
|
protected array $auditInclude = [
|
||||||
|
'name',
|
||||||
|
'email',
|
||||||
|
];
|
||||||
|
|
||||||
|
public static array $auditModifiers = [];
|
||||||
|
|
||||||
/*
|
/*
|
||||||
* ========================================================================
|
* ========================================================================
|
||||||
* RELATIONSHIPS
|
* 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',
|
'log' => 'Audit Log',
|
||||||
'settings_history' => 'Settings History',
|
'settings_history' => 'Settings History',
|
||||||
|
'user_history' => 'User History',
|
||||||
|
'user_history_entry' => 'User :id: :change',
|
||||||
|
|
||||||
];
|
];
|
||||||
|
@ -4,8 +4,13 @@ return [
|
|||||||
'users' => 'Users',
|
'users' => 'Users',
|
||||||
|
|
||||||
'username' => 'Username',
|
'username' => 'Username',
|
||||||
|
'name' => 'Username',
|
||||||
'email' => 'Email',
|
'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!',
|
'hello' => 'Hello :user!',
|
||||||
'for_user' => 'for User',
|
'for_user' => 'for User',
|
||||||
];
|
];
|
||||||
|
@ -8,7 +8,7 @@
|
|||||||
</div>
|
</div>
|
||||||
<div class="card-body">
|
<div class="card-body">
|
||||||
|
|
||||||
<div class="history mb-6">
|
<div class="history mb-4">
|
||||||
@foreach($settings_history as $entry)
|
@foreach($settings_history as $entry)
|
||||||
<x-history.settings-entry :entry="$entry"/>
|
<x-history.settings-entry :entry="$entry"/>
|
||||||
@endforeach
|
@endforeach
|
||||||
@ -19,4 +19,21 @@
|
|||||||
</div>
|
</div>
|
||||||
</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
|
@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