'integer', 'download_count' => 'integer', 'is_chunked' => 'boolean', 'is_expired' => 'boolean', 'expires_at' => 'datetime', 'last_downloaded_at' => 'datetime', ]; // Relationships public function activityLogs() { return $this->morphMany(ActivityLog::class, 'loggable'); } public function downloads() { return $this->activityLogs()->where('event_type', 'download'); } // Accessors public function getFormattedSizeAttribute() { $bytes = $this->size; $units = ['B', 'KB', 'MB', 'GB', 'TB']; for ($i = 0; $bytes > 1024 && $i < count($units) - 1; $i++) { $bytes /= 1024; } return round($bytes, 2) . ' ' . $units[$i]; } public function getDownloadUrlAttribute() { return route('files.download', $this); } public function getFullPathAttribute() { return Storage::disk($this->disk)->path($this->storage_path); } // Methods public function logDownload($ipAddress, $userAgent = null) { // Create activity log entry $this->activityLogs()->create([ 'event_type' => 'download', 'ip_address' => $ipAddress, 'user_agent' => $userAgent, ]); // Update cached counters $this->increment('download_count'); $this->update(['last_downloaded_at' => now()]); } public function setExpiration($days = null) { $days = $days ?? config('fileupload.expiration_days'); if (config('fileupload.expiration_enabled')) { $this->expires_at = now()->addDays((int) $days); $this->save(); } } public function isExpired() { return $this->is_expired || ($this->expires_at && $this->expires_at->isPast()); } // Scopes public function scopeNotExpired($query) { return $query->where(function ($q) { $q->where('is_expired', false) ->where(function ($q2) { $q2->whereNull('expires_at') ->orWhere('expires_at', '>', now()); }); }); } public function scopeByIp($query, string $ip) { return $query->where('ip_address', $ip); } }