getClientName(); $type = strtolower($type); Log::info("Preparing S3 structure for client: {$clientName}, record {$recordId}, type: {$type}"); $folderPath = "{$clientName}/records/{$type}/{$recordId}/attachments"; Log::info("S3 folder structure: {$folderPath}"); return true; } /** * Store file temporarily with client structure */ public function storeTemporarily($uploadedFile) { try { $clientName = $this->getClientName(); $extension = $uploadedFile->getClientOriginalExtension(); $fileName = time() . '_' . Str::random(10) . '.' . $extension; $tempPath = "{$clientName}/temp/uploads/{$fileName}"; Log::info("=== STORING FILE TEMPORARILY ==="); Log::info("Client: {$clientName}"); Log::info("Original filename: " . $uploadedFile->getClientOriginalName()); Log::info("File size: " . $uploadedFile->getSize() . " bytes"); Log::info("Temp path: {$tempPath}"); try { $storedPath = Storage::disk('s3')->putFileAs("{$clientName}/temp/uploads", $uploadedFile, $fileName); Log::info("Method 1 success - putFileAs returned: {$storedPath}"); if (Storage::disk('s3')->exists($tempPath)) { $storedSize = Storage::disk('s3')->size($tempPath); Log::info("File verification successful - size: {$storedSize} bytes"); if ($storedSize === $uploadedFile->getSize()) { Log::info("File sizes match perfectly"); return $tempPath; } else { Log::warning("⚠ File size mismatch - Original: {$uploadedFile->getSize()}, Stored: {$storedSize}"); return $tempPath; } } else { throw new \Exception("File not found after putFileAs"); } } catch (\Exception $e) { Log::warning("Method 1 failed: " . $e->getMessage()); } try { Log::info("Trying Method 2: put with file contents"); $fileContent = file_get_contents($uploadedFile->getRealPath()); if (!$fileContent) { throw new \Exception("Could not read file contents"); } $stored = Storage::disk('s3')->put($tempPath, $fileContent); if ($stored && Storage::disk('s3')->exists($tempPath)) { Log::info("Method 2 success - put with contents"); return $tempPath; } else { throw new \Exception("Put method failed"); } } catch (\Exception $e) { Log::warning("Method 2 failed: " . $e->getMessage()); } throw new \Exception("All temp storage methods failed"); } catch (\Exception $e) { Log::error("Error storing file temporarily: " . $e->getMessage()); Log::error("Stack trace: " . $e->getTraceAsString()); throw $e; } } /** * Upload attachment directly to final S3 location with client structure */ public function uploadAttachment($file, $recordId, $type) { try { $clientName = $this->getClientName(); $type = strtolower($type); $extension = $file->getClientOriginalExtension(); $fileName = time() . '_' . Str::random(10) . '.' . $extension; $finalPath = "{$clientName}/records/{$type}/{$recordId}/attachments/{$fileName}"; Log::info("Uploading attachment to S3:"); Log::info("- Client: {$clientName}"); Log::info("- Record ID: {$recordId}"); Log::info("- Type: {$type}"); Log::info("- File path: {$finalPath}"); Log::info("- File size: " . $file->getSize() . " bytes"); $storedPath = Storage::disk('s3')->putFileAs( "{$clientName}/records/{$type}/{$recordId}/attachments", $file, $fileName ); Log::info("File uploaded successfully to S3: {$storedPath}"); if (Storage::disk('s3')->exists($finalPath)) { Log::info("S3 upload verified successfully"); return $finalPath; } else { throw new \Exception("File verification failed - not found on S3"); } } catch (\Exception $e) { Log::error("Error uploading attachment to S3: " . $e->getMessage()); throw $e; } } /** * Upload XML receipt for import functionality with client structure */ public function uploadXmlReceipt($file, $recordId, $type) { try { $clientName = $this->getClientName(); $type = strtolower($type); $extension = $file->getClientOriginalExtension() ?: 'xml'; $fileName = 'receipt_' . time() . '_' . Str::random(8) . '.' . $extension; $finalPath = "{$clientName}/records/{$type}/{$recordId}/attachments/{$fileName}"; Log::info("Uploading XML receipt to S3:"); Log::info("- Client: {$clientName}"); Log::info("- Path: {$finalPath}"); $storedPath = Storage::disk('s3')->putFileAs( "{$clientName}/records/{$type}/{$recordId}/attachments", $file, $fileName ); Log::info("XML receipt uploaded to S3: {$storedPath}"); return $finalPath; } catch (\Exception $e) { Log::error("Error uploading XML receipt to S3: " . $e->getMessage()); throw $e; } } /** * Get S3 attachment URL */ public function getAttachmentUrl($filePath) { try { if (!$filePath) { return null; } Log::info("Getting S3 attachment URL for: {$filePath}"); if (!Storage::disk('s3')->exists($filePath)) { Log::warning("S3 attachment file not found: {$filePath}"); $directory = dirname($filePath); try { $files = Storage::disk('s3')->files($directory); Log::info("Files in S3 directory {$directory}: " . json_encode($files)); } catch (\Exception $e) { Log::warning("Could not list S3 directory {$directory}: " . $e->getMessage()); } return null; } $url = Storage::disk('s3')->temporaryUrl($filePath, now()->addHours(1)); Log::info("Generated S3 temporary URL for: {$filePath}"); return $url; } catch (\Exception $e) { Log::error("Error getting S3 attachment URL for {$filePath}: " . $e->getMessage()); return null; } } /** * Delete attachment from S3 */ public function deleteAttachment($filePath) { try { if (!$filePath) { return false; } Log::info("Deleting S3 attachment: {$filePath}"); if (Storage::disk('s3')->exists($filePath)) { $deleted = Storage::disk('s3')->delete($filePath); if ($deleted) { Log::info("S3 attachment deleted successfully: {$filePath}"); return true; } else { Log::error("Failed to delete S3 attachment: {$filePath}"); return false; } } else { Log::warning("S3 attachment not found for deletion: {$filePath}"); return false; } } catch (\Exception $e) { Log::error("Error deleting S3 attachment: " . $e->getMessage()); throw $e; } } /** * Debug S3 configuration and connectivity */ public function debugFileSystem() { $clientName = $this->getClientName(); Log::info("=== S3 DEBUG ==="); Log::info("Client Name: {$clientName}"); Log::info("S3 Configuration:"); Log::info("- Bucket: " . config('filesystems.disks.s3.bucket')); Log::info("- Region: " . config('filesystems.disks.s3.region')); Log::info("- URL: " . config('filesystems.disks.s3.url')); Log::info("- Key: " . (config('filesystems.disks.s3.key') ? 'Set' : 'Not set')); Log::info("- Secret: " . (config('filesystems.disks.s3.secret') ? 'Set' : 'Not set')); try { $testFile = "{$clientName}/test_connection_" . time() . '.txt'; $testContent = 'S3 connection test: ' . now(); Log::info("Testing S3 connection with client structure..."); Storage::disk('s3')->put($testFile, $testContent); if (Storage::disk('s3')->exists($testFile)) { Log::info("S3 connection test: SUCCESS"); Storage::disk('s3')->delete($testFile); } else { Log::error("S3 connection test: FAILED - file not found after upload"); } } catch (\Exception $e) { Log::error("S3 connection test: FAILED - " . $e->getMessage()); } Log::info("=== END S3 DEBUG ==="); } /** * Clean up old temp files from S3 for specific client or all clients */ public function cleanupTempFiles($olderThanHours = 24, $specificClient = null) { try { $clientName = $specificClient ?: $this->getClientName(); $tempPath = $specificClient ? "{$specificClient}/temp/uploads" : "{$clientName}/temp/uploads"; $tempFiles = Storage::disk('s3')->files($tempPath); $cutoffTime = now()->subHours($olderThanHours); $deletedCount = 0; Log::info("Cleaning up S3 temp files for client '{$clientName}' older than {$olderThanHours} hours"); foreach ($tempFiles as $file) { $fileTime = Storage::disk('s3')->lastModified($file); if ($fileTime < $cutoffTime->timestamp) { if (Storage::disk('s3')->delete($file)) { $deletedCount++; Log::info("Cleaned up old S3 temp file: {$file}"); } } } Log::info("S3 cleanup completed for client '{$clientName}'. Deleted {$deletedCount} temp files."); } catch (\Exception $e) { Log::error("Error cleaning up S3 temp files: " . $e->getMessage()); } } /** * Clean up temp files for all clients */ public function cleanupAllClientTempFiles($olderThanHours = 24) { try { $allDirectories = Storage::disk('s3')->directories(''); $deletedCount = 0; Log::info("Cleaning up temp files for all clients older than {$olderThanHours} hours"); foreach ($allDirectories as $clientDir) { $tempPath = "{$clientDir}/temp/uploads"; if (Storage::disk('s3')->exists($tempPath)) { $tempFiles = Storage::disk('s3')->files($tempPath); $cutoffTime = now()->subHours($olderThanHours); foreach ($tempFiles as $file) { $fileTime = Storage::disk('s3')->lastModified($file); if ($fileTime < $cutoffTime->timestamp) { if (Storage::disk('s3')->delete($file)) { $deletedCount++; Log::info("Cleaned up old S3 temp file: {$file}"); } } } } } Log::info("S3 cleanup completed for all clients. Deleted {$deletedCount} temp files."); } catch (\Exception $e) { Log::error("Error cleaning up all client temp files: " . $e->getMessage()); } } /** * Check if a file exists on S3 */ public function fileExists($filePath) { try { return Storage::disk('s3')->exists($filePath); } catch (\Exception $e) { Log::error("Error checking if S3 file exists: " . $e->getMessage()); return false; } } /** * Get file size from S3 */ public function getFileSize($filePath) { try { if (Storage::disk('s3')->exists($filePath)) { return Storage::disk('s3')->size($filePath); } return 0; } catch (\Exception $e) { Log::error("Error getting S3 file size: " . $e->getMessage()); return 0; } } /** * Get file last modified time from S3 */ public function getFileLastModified($filePath) { try { if (Storage::disk('s3')->exists($filePath)) { return Storage::disk('s3')->lastModified($filePath); } return null; } catch (\Exception $e) { Log::error("Error getting S3 file last modified: " . $e->getMessage()); return null; } } /** * Get all files for a specific client and type */ public function getClientFiles($type = null, $clientName = null) { try { $clientName = $clientName ?: $this->getClientName(); $type = $type ? strtolower($type) : '*'; $basePath = $type === '*' ? "{$clientName}/records" : "{$clientName}/records/{$type}"; $files = Storage::disk('s3')->allFiles($basePath); Log::info("Found " . count($files) . " files for client '{$clientName}' and type '{$type}'"); return $files; } catch (\Exception $e) { Log::error("Error getting client files: " . $e->getMessage()); return []; } } }