Records every successful document download and surfaces the counts in the admin area, so the docket owner can see which filings are being pulled and how often. - document_accesses table + DocumentAccess model - DocumentController@download records each successful download with IP and user agent - documents:import-access-log backfills history from an archived nginx log - Dashboard gains a 60-day downloads card and a most-requested list - New Document Access page with a date-range filter and per-day trend Client IPs were only captured from 2026-07-30, when the reverse proxy chain started forwarding X-Forwarded-For correctly. Rows imported from the old nginx log therefore have no IP, and the UI labels those periods as download counts rather than unique visitors instead of showing a misleading number.
48 lines
1.2 KiB
PHP
48 lines
1.2 KiB
PHP
<?php
|
|
|
|
namespace App\Models;
|
|
|
|
use Illuminate\Database\Eloquent\Model;
|
|
use Illuminate\Database\Eloquent\Relations\BelongsTo;
|
|
|
|
class DocumentAccess extends Model
|
|
{
|
|
protected $table = 'document_accesses';
|
|
|
|
protected $fillable = [
|
|
'document_id',
|
|
'accessed_at',
|
|
'ip_address',
|
|
'user_agent',
|
|
'source',
|
|
];
|
|
|
|
protected $casts = [
|
|
'accessed_at' => 'datetime',
|
|
];
|
|
|
|
/**
|
|
* Get the document that was accessed.
|
|
*/
|
|
public function document(): BelongsTo
|
|
{
|
|
return $this->belongsTo(Document::class);
|
|
}
|
|
|
|
/**
|
|
* Requests whose user agent looks like a crawler rather than a person.
|
|
* Kept as a query-time filter rather than a write-time one so the raw
|
|
* record stays complete and the definition can be changed later.
|
|
*/
|
|
public function scopeExcludingBots($query)
|
|
{
|
|
foreach (['%bot%', '%crawl%', '%spider%', '%slurp%'] as $pattern) {
|
|
$query->where(function ($q) use ($pattern) {
|
|
$q->whereNull('user_agent')
|
|
->orWhere('user_agent', 'not ilike', $pattern);
|
|
});
|
|
}
|
|
|
|
return $query;
|
|
}
|
|
}
|