445 lines
14 KiB
PHP
445 lines
14 KiB
PHP
<?php
|
|
|
|
declare(strict_types=1);
|
|
|
|
namespace App\Http\Controllers\Api;
|
|
|
|
use App\Http\Controllers\Controller;
|
|
use App\Http\Requests\StoreFileRequest;
|
|
use App\Http\Requests\UpdateFileRequest;
|
|
use App\Http\Resources\File\FileResource;
|
|
use App\Http\Resources\File\FileCollection;
|
|
use App\Repositories\FileRepositoryInterface;
|
|
use Illuminate\Http\JsonResponse;
|
|
use Illuminate\Http\Request;
|
|
use Illuminate\Http\Resources\Json\AnonymousResourceCollection;
|
|
use Illuminate\Support\Facades\Log;
|
|
use Illuminate\Support\Facades\Storage;
|
|
use Illuminate\Support\Str;
|
|
|
|
class FileController extends Controller
|
|
{
|
|
public function __construct(
|
|
private readonly FileRepositoryInterface $fileRepository
|
|
) {
|
|
}
|
|
|
|
/**
|
|
* Display a listing of files.
|
|
*/
|
|
public function index(Request $request): FileCollection|JsonResponse
|
|
{
|
|
try {
|
|
$perPage = $request->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;
|
|
}
|
|
}
|