isValid()) { continue; } $this->validate($file); $path = $file->store('feedback-attachments', $disk); $records[] = FeedbackAttachment::create([ 'uuid' => (string) Str::uuid(), 'feedback_id' => $model instanceof \App\Models\Feedback ? $model->id : null, 'feedback_interaction_id' => $interactionId, 'user_id' => $userId, 'name' => $file->getClientOriginalName(), 'path' => $path, 'disk' => $disk, 'collection' => $collection, 'mime_type' => $file->getMimeType(), 'size' => $file->getSize(), ]); } return $records; } /** * Generate a temporary URL for a file on a private disk. */ public function getTemporaryUrl(FeedbackAttachment $attachment, int $minutes = 10): string { if ($attachment->disk === 'public') { return Storage::disk('public')->url($attachment->path); } return Storage::disk($attachment->disk)->temporaryUrl( $attachment->path, now()->addMinutes($minutes), ); } /** * Generate a preview (inline) URL vs download URL. */ public function getPreviewUrl(FeedbackAttachment $attachment, int $minutes = 60): string { return $this->getTemporaryUrl($attachment, $minutes); } /** * Check if attachment is an image (for inline preview). */ public function isImage(FeedbackAttachment $attachment): bool { return in_array($attachment->mime_type, ['image/jpeg', 'image/jpg', 'image/png']); } /** * Get all attachments of a specific collection for a model. */ public function getByCollection(Model $model, string $collection) { $query = FeedbackAttachment::byCollection($collection); if ($model instanceof \App\Models\Feedback) { $query->where('feedback_id', $model->id); } return $query->orderBy('created_at', 'desc')->get(); } /** * Check if a model has at least one attachment in the given collection. */ public function hasCollection(Model $model, string $collection): bool { return $this->getByCollection($model, $collection)->isNotEmpty(); } /** * Validate file type and size. */ protected function validate(UploadedFile $file): void { $errors = []; if (! in_array($file->getMimeType(), $this->allowedMimes)) { $errors['file'] = __('feedback.file_type_not_allowed', ['type' => $file->getMimeType()]); } if ($file->getSize() > $this->maxSize * 1024) { $errors['file'] = __('feedback.file_size_exceeds', ['max' => $this->maxSize]); } if (! empty($errors)) { throw ValidationException::withMessages($errors); } } }