Skip to content

Instantly share code, notes, and snippets.

@medaminebt
Created December 18, 2024 16:20
Show Gist options
  • Save medaminebt/23ff6d1809f5dce9c6ac7d5214deae29 to your computer and use it in GitHub Desktop.
Save medaminebt/23ff6d1809f5dce9c6ac7d5214deae29 to your computer and use it in GitHub Desktop.
Controller handeling cloudflare R2 Storage
<?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