# Audit Trail

Audit Trail records important changes and authentication activity in the admin CMS. It is a custom Laravel implementation; it does not use Spatie Activitylog.

## Overview

| Item | Value |
|---|---|
| Route | `/{site-slug}/audit-trail` (index) and `/{site-slug}/audit-trail/toggle` (logging switch) |
| Route name | `audit-trail.index`, `audit-trail.toggle` |
| Permission | `audit-trail.view` (guard: `admin`) for both routes |
| Logging setting | `settings.audit_enabled` (`1` = active, `0` = inactive) |
| Controller | `app/Http/Controllers/Backend/AuditTrailController.php` |
| View | `resources/views/backend/module/auditTrail/index.blade.php` |
| Model/table | `App\Models\Backend\AuditLog` / `audit_logs` |
| Logger | `app/Services/AuditLogger.php` |
| Observer | `app/Observers/AuditLogObserver.php` |
| Migration | `2026_10_20_000001_create_audit_trail_table.php` |

## Recorded events

- `created`, `updated`, and `deleted` model changes during web requests.
- `login`, `login_failed`, and `logout` authentication events.

Console seeding and other console model writes are excluded from observer-generated CRUD entries. Generic request/route events are not recorded.

## Activate / deactivate logging

The Audit Trail page has an **Active / Inactive** switch in the card header. It updates the `audit_enabled` key in the `settings` table (`1` = active, `0` = inactive) through `SettingHelper`, and `AuditLogger::enabled()` checks that value before every write. The change applies immediately to observer-generated CRUD entries and to login/logout events; no config cache clear or redeploy is needed.

When the setting has never been saved, logging falls back to `AUDIT_LOG_ENABLED` in `config/audit.php` (default `true`). This keeps existing installations and `.env`-based setups working.

Deactivating logging only stops **new** records. Existing records stay visible in the table, and the page still loads normally while inactive. To clean up existing entries during development, delete rows from `audit_logs` manually (for example with a database client) or add a dedicated clear/prune action if the team needs one.

The toggle uses the `audit-trail.view` permission and the `audit-trail.toggle` route (`POST /{site-slug}/audit-trail/toggle`).

## Enable Audit Trail for another module

Audit Trail observes Eloquent models, not controller names. To audit a module, register its model with `AuditLogObserver` in `app/Providers/AppServiceProvider.php`:

1. Import the model:
   ```php
   use App\Models\Backend\ContentNewModule;
   ```
2. Add the class to the `$auditedModel` list in `boot()`:
   ```php
   foreach ([
       // Existing audited models...
       ContentNewModule::class,
   ] as $auditedModel) {
       $auditedModel::observe(AuditLogObserver::class);
   }
   ```
3. Use normal Eloquent model lifecycle operations in web requests (`create()`, `save()`, `update()`, or `delete()`). The observer records the CRUD event and safe field changes automatically; no logging call is needed in the module controller.

Register every model whose changes should be tracked, including translation or related models if those records need their own audit entries. A model that is not registered will not be observed.

### Operations that bypass the observer

Direct query-builder or bulk operations such as `DB::table(...)->update(...)` and `Model::where(...)->update(...)` do not dispatch per-model Eloquent events. Prefer loading and saving each model when each affected record should have its own Audit Trail entry. If a bulk operation needs a single explicit audit entry, call `AuditLogger::record()` from the web-request code and provide the affected model and before/after changes:

```php
use App\Services\AuditLogger;

app(AuditLogger::class)->record(
    event: 'updated',
    subject: $model,
    changes: app(AuditLogger::class)->changes($before, $after),
);
```

Do not add this manual call when the same model is already observed and saved through Eloquent, or it will create a duplicate entry. Console operations are intentionally excluded; this logger is for user-facing HTTP activity.

## Stored data

The `audit_logs` table stores the event/result, module and description, actor and affected-record identifiers/labels, safe field changes, IP address, user agent, and timestamp. Field changes use the shape `{"field": {"old": "...", "new": "..."}}`. Sensitive fields are excluded; values for fields such as email, phone, address, message, body, content, and description are redacted.

IP address and user agent are captured from the current HTTP request. Historical entries created before those fields were captured may have null metadata.

## Admin interface

The page provides search and event/module filters. Each row's Changes control opens a modal containing:

1. A **Changed fields** table with Field, Before, and After columns (when changes exist).
2. A **Request information** table with IP address and user agent. Missing historical values display as “Not recorded”.

The page query is restricted to CRUD and authentication events; seeded/internal events are not shown.

## Permissions and menu

The Audit Trail menu is managed by `BackendMenuSeeder`. The `audit-trail.view` permission is assigned by `AdminRolePermissionSeeder` to the standard admin roles. The permission middleware protects both the index and toggle routes, so only users who can view the audit trail can change its logging state.

## Related implementation

- `AuditLogger::record()` creates entries and captures request metadata.
- `AuditLogObserver` records model lifecycle changes and skips console runs.
- Authentication controllers call `AuditLogger` for login, failed login, and logout.
- `AuditTrailController::index()` applies filters and resolves current backend usernames for actor display.
- `AuditTrailController::toggle()` flips `settings.audit_enabled`; `AuditLogger::enabled()` gates every write.
- `tests/Feature/AuditTrailToggleTest.php` covers the switch, the inactive state and logger gating.
