'boolean', 'file_size' => 'integer', ]; // ========================================================================= // Relationships // ========================================================================= /** * Polymorphic 관계 - 연결된 모델 (Post, Product 등) */ public function fileable(): MorphTo { return $this->morphTo(); } /** * 업로더 */ public function uploader(): BelongsTo { return $this->belongsTo(User::class, 'uploaded_by'); } /** * 생성자 */ public function creator(): BelongsTo { return $this->belongsTo(User::class, 'created_by'); } // ========================================================================= // Helper Methods // ========================================================================= /** * 스토리지 전체 경로 */ public function getStoragePath(): string { return Storage::disk('tenant')->path($this->file_path); } /** * 파일 존재 여부 */ public function existsInStorage(): bool { return Storage::disk('tenant')->exists($this->file_path); } /** * 다운로드 응답 */ public function download() { if (! $this->existsInStorage()) { abort(404, '파일을 찾을 수 없습니다.'); } $fileName = $this->display_name ?? $this->original_name ?? $this->file_name; return response()->download($this->getStoragePath(), $fileName); } /** * 파일 URL (public 접근용) */ public function getUrl(): ?string { if (! $this->existsInStorage()) { return null; } return Storage::disk('tenant')->url($this->file_path); } /** * 파일 크기 포맷팅 */ public function getFormattedSize(): string { $bytes = $this->file_size; if ($bytes >= 1073741824) { return number_format($bytes / 1073741824, 2).' GB'; } elseif ($bytes >= 1048576) { return number_format($bytes / 1048576, 2).' MB'; } elseif ($bytes >= 1024) { return number_format($bytes / 1024, 2).' KB'; } return $bytes.' bytes'; } /** * 파일 확장자 */ public function getExtension(): string { $fileName = $this->stored_name ?? $this->file_name ?? $this->original_name; return pathinfo($fileName, PATHINFO_EXTENSION); } /** * 이미지 여부 */ public function isImage(): bool { return str_starts_with($this->mime_type ?? '', 'image/'); } /** * Soft Delete with user tracking */ public function softDeleteFile(int $userId): void { $this->deleted_by = $userId; $this->save(); $this->delete(); } /** * 완전 삭제 (물리 파일 포함) */ public function permanentDelete(): void { if ($this->existsInStorage()) { Storage::disk('tenant')->delete($this->file_path); } $this->forceDelete(); } // ========================================================================= // Scopes // ========================================================================= /** * 특정 모델의 파일들 */ public function scopeForModel($query, string $type, int $id) { return $query->where('fileable_type', $type) ->where('fileable_id', $id); } /** * 임시 파일만 */ public function scopeTemp($query) { return $query->where('is_temp', true); } /** * 임시 파일 제외 */ public function scopeNonTemp($query) { return $query->where('is_temp', false); } /** * 특정 문서의 파일들 */ public function scopeForDocument($query, int $documentId, string $documentType) { return $query->where('document_id', $documentId) ->where('document_type', $documentType); } }