get('per_page', 15); $filters = [ 'search' => $request->get('search'), 'mime_type' => $request->get('mime_type'), 'uploaded_by' => $request->get('uploaded_by'), 'category' => $request->get('category'), 'client_id' => $request->get('client_id'), 'date_from' => $request->get('date_from'), 'date_to' => $request->get('date_to'), 'sort_by' => $request->get('sort_by', 'uploaded_at'), 'sort_direction' => $request->get('sort_direction', 'desc'), ]; // Remove null filters $filters = array_filter($filters, function ($value) { return $value !== null && $value !== ''; }); $files = $this->fileRepository->paginate($perPage, $filters); return new FileCollection($files); } catch (\Exception $e) { Log::error('Error fetching files: ' . $e->getMessage(), [ 'exception' => $e, 'trace' => $e->getTraceAsString(), ]); return response()->json([ 'message' => 'Une erreur est survenue lors de la récupération des fichiers.', 'error' => config('app.debug') ? $e->getMessage() : null, ], 500); } } /** * Store a newly uploaded file. */ public function store(StoreFileRequest $request): FileResource|JsonResponse { try { $validatedData = $request->validated(); $file = $request->file('file'); // Generate organized storage path $storagePath = $this->generateOrganizedPath( $validatedData['category'], $validatedData['client_id'] ?? null, $validatedData['subcategory'] ?? null, $validatedData['file_name'] ); // Store the file $storedFilePath = $file->store($storagePath, 'public'); // Calculate SHA256 hash $hash = hash_file('sha256', $file->path()); // Prepare data for database $fileData = array_merge($validatedData, [ 'storage_uri' => $storedFilePath, 'sha256' => $hash, 'uploaded_by' => $request->user()->id, 'uploaded_at' => now(), ]); $createdFile = $this->fileRepository->create($fileData); return new FileResource($createdFile); } catch (\Exception $e) { Log::error('Error uploading file: ' . $e->getMessage(), [ 'exception' => $e, 'trace' => $e->getTraceAsString(), 'user_id' => $request->user()->id, ]); return response()->json([ 'message' => 'Une erreur est survenue lors de l\'upload du fichier.', 'error' => config('app.debug') ? $e->getMessage() : null, ], 500); } } /** * Display the specified file. */ public function show(string $id): FileResource|JsonResponse { try { $file = $this->fileRepository->find($id); if (!$file) { return response()->json([ 'message' => 'Fichier non trouvé.', ], 404); } return new FileResource($file); } catch (\Exception $e) { Log::error('Error fetching file: ' . $e->getMessage(), [ 'exception' => $e, 'trace' => $e->getTraceAsString(), 'file_id' => $id, ]); return response()->json([ 'message' => 'Une erreur est survenue lors de la récupération du fichier.', 'error' => config('app.debug') ? $e->getMessage() : null, ], 500); } } /** * Update the specified file metadata. */ public function update(UpdateFileRequest $request, string $id): FileResource|JsonResponse { try { $file = $this->fileRepository->find($id); if (!$file) { return response()->json([ 'message' => 'Fichier non trouvé.', ], 404); } $validatedData = $request->validated(); // If category or client changed, move the file if (isset($validatedData['category']) || isset($validatedData['client_id']) || isset($validatedData['subcategory'])) { $newStoragePath = $this->generateOrganizedPath( $validatedData['category'] ?? $this->extractCategoryFromPath($file->storage_uri), $validatedData['client_id'] ?? $this->extractClientFromPath($file->storage_uri), $validatedData['subcategory'] ?? $this->extractSubcategoryFromPath($file->storage_uri), $file->file_name ); if ($newStoragePath !== $file->storage_uri) { // Move file to new location Storage::disk('public')->move($file->storage_uri, $newStoragePath); $validatedData['storage_uri'] = $newStoragePath; } } $updated = $this->fileRepository->update($id, $validatedData); if (!$updated) { return response()->json([ 'message' => 'Fichier non trouvé ou échec de la mise à jour.', ], 404); } $updatedFile = $this->fileRepository->find($id); return new FileResource($updatedFile); } catch (\Exception $e) { Log::error('Error updating file: ' . $e->getMessage(), [ 'exception' => $e, 'trace' => $e->getTraceAsString(), 'file_id' => $id, ]); return response()->json([ 'message' => 'Une erreur est survenue lors de la mise à jour du fichier.', 'error' => config('app.debug') ? $e->getMessage() : null, ], 500); } } /** * Remove the specified file. */ public function destroy(string $id): JsonResponse { try { $file = $this->fileRepository->find($id); if (!$file) { return response()->json([ 'message' => 'Fichier non trouvé.', ], 404); } // Delete file from storage Storage::disk('public')->delete($file->storage_uri); // Delete from database $deleted = $this->fileRepository->delete($id); if (!$deleted) { return response()->json([ 'message' => 'Fichier non trouvé ou échec de la suppression.', ], 404); } return response()->json([ 'message' => 'Fichier supprimé avec succès.', ], 200); } catch (\Exception $e) { Log::error('Error deleting file: ' . $e->getMessage(), [ 'exception' => $e, 'trace' => $e->getTraceAsString(), 'file_id' => $id, ]); return response()->json([ 'message' => 'Une erreur est survenue lors de la suppression du fichier.', 'error' => config('app.debug') ? $e->getMessage() : null, ], 500); } } /** * Get files by category. */ public function byCategory(Request $request, string $category): FileCollection|JsonResponse { try { $perPage = $request->get('per_page', 15); $files = $this->fileRepository->getByCategory($category, $perPage); return new FileCollection($files); } catch (\Exception $e) { Log::error('Error fetching files by category: ' . $e->getMessage(), [ 'exception' => $e, 'category' => $category, ]); return response()->json([ 'message' => 'Une erreur est survenue lors de la récupération des fichiers par catégorie.', 'error' => config('app.debug') ? $e->getMessage() : null, ], 500); } } /** * Get files by client. */ public function byClient(Request $request, int $clientId): FileCollection|JsonResponse { try { $perPage = $request->get('per_page', 15); $files = $this->fileRepository->getByClient($clientId, $perPage); return new FileCollection($files); } catch (\Exception $e) { Log::error('Error fetching files by client: ' . $e->getMessage(), [ 'exception' => $e, 'client_id' => $clientId, ]); return response()->json([ 'message' => 'Une erreur est survenue lors de la récupération des fichiers du client.', 'error' => config('app.debug') ? $e->getMessage() : null, ], 500); } } /** * Get organized file structure. */ public function organized(): JsonResponse { try { $organizedFiles = $this->fileRepository->getOrganizedFiles(); return response()->json([ 'data' => $organizedFiles, 'message' => 'Structure de fichiers récupérée avec succès.', ], 200); } catch (\Exception $e) { Log::error('Error fetching organized files: ' . $e->getMessage(), [ 'exception' => $e, ]); return response()->json([ 'message' => 'Une erreur est survenue lors de la récupération de la structure de fichiers.', 'error' => config('app.debug') ? $e->getMessage() : null, ], 500); } } /** * Get storage statistics. */ public function stats(): JsonResponse { try { $stats = $this->fileRepository->getStorageStats(); return response()->json([ 'data' => $stats, 'message' => 'Statistiques de stockage récupérées avec succès.', ], 200); } catch (\Exception $e) { Log::error('Error fetching storage stats: ' . $e->getMessage(), [ 'exception' => $e, ]); return response()->json([ 'message' => 'Une erreur est survenue lors de la récupération des statistiques.', 'error' => config('app.debug') ? $e->getMessage() : null, ], 500); } } /** * Download a file. */ public function download(string $id): JsonResponse { try { $file = $this->fileRepository->find($id); if (!$file) { return response()->json([ 'message' => 'Fichier non trouvé.', ], 404); } if (!Storage::disk('public')->exists($file->storage_uri)) { return response()->json([ 'message' => 'Fichier physique non trouvé sur le stockage.', ], 404); } $downloadUrl = Storage::disk('public')->url($file->storage_uri); return response()->json([ 'data' => [ 'download_url' => $downloadUrl, 'file_name' => $file->file_name, 'mime_type' => $file->mime_type, ], 'message' => 'URL de téléchargement générée avec succès.', ], 200); } catch (\Exception $e) { Log::error('Error generating download URL: ' . $e->getMessage(), [ 'exception' => $e, 'file_id' => $id, ]); return response()->json([ 'message' => 'Une erreur est survenue lors de la génération de l\'URL de téléchargement.', 'error' => config('app.debug') ? $e->getMessage() : null, ], 500); } } /** * Generate organized storage path. */ private function generateOrganizedPath(string $category, ?int $clientId, ?string $subcategory, string $fileName): string { $pathParts = []; if ($clientId) { $pathParts[] = 'client'; $pathParts[] = $clientId; } else { $pathParts[] = 'general'; } $pathParts[] = $category; if ($subcategory) { $pathParts[] = Str::slug($subcategory); } else { $pathParts[] = 'files'; } // Add timestamp to avoid conflicts $timestamp = now()->format('Y-m-d_H-i-s'); $extension = pathinfo($fileName, PATHINFO_EXTENSION); $basename = pathinfo($fileName, PATHINFO_FILENAME); $safeFilename = Str::slug($basename) . '_' . $timestamp . '.' . $extension; $pathParts[] = $safeFilename; return implode('/', $pathParts); } /** * Extract category from storage path. */ private function extractCategoryFromPath(string $storageUri): string { $pathParts = explode('/', $storageUri); return $pathParts[count($pathParts) - 3] ?? 'general'; } /** * Extract client ID from storage path. */ private function extractClientFromPath(string $storageUri): ?int { $pathParts = explode('/', $storageUri); if (count($pathParts) >= 4 && $pathParts[0] === 'client') { return (int) $pathParts[1]; } return null; } /** * Extract subcategory from storage path. */ private function extractSubcategoryFromPath(string $storageUri): ?string { $pathParts = explode('/', $storageUri); return $pathParts[count($pathParts) - 2] ?? null; } }