# Audit Log System - Implementation & Usage Guide

## Overview

The audit log system automatically tracks all changes to models using Laravel's model events. It records:
- **What changed** (old and new values)
- **Who made the change** (user ID)
- **When it happened** (timestamp)
- **Where from** (IP address)
- **What device** (user agent)

---

## How It Works

### 1. **Automatic Tracking with `Auditable` Trait**

Add the trait to any model you want to audit:

```php
use App\Traits\Auditable;
use Illuminate\Database\Eloquent\SoftDeletes;

class Brand extends Model
{
    use SoftDeletes, Auditable;
    // ...
}
```

### 2. **What Gets Recorded**

The system automatically logs these actions:

| Action | Trigger | Records |
|--------|---------|---------|
| `created` | `Model::create()` | New values |
| `updated` | `$model->update()` | Old vs new values |
| `deleted` | `$model->delete()` | Old values (soft delete) |
| `restored` | `$model->restore()` | Old values |
| `force_deleted` | `$model->forceDelete()` | Old values (permanent delete) |

### 3. **User Column Population**

The trait also automatically fills audit user columns on your model:

```php
// In your migration
$table->bigInteger('created_by')->nullable();
$table->bigInteger('updated_by')->nullable();
$table->bigInteger('deleted_by')->nullable();
$table->bigInteger('restored_by')->nullable();
$table->bigInteger('force_deleted_by')->nullable();
```

These are automatically populated with the logged-in user's ID.

---

## Usage Examples

### Basic CRUD with Audit Trail

```php
// Create - automatically logs with created_by and action='created'
$brand = Brand::create([
    'name' => 'Fashion Hub',
    'category' => 'Fashion',
    'contact_email' => 'maria@fashionhub.com'
]);
// AuditLog entry: action='created', user_id=1, new_values={...}

// Update - logs what changed
$brand->update(['category' => 'Fashion & Lifestyle']);
// AuditLog entry: action='updated', user_id=1, old_values={...}, new_values={...}

// Soft Delete - logs deletion without removing the row
$brand->delete();
// AuditLog entry: action='deleted', user_id=1, old_values={...}

// Restore - logs restoration
$brand->restore();
// AuditLog entry: action='restored', user_id=1

// Permanent Delete - logs force delete (row is actually removed)
$brand->forceDelete();
// AuditLog entry: action='force_deleted', user_id=1, old_values={...}
```

### Query Audit History

```php
// Get all audit logs for a brand
$brand = Brand::find(1);
$auditLogs = $brand->auditLogs()->get();

// Get audit logs ordered by latest first
$auditLogs = $brand->auditLogs()
    ->orderByDesc('created_at')
    ->get();

// Get only creation logs
$creationLog = $brand->auditLogs()
    ->where('action', 'created')
    ->first();

// Get with user information
$auditLogs = $brand->auditLogs()
    ->with('user')
    ->orderByDesc('created_at')
    ->get();

// Display change details
foreach ($auditLogs as $log) {
    echo "User: " . $log->user->name;
    echo "Action: " . $log->action;
    echo "Old: " . json_encode($log->old_values);
    echo "New: " . json_encode($log->new_values);
}
```

### Query Across Models

```php
use App\Models\AuditLog;

// Find all audit logs for a specific user
$userActions = AuditLog::where('user_id', 1)->get();

// Find all deletions
$allDeletions = AuditLog::where('action', 'deleted')->get();

// Find all updates to email fields in last 7 days
$emailUpdates = AuditLog::where('action', 'updated')
    ->whereDate('created_at', '>=', now()->subDays(7))
    ->whereJsonContains('new_values->contact_email', true)
    ->get();

// Find all changes by IP
$actionsFromIP = AuditLog::where('ip_address', '192.168.1.1')->get();
```

---

## In the Views

### Show Audit History on Detail Page

```blade
@foreach($auditLogs as $log)
    <div class="audit-entry">
        <span class="badge badge-{{ $log->action }}">
            {{ ucfirst($log->action) }}
        </span>
        
        <span>{{ $log->user?->name ?? 'System' }}</span>
        <span>{{ $log->created_at->format('M d, Y H:i') }}</span>
        
        @if($log->action === 'updated')
            <details>
                <summary>View Changes</summary>
                @foreach($log->new_values as $key => $newVal)
                    @if($log->old_values[$key] !== $newVal)
                        <div>{{ $key }}: {{ $log->old_values[$key] }} → {{ $newVal }}</div>
                    @endif
                @endforeach
            </details>
        @endif
    </div>
@endforeach
```

---

## Best Practices

### 1. **Exclude Sensitive Fields**

The `getAuditData()` method already excludes passwords and tokens:

```php
protected function getAuditData(): array
{
    return Arr::except($this->getAttributes(), [
        'password',
        'remember_token',
    ]);
}
```

Override it if you need to exclude more:

```php
// In your model
protected function getAuditData(): array
{
    return Arr::except($this->getAttributes(), [
        'password',
        'remember_token',
        'api_key',      // Add custom fields
        'secret_token',
    ]);
}
```

### 2. **Custom Audit Data Per Model**

Override methods in your model for custom behavior:

```php
class Brand extends Model
{
    use Auditable;

    // Only audit specific fields
    protected function getAuditData(): array
    {
        return [
            'name' => $this->name,
            'category' => $this->category,
            'is_active' => $this->is_active,
        ];
    }

    // Custom user resolution (e.g., from JWT token, not session)
    protected function getAuditUserId(): ?int
    {
        return auth('api')->id() ?? auth()->id();
    }
}
```

### 3. **Performance Considerations**

The audit system stores entire JSON records. For large models:

```php
// Limit what gets audited
protected function getAuditData(): array
{
    return Arr::only($this->getAttributes(), [
        'name',
        'category',
        'status',
        // Only important fields
    ]);
}

// Or add database indexes for faster queries
Schema::create('audit_logs', function (Blueprint $table) {
    // ...
    $table->index(['auditable_type', 'auditable_id']);
    $table->index('user_id');
    $table->index('action');
    $table->index('created_at'); // For time-range queries
});
```

### 4. **Querying Efficiently**

```php
// Get latest 10 changes
$recent = AuditLog::where('auditable_type', Brand::class)
    ->orderByDesc('created_at')
    ->limit(10)
    ->get();

// Pagination for large datasets
$auditLogs = $brand->auditLogs()
    ->orderByDesc('created_at')
    ->paginate(20);
```

### 5. **Controller Integration**

```php
class BrandController extends Controller
{
    public function show(Brand $brand)
    {
        $auditLogs = $brand->auditLogs()
            ->with('user')
            ->orderByDesc('created_at')
            ->paginate(10);

        return view('brands.show', compact('brand', 'auditLogs'));
    }

    public function auditHistory(Brand $brand)
    {
        $auditLogs = $brand->auditLogs()
            ->with('user')
            ->orderByDesc('created_at')
            ->paginate(20);

        return view('brands.audit-history', compact('brand', 'auditLogs'));
    }
}
```

---

## API Response Example

```json
{
  "id": 5,
  "auditable_type": "App\\Models\\Brand",
  "auditable_id": "1",
  "user_id": 1,
  "action": "updated",
  "old_values": {
    "category": "Fashion",
    "is_active": 1,
    "updated_at": "2026-06-01 10:00:00"
  },
  "new_values": {
    "category": "Fashion & Lifestyle",
    "is_active": 1,
    "updated_at": "2026-06-01 12:30:00"
  },
  "ip_address": "192.168.1.100",
  "user_agent": "Mozilla/5.0...",
  "created_at": "2026-06-01T12:30:00.000000Z",
  "updated_at": "2026-06-01T12:30:00.000000Z",
  "user": {
    "id": 1,
    "name": "John Doe",
    "email": "john@example.com"
  }
}
```

---

## Database Schema

```sql
audit_logs:
- id: bigint (PK)
- auditable_type: string (model class)
- auditable_id: string (record ID)
- user_id: bigint (FK to users)
- action: string (created|updated|deleted|restored|force_deleted)
- old_values: json (previous state)
- new_values: json (new state)
- ip_address: string
- user_agent: text
- created_at: timestamp
- updated_at: timestamp

Indexes:
- [auditable_type, auditable_id] - fast model lookups
- [user_id] - fast user history
- [action] - filter by action type
- [created_at] - time-range queries
```

---

## Solving the `force_deleted_by` Problem

Before this system:
- ❌ When you `forceDelete()`, the entire row (including `force_deleted_by`) is permanently removed
- ❌ You lose the information about who deleted it

With the audit log system:
- ✅ `audit_logs` table keeps a permanent history
- ✅ Even after `forceDelete()`, you can still query who performed the deletion
- ✅ You have timestamp, IP, user agent, and all previous data

```php
// After permanent deletion
$brand = Brand::withTrashed()->find($id); // Returns null

// But audit log still exists
$deletion = AuditLog::where('auditable_type', Brand::class)
    ->where('auditable_id', $id)
    ->where('action', 'force_deleted')
    ->first();

echo "Deleted by: " . $deletion->user->name;
echo "IP: " . $deletion->ip_address;
echo "Time: " . $deletion->created_at;
```

---

## Migration Checklist

- [x] Create migration: `2026_05_19_000002_create_audit_logs_table.php`
- [x] Create model: `app/Models/AuditLog.php`
- [x] Create trait: `app/Traits/Auditable.php`
- [x] Add trait to models: `Brand`, `User`, etc.
- [x] Run migrations: `php artisan migrate`
- [x] Create routes for audit history
- [x] Create views for displaying audit logs
- [ ] (Optional) Add admin dashboard to browse all audit logs
- [ ] (Optional) Add export functionality (CSV/PDF)
- [ ] (Optional) Add real-time notifications for critical actions

---

## Next Steps

### Optional Enhancements

1. **Admin Audit Dashboard**
   - Browse all audit logs across all models
   - Filter by action, user, date, model type

2. **Notifications**
   - Alert admins of critical actions (delete, force delete)
   - Send email to user about account changes

3. **Archive Old Logs**
   - Move logs older than 1 year to archive table
   - Keep recent logs in main table for performance

4. **API Endpoint**
   - Expose audit logs via API for integration with other systems
   - Add authentication and permission checks

5. **Change Log Export**
   - Generate CSV/PDF reports of model changes
   - Support date range filtering

---

## Testing

```php
// Test that audit logs are created
public function test_audit_log_created_on_model_creation()
{
    $brand = Brand::create(['name' => 'Test Brand']);
    
    $this->assertDatabaseHas('audit_logs', [
        'auditable_id' => $brand->id,
        'action' => 'created',
        'user_id' => auth()->id(),
    ]);
}

// Test that old values are captured
public function test_audit_log_captures_old_values()
{
    $brand = Brand::create(['name' => 'Old Name']);
    $brand->update(['name' => 'New Name']);
    
    $log = $brand->auditLogs()->where('action', 'updated')->first();
    $this->assertEquals('Old Name', $log->old_values['name']);
    $this->assertEquals('New Name', $log->new_values['name']);
}
```

---

## Support & Troubleshooting

**Q: Why aren't audit logs being created?**
- Ensure the trait is added to the model
- Check that migrations have been run
- Verify the user is authenticated (for user_id)

**Q: How do I exclude a field from auditing?**
- Override `getAuditData()` method in your model

**Q: Can I audit relationships changes?**
- Currently, only model attribute changes are tracked
- For relationship changes, create manual log entries in model observers

**Q: How do I query by changed field?**
- Use JSON queries: `whereJsonContains('new_values->email', 'test@example.com')`
