'datetime', 'last_downloaded_at' => 'datetime', 'created_at' => 'datetime', 'download_count' => 'integer', 'max_downloads' => 'integer', ]; /** * Boot method: Auto-generate token */ protected static function boot() { parent::boot(); static::creating(function ($model) { if (empty($model->token)) { $model->token = self::generateToken(); } }); } /** * Generate a unique 64-character token */ public static function generateToken(): string { return bin2hex(random_bytes(32)); // 64 chars } /** * Get the file associated with this share link */ public function file(): BelongsTo { return $this->belongsTo(File::class); } /** * Get the tenant that owns the share link */ public function tenant(): BelongsTo { return $this->belongsTo(Tenant::class); } /** * Check if the link is expired */ public function isExpired(): bool { return $this->expires_at && $this->expires_at->isPast(); } /** * Check if download limit reached */ public function isDownloadLimitReached(): bool { return $this->max_downloads && $this->download_count >= $this->max_downloads; } /** * Check if the link is still valid */ public function isValid(): bool { return ! $this->isExpired() && ! $this->isDownloadLimitReached(); } /** * Increment download count */ public function incrementDownloadCount(string $ip): void { $this->increment('download_count'); $this->update([ 'last_downloaded_at' => now(), 'last_downloaded_ip' => $ip, ]); } /** * Scope: Non-expired links */ public function scopeValid($query) { return $query->where('expires_at', '>', now()) ->where(function ($q) { $q->whereNull('max_downloads') ->orWhereRaw('download_count < max_downloads'); }); } }