2025-12-01 17:02:01 +03:00

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;
}
}