Created
December 18, 2024 16:20
-
-
Save medaminebt/23ff6d1809f5dce9c6ac7d5214deae29 to your computer and use it in GitHub Desktop.
Controller handeling cloudflare R2 Storage
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
namespace App\Http\Controllers; | |
use App\Models\File; | |
use Illuminate\Http\Request; | |
use Illuminate\Support\Facades\Log; | |
use Illuminate\Support\Facades\Storage; | |
class FilesRController extends Controller | |
{ | |
public function r2_upload_file(Request $request) | |
{ | |
try { | |
$request->validate([ | |
'company_id' => 'required|integer|exists:companies,id', | |
'type' => [ | |
'required', | |
'integer', | |
function ($attribute, $value, $fail) { | |
if ($value != File::TYPE_R2) { | |
$fail('The ' . $attribute . ' must be ' . File::TYPE_R2 . '.'); | |
} | |
}, | |
], | |
'file' => 'required|file', | |
]); | |
// Generate a unique file name | |
$originalFileName = $request->file('file')->getClientOriginalName(); | |
$file_name = auth()->user()->id . '_' . time() . '_' . $originalFileName; | |
$file_path = 'files/' . $file_name; | |
// Store the file in R2 with public visibility | |
Storage::disk('r2')->putFileAs('files', $request->file('file'), $file_name, [ | |
'visibility' => 'public', | |
]); | |
// Get the base URL from configuration | |
$baseUrl = Storage::disk('r2')->url('/'); | |
// Generate the default URL | |
$url = rtrim($baseUrl, '/') . '/' . $file_path; | |
// Generate the custom domain URL if available | |
$customDomain = env('R2_CUSTOM_DOMAIN'); | |
$customDomainUrl = null; | |
if ($customDomain) { | |
$customDomainUrl = rtrim($customDomain, '/') . '/' . $file_path; | |
} | |
// Save the file information in the database | |
$file = File::create([ | |
'company_id' => $request->company_id, | |
'user_id' => auth()->user()->id, | |
'type' => $request->type, | |
'file_name' => $file_path, | |
'url' => $url, | |
]); | |
insert_in_history_table('created', $file->id, $file->getTable()); | |
// Prepare the response data | |
$responseData = [ | |
'file_id' => $file->id, | |
'url' => $url, | |
'message' => 'File uploaded successfully to R2.', | |
]; | |
if ($customDomainUrl) { | |
$responseData['custom_domain_url'] = $customDomainUrl; | |
} | |
return response()->json($responseData); | |
} catch (\Aws\S3\Exception\S3Exception $e) { | |
report($e); | |
return response()->json([ | |
'message' => 'Unable to upload the file. Please contact your platform administrator.', | |
], 500); | |
} catch (\Throwable $th) { | |
report($th); | |
return response()->json([ | |
'message' => 'An unexpected error occurred. Please contact your platform administrator.', | |
], 500); | |
} | |
} | |
public function r2_storage_usage() | |
{ | |
try { | |
// Get all files in the R2 bucket | |
$objects = Storage::disk('r2')->allFiles(); | |
$totalSize = 0; | |
$fileCount = count($objects); | |
$fileSizes = []; | |
$fileTypes = []; | |
foreach ($objects as $object) { | |
// Get the file size | |
$size = Storage::disk('r2')->size($object); | |
$totalSize += $size; | |
$fileSizes[] = $size; | |
// Get file extension/type | |
$extension = pathinfo($object, PATHINFO_EXTENSION); | |
$fileTypes[$extension] = ($fileTypes[$extension] ?? 0) + 1; | |
} | |
// Calculate additional statistics | |
$averageSize = $fileCount > 0 ? $totalSize / $fileCount : 0; | |
$maxSize = !empty($fileSizes) ? max($fileSizes) : 0; | |
$minSize = !empty($fileSizes) ? min($fileSizes) : 0; | |
// Total capacity in bytes (from .env or default to 10 GB) | |
$totalCapacityGB = env('R2_TOTAL_CAPACITY_GB', 10); | |
$totalCapacityBytes = $totalCapacityGB * pow(1024, 3); // Convert GB to bytes | |
// Calculate percentage used | |
$percentageUsed = $totalCapacityBytes > 0 ? ($totalSize / $totalCapacityBytes) * 100 : 0; | |
$percentageUsedFormatted = number_format($percentageUsed, 4) . '%'; | |
// Calculate remaining storage | |
$remainingBytes = $totalCapacityBytes - $totalSize; | |
// Format sizes with increased precision | |
$totalSizeGB = $this->formatBytes($totalSize, 4, 'GB'); | |
$remainingGB = $this->formatBytes($remainingBytes, 4, 'GB'); | |
$averageSizeReadable = $this->formatBytes($averageSize); | |
$maxSizeReadable = $this->formatBytes($maxSize); | |
$minSizeReadable = $this->formatBytes($minSize); | |
// Prepare response data | |
$responseData = [ | |
'total_files' => $fileCount, | |
'total_size_bytes' => $totalSize, | |
'total_size_gb' => $totalSizeGB, | |
'total_capacity_gb' => $totalCapacityGB . ' GB', | |
'remaining_storage_gb' => $remainingGB, | |
'percentage_used' => $percentageUsedFormatted, | |
'average_file_size' => $averageSizeReadable, | |
'largest_file_size' => $maxSizeReadable, | |
'smallest_file_size' => $minSizeReadable, | |
'file_types_count' => $fileTypes, | |
'message' => 'R2 Cloud Storage usage calculated successfully.', | |
]; | |
return response()->json($responseData); | |
} catch (\Aws\S3\Exception\S3Exception $e) { | |
// Handle AWS S3 specific exceptions | |
report($e); | |
return response()->json([ | |
'message' => 'Unable to connect to the storage service. Please contact your platform administrator.', | |
], 500); | |
} catch (\Throwable $th) { | |
// Handle other exceptions | |
report($th); | |
return response()->json([ | |
'message' => 'An unexpected error occurred. Please contact your platform administrator.', | |
], 500); | |
} | |
} | |
private function formatBytes($bytes, $precision = 2, $targetUnit = null) | |
{ | |
$units = ['B', 'KB', 'MB', 'GB', 'TB']; | |
$bytes = max($bytes, 0); | |
$pow = $targetUnit ? array_search($targetUnit, $units) : floor(($bytes ? log($bytes) : 0) / log(1024)); | |
$pow = min($pow, count($units) - 1); | |
// Calculate the value | |
$bytes /= pow(1024, $pow); | |
return round($bytes, $precision) . ' ' . $units[$pow]; | |
} | |
public function r2_bulk_upload(Request $request) | |
{ | |
try { | |
$request->validate([ | |
'company_id' => 'required|integer|exists:companies,id', | |
'type' => [ | |
'required', | |
'integer', | |
function ($attribute, $value, $fail) { | |
if ($value != File::TYPE_R2) { | |
$fail('The ' . $attribute . ' must be ' . File::TYPE_R2 . '.'); | |
} | |
}, | |
], | |
'files' => 'required|array', | |
'files.*' => 'required|file', | |
]); | |
$uploadedFiles = []; | |
foreach ($request->file('files') as $file) { | |
// Generate a unique file name | |
$originalFileName = $file->getClientOriginalName(); | |
$fileName = auth()->user()->id . '_' . time() . '_' . $originalFileName; | |
$filePath = 'files/' . $fileName; | |
// Store the file in R2 with public visibility | |
Storage::disk('r2')->putFileAs('files', $file, $fileName, [ | |
'visibility' => 'public', | |
]); | |
// Get the base URL from configuration | |
$baseUrl = Storage::disk('r2')->url('/'); | |
// Generate the URL | |
$url = rtrim($baseUrl, '/') . '/' . $filePath; | |
// Save the file information in the database | |
$fileModel = File::create([ | |
'company_id' => $request->company_id, | |
'user_id' => auth()->user()->id, | |
'type' => $request->type, | |
'file_name' => $filePath, | |
'url' => $url, | |
]); | |
// Add to history | |
insert_in_history_table('created', $fileModel->id, $fileModel->getTable()); | |
$uploadedFiles[] = [ | |
'file_id' => $fileModel->id, | |
'url' => $url, | |
'file_name' => $originalFileName, | |
]; | |
} | |
return response()->json([ | |
'message' => 'Files uploaded successfully to R2.', | |
'files' => $uploadedFiles, | |
]); | |
} catch (\Aws\S3\Exception\S3Exception $e) { | |
report($e); | |
return response()->json([ | |
'message' => 'Unable to upload the files. Please contact your platform administrator.', | |
], 500); | |
} catch (\Throwable $th) { | |
report($th); | |
return response()->json([ | |
'message' => 'An unexpected error occurred. Please contact your platform administrator.', | |
], 500); | |
} | |
} | |
////////////////////////////// | |
// TEST APIs | |
// TEST API | |
public function r2_delete_file($id) | |
{ | |
try { | |
logger('delete_file called with ID:', ['id' => $id]); | |
$file = File::findOrFail($id); | |
logger('File retrieved:', ['id' => $file->id, 'type' => $file->type]); | |
if ((int)$file->type !== (int)File::TYPE_R2) { | |
logger('Type mismatch:', ['expected' => File::TYPE_R2, 'actual' => $file->type]); | |
return response()->json(['message' => 'File is not stored in R2.'], 400); | |
} | |
// Delete the file from R2 storage | |
if (Storage::disk('r2')->exists($file->file_name)) { | |
Storage::disk('r2')->delete($file->file_name); | |
logger('File deleted from R2 storage:', ['file_name' => $file->file_name]); | |
} else { | |
logger('File not found in R2 storage:', ['file_name' => $file->file_name]); | |
} | |
// Hard delete from the database | |
$file->delete(); | |
logger('File record deleted from database:', ['id' => $file->id]); | |
return response()->json(['message' => 'File deleted successfully from R2 and database.']); | |
} catch (\Aws\S3\Exception\S3Exception $e) { | |
report($e); | |
logger('AWS S3 Exception:', ['exception' => $e]); | |
return response()->json([ | |
'message' => 'Unable to delete the file from storage. Please contact your platform administrator.', | |
], 500); | |
} catch (\Exception $e) { | |
report($e); | |
logger('General Exception:', ['exception' => $e]); | |
return response()->json([ | |
'message' => 'An unexpected error occurred. Please contact your platform administrator.', | |
], 500); | |
} | |
} | |
// TEST API | |
public function list_all_storage_files(Request $request) | |
{ | |
try { | |
// Get the 'page' and 'per_page' parameters from the request, with default values | |
$page = $request->input('page', 1); | |
$perPage = $request->input('per_page', 20); | |
// Retrieve files with pagination | |
$filesQuery = File::query()->select(['id', 'file_name', 'url', 'type']); | |
// Apply filters | |
if ($request->has('type')) { | |
$filesQuery->where('type', $request->input('type')); | |
} | |
// Filter by folder name if provided | |
if ($request->has('folder_name')) { | |
$folderName = $request->input('folder_name'); | |
$folderPath = 'files/' . $folderName . '/%'; | |
$filesQuery->where('file_name', 'like', $folderPath); | |
} | |
// Paginate the results | |
$files = $filesQuery->paginate($perPage, ['*'], 'page', $page); | |
// Prepare the response data | |
$filesData = []; | |
foreach ($files->items() as $file) { | |
// Extract folder name from file_name | |
$folderPath = dirname($file->file_name); | |
$folderName = str_replace('files/', '', $folderPath); // Remove 'files/' prefix | |
$filesData[] = [ | |
'id' => $file->id, | |
'file_name' => $file->file_name, | |
'url' => $file->url, | |
'type' => $file->type, | |
'folder_name' => $folderName, | |
]; | |
} | |
$responseData = [ | |
'files' => $filesData, | |
'pagination' => [ | |
'total_files' => $files->total(), | |
'per_page' => $files->perPage(), | |
'current_page' => $files->currentPage(), | |
'last_page' => $files->lastPage(), | |
'from' => $files->firstItem(), | |
'to' => $files->lastItem(), | |
], | |
'message' => 'Files retrieved successfully.' | |
]; | |
return response()->json($responseData); | |
} catch (\Throwable $th) { | |
report($th); | |
return response()->json([ | |
'message' => 'Unable to retrieve files. Please contact your platform administrator.' | |
], 500); | |
} | |
} | |
// TEST API | |
/** | |
* List all folders in the R2 bucket under 'files/' and count the number of files in each. | |
* | |
*/ | |
public function list_r2_folders() | |
{ | |
try { | |
// *** Logging: Start of list_r2_folders *** | |
// Remove this log in production | |
Log::info('list_r2_folders called'); | |
// Define the prefix | |
$prefix = 'files/'; | |
// List all contents under 'files/' with recursive flag | |
$objects = Storage::disk('r2')->listContents($prefix, true); | |
// Extract unique folder names | |
$folders = []; | |
foreach ($objects as $object) { | |
if ($object['type'] === 'file') { | |
// Extract the immediate folder name after 'files/' | |
$pathParts = explode('/', $object['path']); | |
if (count($pathParts) >= 2) { | |
$folderName = $pathParts[1]; | |
if (!isset($folders[$folderName])) { | |
$folders[$folderName] = 0; | |
} | |
$folders[$folderName]++; | |
} | |
} | |
} | |
// *** Logging: Folders and file counts *** | |
// Remove this log in production | |
Log::info('Folders and file counts', ['folders' => $folders]); | |
return response()->json([ | |
'folders' => $folders, | |
'message' => ' R2 Cloud Storage - Folders retrieved successfully.' | |
], 200); | |
} catch (\Aws\S3\Exception\S3Exception $e) { | |
report($e); | |
// *** Logging: S3Exception caught in list_r2_folders *** | |
// Remove this log in production | |
Log::error('S3Exception in list_r2_folders', ['error' => $e->getMessage()]); | |
return response()->json([ | |
'message' => 'Unable to retrieve folders. Please contact your platform administrator.', | |
'error' => $e->getMessage(), // Include for debugging (remove in production) | |
], 500); | |
} catch (\Throwable $th) { | |
report($th); | |
// *** Logging: General exception caught in list_r2_folders *** | |
// Remove this log in production | |
Log::error('Exception in list_r2_folders', ['error' => $th->getMessage(), 'trace' => $th->getTraceAsString()]); | |
return response()->json([ | |
'message' => 'An unexpected error occurred. Please contact your platform administrator.', | |
'error' => $th->getMessage(), // Included for debugging (remove in production) | |
], 500); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment