RecordFileService.php 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416
  1. <?php
  2. namespace App\Services;
  3. use Illuminate\Support\Facades\Storage;
  4. use Illuminate\Support\Facades\Log;
  5. use Illuminate\Support\Str;
  6. use Illuminate\Http\UploadedFile;
  7. class RecordFileService
  8. {
  9. /**
  10. * Get client name from session, fallback to 'default'
  11. */
  12. private function getClientName()
  13. {
  14. $clientName = session('clientName', 'default');
  15. $clientName = Str::slug($clientName, '_');
  16. Log::info("Using client name for folders: {$clientName}");
  17. return $clientName;
  18. }
  19. /**
  20. * Create record folders with client structure
  21. */
  22. public function createRecordFolders($recordId, $type)
  23. {
  24. $clientName = $this->getClientName();
  25. $type = strtolower($type);
  26. Log::info("Preparing S3 structure for client: {$clientName}, record {$recordId}, type: {$type}");
  27. $folderPath = "{$clientName}/records/{$type}/{$recordId}/attachments";
  28. Log::info("S3 folder structure: {$folderPath}");
  29. return true;
  30. }
  31. /**
  32. * Store file temporarily with client structure
  33. */
  34. public function storeTemporarily($uploadedFile)
  35. {
  36. try {
  37. $clientName = $this->getClientName();
  38. $extension = $uploadedFile->getClientOriginalExtension();
  39. $fileName = time() . '_' . Str::random(10) . '.' . $extension;
  40. $tempPath = "{$clientName}/temp/uploads/{$fileName}";
  41. Log::info("=== STORING FILE TEMPORARILY ===");
  42. Log::info("Client: {$clientName}");
  43. Log::info("Original filename: " . $uploadedFile->getClientOriginalName());
  44. Log::info("File size: " . $uploadedFile->getSize() . " bytes");
  45. Log::info("Temp path: {$tempPath}");
  46. try {
  47. $storedPath = Storage::disk('s3')->putFileAs("{$clientName}/temp/uploads", $uploadedFile, $fileName);
  48. Log::info("Method 1 success - putFileAs returned: {$storedPath}");
  49. if (Storage::disk('s3')->exists($tempPath)) {
  50. $storedSize = Storage::disk('s3')->size($tempPath);
  51. Log::info("File verification successful - size: {$storedSize} bytes");
  52. if ($storedSize === $uploadedFile->getSize()) {
  53. Log::info("File sizes match perfectly");
  54. return $tempPath;
  55. } else {
  56. Log::warning("⚠ File size mismatch - Original: {$uploadedFile->getSize()}, Stored: {$storedSize}");
  57. return $tempPath;
  58. }
  59. } else {
  60. throw new \Exception("File not found after putFileAs");
  61. }
  62. } catch (\Exception $e) {
  63. Log::warning("Method 1 failed: " . $e->getMessage());
  64. }
  65. try {
  66. Log::info("Trying Method 2: put with file contents");
  67. $fileContent = file_get_contents($uploadedFile->getRealPath());
  68. if (!$fileContent) {
  69. throw new \Exception("Could not read file contents");
  70. }
  71. $stored = Storage::disk('s3')->put($tempPath, $fileContent);
  72. if ($stored && Storage::disk('s3')->exists($tempPath)) {
  73. Log::info("Method 2 success - put with contents");
  74. return $tempPath;
  75. } else {
  76. throw new \Exception("Put method failed");
  77. }
  78. } catch (\Exception $e) {
  79. Log::warning("Method 2 failed: " . $e->getMessage());
  80. }
  81. throw new \Exception("All temp storage methods failed");
  82. } catch (\Exception $e) {
  83. Log::error("Error storing file temporarily: " . $e->getMessage());
  84. Log::error("Stack trace: " . $e->getTraceAsString());
  85. throw $e;
  86. }
  87. }
  88. /**
  89. * Upload attachment directly to final S3 location with client structure
  90. */
  91. public function uploadAttachment($file, $recordId, $type)
  92. {
  93. try {
  94. $clientName = $this->getClientName();
  95. $type = strtolower($type);
  96. $extension = $file->getClientOriginalExtension();
  97. $fileName = time() . '_' . Str::random(10) . '.' . $extension;
  98. $finalPath = "{$clientName}/records/{$type}/{$recordId}/attachments/{$fileName}";
  99. Log::info("Uploading attachment to S3:");
  100. Log::info("- Client: {$clientName}");
  101. Log::info("- Record ID: {$recordId}");
  102. Log::info("- Type: {$type}");
  103. Log::info("- File path: {$finalPath}");
  104. Log::info("- File size: " . $file->getSize() . " bytes");
  105. $storedPath = Storage::disk('s3')->putFileAs(
  106. "{$clientName}/records/{$type}/{$recordId}/attachments",
  107. $file,
  108. $fileName
  109. );
  110. Log::info("File uploaded successfully to S3: {$storedPath}");
  111. if (Storage::disk('s3')->exists($finalPath)) {
  112. Log::info("S3 upload verified successfully");
  113. return $finalPath;
  114. } else {
  115. throw new \Exception("File verification failed - not found on S3");
  116. }
  117. } catch (\Exception $e) {
  118. Log::error("Error uploading attachment to S3: " . $e->getMessage());
  119. throw $e;
  120. }
  121. }
  122. /**
  123. * Upload XML receipt for import functionality with client structure
  124. */
  125. public function uploadXmlReceipt($file, $recordId, $type)
  126. {
  127. try {
  128. $clientName = $this->getClientName();
  129. $type = strtolower($type);
  130. $extension = $file->getClientOriginalExtension() ?: 'xml';
  131. $fileName = 'receipt_' . time() . '_' . Str::random(8) . '.' . $extension;
  132. $finalPath = "{$clientName}/records/{$type}/{$recordId}/attachments/{$fileName}";
  133. Log::info("Uploading XML receipt to S3:");
  134. Log::info("- Client: {$clientName}");
  135. Log::info("- Path: {$finalPath}");
  136. $storedPath = Storage::disk('s3')->putFileAs(
  137. "{$clientName}/records/{$type}/{$recordId}/attachments",
  138. $file,
  139. $fileName
  140. );
  141. Log::info("XML receipt uploaded to S3: {$storedPath}");
  142. return $finalPath;
  143. } catch (\Exception $e) {
  144. Log::error("Error uploading XML receipt to S3: " . $e->getMessage());
  145. throw $e;
  146. }
  147. }
  148. /**
  149. * Get S3 attachment URL
  150. */
  151. public function getAttachmentUrl($filePath)
  152. {
  153. try {
  154. if (!$filePath) {
  155. return null;
  156. }
  157. Log::info("Getting S3 attachment URL for: {$filePath}");
  158. if (!Storage::disk('s3')->exists($filePath)) {
  159. Log::warning("S3 attachment file not found: {$filePath}");
  160. $directory = dirname($filePath);
  161. try {
  162. $files = Storage::disk('s3')->files($directory);
  163. Log::info("Files in S3 directory {$directory}: " . json_encode($files));
  164. } catch (\Exception $e) {
  165. Log::warning("Could not list S3 directory {$directory}: " . $e->getMessage());
  166. }
  167. return null;
  168. }
  169. $url = Storage::disk('s3')->temporaryUrl($filePath, now()->addHours(1));
  170. Log::info("Generated S3 temporary URL for: {$filePath}");
  171. return $url;
  172. } catch (\Exception $e) {
  173. Log::error("Error getting S3 attachment URL for {$filePath}: " . $e->getMessage());
  174. return null;
  175. }
  176. }
  177. /**
  178. * Delete attachment from S3
  179. */
  180. public function deleteAttachment($filePath)
  181. {
  182. try {
  183. if (!$filePath) {
  184. return false;
  185. }
  186. Log::info("Deleting S3 attachment: {$filePath}");
  187. if (Storage::disk('s3')->exists($filePath)) {
  188. $deleted = Storage::disk('s3')->delete($filePath);
  189. if ($deleted) {
  190. Log::info("S3 attachment deleted successfully: {$filePath}");
  191. return true;
  192. } else {
  193. Log::error("Failed to delete S3 attachment: {$filePath}");
  194. return false;
  195. }
  196. } else {
  197. Log::warning("S3 attachment not found for deletion: {$filePath}");
  198. return false;
  199. }
  200. } catch (\Exception $e) {
  201. Log::error("Error deleting S3 attachment: " . $e->getMessage());
  202. throw $e;
  203. }
  204. }
  205. /**
  206. * Debug S3 configuration and connectivity
  207. */
  208. public function debugFileSystem()
  209. {
  210. $clientName = $this->getClientName();
  211. Log::info("=== S3 DEBUG ===");
  212. Log::info("Client Name: {$clientName}");
  213. Log::info("S3 Configuration:");
  214. Log::info("- Bucket: " . config('filesystems.disks.s3.bucket'));
  215. Log::info("- Region: " . config('filesystems.disks.s3.region'));
  216. Log::info("- URL: " . config('filesystems.disks.s3.url'));
  217. Log::info("- Key: " . (config('filesystems.disks.s3.key') ? 'Set' : 'Not set'));
  218. Log::info("- Secret: " . (config('filesystems.disks.s3.secret') ? 'Set' : 'Not set'));
  219. try {
  220. $testFile = "{$clientName}/test_connection_" . time() . '.txt';
  221. $testContent = 'S3 connection test: ' . now();
  222. Log::info("Testing S3 connection with client structure...");
  223. Storage::disk('s3')->put($testFile, $testContent);
  224. if (Storage::disk('s3')->exists($testFile)) {
  225. Log::info("S3 connection test: SUCCESS");
  226. Storage::disk('s3')->delete($testFile);
  227. } else {
  228. Log::error("S3 connection test: FAILED - file not found after upload");
  229. }
  230. } catch (\Exception $e) {
  231. Log::error("S3 connection test: FAILED - " . $e->getMessage());
  232. }
  233. Log::info("=== END S3 DEBUG ===");
  234. }
  235. /**
  236. * Clean up old temp files from S3 for specific client or all clients
  237. */
  238. public function cleanupTempFiles($olderThanHours = 24, $specificClient = null)
  239. {
  240. try {
  241. $clientName = $specificClient ?: $this->getClientName();
  242. $tempPath = $specificClient ? "{$specificClient}/temp/uploads" : "{$clientName}/temp/uploads";
  243. $tempFiles = Storage::disk('s3')->files($tempPath);
  244. $cutoffTime = now()->subHours($olderThanHours);
  245. $deletedCount = 0;
  246. Log::info("Cleaning up S3 temp files for client '{$clientName}' older than {$olderThanHours} hours");
  247. foreach ($tempFiles as $file) {
  248. $fileTime = Storage::disk('s3')->lastModified($file);
  249. if ($fileTime < $cutoffTime->timestamp) {
  250. if (Storage::disk('s3')->delete($file)) {
  251. $deletedCount++;
  252. Log::info("Cleaned up old S3 temp file: {$file}");
  253. }
  254. }
  255. }
  256. Log::info("S3 cleanup completed for client '{$clientName}'. Deleted {$deletedCount} temp files.");
  257. } catch (\Exception $e) {
  258. Log::error("Error cleaning up S3 temp files: " . $e->getMessage());
  259. }
  260. }
  261. /**
  262. * Clean up temp files for all clients
  263. */
  264. public function cleanupAllClientTempFiles($olderThanHours = 24)
  265. {
  266. try {
  267. $allDirectories = Storage::disk('s3')->directories('');
  268. $deletedCount = 0;
  269. Log::info("Cleaning up temp files for all clients older than {$olderThanHours} hours");
  270. foreach ($allDirectories as $clientDir) {
  271. $tempPath = "{$clientDir}/temp/uploads";
  272. if (Storage::disk('s3')->exists($tempPath)) {
  273. $tempFiles = Storage::disk('s3')->files($tempPath);
  274. $cutoffTime = now()->subHours($olderThanHours);
  275. foreach ($tempFiles as $file) {
  276. $fileTime = Storage::disk('s3')->lastModified($file);
  277. if ($fileTime < $cutoffTime->timestamp) {
  278. if (Storage::disk('s3')->delete($file)) {
  279. $deletedCount++;
  280. Log::info("Cleaned up old S3 temp file: {$file}");
  281. }
  282. }
  283. }
  284. }
  285. }
  286. Log::info("S3 cleanup completed for all clients. Deleted {$deletedCount} temp files.");
  287. } catch (\Exception $e) {
  288. Log::error("Error cleaning up all client temp files: " . $e->getMessage());
  289. }
  290. }
  291. /**
  292. * Check if a file exists on S3
  293. */
  294. public function fileExists($filePath)
  295. {
  296. try {
  297. return Storage::disk('s3')->exists($filePath);
  298. } catch (\Exception $e) {
  299. Log::error("Error checking if S3 file exists: " . $e->getMessage());
  300. return false;
  301. }
  302. }
  303. /**
  304. * Get file size from S3
  305. */
  306. public function getFileSize($filePath)
  307. {
  308. try {
  309. if (Storage::disk('s3')->exists($filePath)) {
  310. return Storage::disk('s3')->size($filePath);
  311. }
  312. return 0;
  313. } catch (\Exception $e) {
  314. Log::error("Error getting S3 file size: " . $e->getMessage());
  315. return 0;
  316. }
  317. }
  318. /**
  319. * Get file last modified time from S3
  320. */
  321. public function getFileLastModified($filePath)
  322. {
  323. try {
  324. if (Storage::disk('s3')->exists($filePath)) {
  325. return Storage::disk('s3')->lastModified($filePath);
  326. }
  327. return null;
  328. } catch (\Exception $e) {
  329. Log::error("Error getting S3 file last modified: " . $e->getMessage());
  330. return null;
  331. }
  332. }
  333. /**
  334. * Get all files for a specific client and type
  335. */
  336. public function getClientFiles($type = null, $clientName = null)
  337. {
  338. try {
  339. $clientName = $clientName ?: $this->getClientName();
  340. $type = $type ? strtolower($type) : '*';
  341. $basePath = $type === '*' ? "{$clientName}/records" : "{$clientName}/records/{$type}";
  342. $files = Storage::disk('s3')->allFiles($basePath);
  343. Log::info("Found " . count($files) . " files for client '{$clientName}' and type '{$type}'");
  344. return $files;
  345. } catch (\Exception $e) {
  346. Log::error("Error getting client files: " . $e->getMessage());
  347. return [];
  348. }
  349. }
  350. }