feat(storage): implement S3 file upload and temporary URL generation functionality
This commit is contained in:
parent
3360e81e96
commit
049484dee5
|
|
@ -0,0 +1,39 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\StorageTest\Controllers;
|
||||||
|
|
||||||
|
use App\Domains\StorageTest\Requests\GenerateS3TemporaryUrlRequest;
|
||||||
|
use App\Domains\StorageTest\Requests\StoreS3TestFileRequest;
|
||||||
|
use App\Domains\StorageTest\Services\S3TestService;
|
||||||
|
use App\Http\Controllers\Controller;
|
||||||
|
use Illuminate\Http\JsonResponse;
|
||||||
|
|
||||||
|
class S3TestController extends Controller
|
||||||
|
{
|
||||||
|
public function __construct(
|
||||||
|
protected S3TestService $s3TestService,
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
public function store(StoreS3TestFileRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json(
|
||||||
|
$this->s3TestService->storeTestFile(
|
||||||
|
$request->file('file'),
|
||||||
|
$request->validated('directory'),
|
||||||
|
(int) $request->validated('expires_in_minutes', 10),
|
||||||
|
),
|
||||||
|
201,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
public function temporaryUrl(GenerateS3TemporaryUrlRequest $request): JsonResponse
|
||||||
|
{
|
||||||
|
return response()->json(
|
||||||
|
$this->s3TestService->generateTemporaryUrl(
|
||||||
|
$request->validated('path'),
|
||||||
|
(int) $request->validated('expires_in_minutes', 10),
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,24 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\StorageTest\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class GenerateS3TemporaryUrlRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, array<int, string>>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'path' => ['required', 'string', 'max:2048'],
|
||||||
|
'expires_in_minutes' => ['nullable', 'integer', 'min:1', 'max:1440'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,25 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\StorageTest\Requests;
|
||||||
|
|
||||||
|
use Illuminate\Foundation\Http\FormRequest;
|
||||||
|
|
||||||
|
class StoreS3TestFileRequest extends FormRequest
|
||||||
|
{
|
||||||
|
public function authorize(): bool
|
||||||
|
{
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, array<int, string>>
|
||||||
|
*/
|
||||||
|
public function rules(): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'file' => ['required', 'file', 'max:10240'],
|
||||||
|
'directory' => ['nullable', 'string', 'max:255'],
|
||||||
|
'expires_in_minutes' => ['nullable', 'integer', 'min:1', 'max:1440'],
|
||||||
|
];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,79 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
namespace App\Domains\StorageTest\Services;
|
||||||
|
|
||||||
|
use Illuminate\Http\UploadedFile;
|
||||||
|
use Illuminate\Support\Facades\Log;
|
||||||
|
use Illuminate\Support\Facades\Storage;
|
||||||
|
use Illuminate\Support\Str;
|
||||||
|
use RuntimeException;
|
||||||
|
|
||||||
|
class S3TestService
|
||||||
|
{
|
||||||
|
/**
|
||||||
|
* @return array<string, int|string|null>
|
||||||
|
*/
|
||||||
|
public function storeTestFile(
|
||||||
|
UploadedFile $file,
|
||||||
|
?string $directory = null,
|
||||||
|
int $expiresInMinutes = 10,
|
||||||
|
): array {
|
||||||
|
$directory = $this->normalizeDirectory($directory);
|
||||||
|
$disk = Storage::disk('s3');
|
||||||
|
$path = $disk->putFile($directory, $file);
|
||||||
|
|
||||||
|
if (! is_string($path) || $path === '') {
|
||||||
|
Log::error('S3 upload returned an empty path.', [
|
||||||
|
'disk' => 's3',
|
||||||
|
'directory' => $directory,
|
||||||
|
'original_name' => $file->getClientOriginalName(),
|
||||||
|
'mime_type' => $file->getClientMimeType(),
|
||||||
|
'size' => $file->getSize(),
|
||||||
|
]);
|
||||||
|
|
||||||
|
throw new RuntimeException('No se pudo subir el archivo al disco s3.');
|
||||||
|
}
|
||||||
|
|
||||||
|
return [
|
||||||
|
'disk' => 's3',
|
||||||
|
'directory' => $directory,
|
||||||
|
'path' => $path,
|
||||||
|
'filename' => basename($path),
|
||||||
|
'original_name' => $file->getClientOriginalName(),
|
||||||
|
'mime_type' => $file->getClientMimeType(),
|
||||||
|
'extension' => $file->extension(),
|
||||||
|
'size' => $file->getSize(),
|
||||||
|
'temporary_url' => $disk->temporaryUrl($path, now()->addMinutes($expiresInMinutes)),
|
||||||
|
'temporary_url_expires_at' => now()->addMinutes($expiresInMinutes)->toIso8601String(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @return array<string, string>
|
||||||
|
*/
|
||||||
|
public function generateTemporaryUrl(string $path, int $expiresInMinutes = 10): array
|
||||||
|
{
|
||||||
|
return [
|
||||||
|
'disk' => 's3',
|
||||||
|
'path' => $path,
|
||||||
|
'temporary_url' => $this->temporaryUrlForPath($path, $expiresInMinutes),
|
||||||
|
'temporary_url_expires_at' => now()->addMinutes($expiresInMinutes)->toIso8601String(),
|
||||||
|
];
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function temporaryUrlForPath(string $path, int $expiresInMinutes): string
|
||||||
|
{
|
||||||
|
return Storage::disk('s3')->temporaryUrl($path, now()->addMinutes($expiresInMinutes));
|
||||||
|
}
|
||||||
|
|
||||||
|
protected function normalizeDirectory(?string $directory): string
|
||||||
|
{
|
||||||
|
$directory = trim((string) $directory, '/');
|
||||||
|
|
||||||
|
if ($directory !== '') {
|
||||||
|
return $directory;
|
||||||
|
}
|
||||||
|
|
||||||
|
return 'testing/attachments/'.now()->format('Y/m/d').'/'.Str::uuid();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
@ -0,0 +1,9 @@
|
||||||
|
<?php
|
||||||
|
|
||||||
|
use App\Domains\StorageTest\Controllers\S3TestController;
|
||||||
|
use Illuminate\Support\Facades\Route;
|
||||||
|
|
||||||
|
Route::prefix('storage-test/s3')->group(function (): void {
|
||||||
|
Route::post('upload', [S3TestController::class, 'store']);
|
||||||
|
Route::get('temporary-url', [S3TestController::class, 'temporaryUrl']);
|
||||||
|
});
|
||||||
|
|
@ -0,0 +1,95 @@
|
||||||
|
{
|
||||||
|
"info": {
|
||||||
|
"_postman_id": "8faefdb8-734a-4262-a3b6-4d49e56ea901",
|
||||||
|
"name": "Storage Test S3",
|
||||||
|
"schema": "https://schema.getpostman.com/json/collection/v2.1.0/collection.json"
|
||||||
|
},
|
||||||
|
"variable": [
|
||||||
|
{
|
||||||
|
"key": "base_url",
|
||||||
|
"value": "http://127.0.0.1:8000"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "expires_in_minutes",
|
||||||
|
"value": "10"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "path",
|
||||||
|
"value": ""
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"item": [
|
||||||
|
{
|
||||||
|
"name": "Upload Test File",
|
||||||
|
"request": {
|
||||||
|
"method": "POST",
|
||||||
|
"header": [],
|
||||||
|
"body": {
|
||||||
|
"mode": "formdata",
|
||||||
|
"formdata": [
|
||||||
|
{
|
||||||
|
"key": "file",
|
||||||
|
"type": "file",
|
||||||
|
"src": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "directory",
|
||||||
|
"value": "testing/manual",
|
||||||
|
"type": "text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "expires_in_minutes",
|
||||||
|
"value": "{{expires_in_minutes}}",
|
||||||
|
"type": "text"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/storage-test/s3/upload",
|
||||||
|
"host": [
|
||||||
|
"{{base_url}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"api",
|
||||||
|
"storage-test",
|
||||||
|
"s3",
|
||||||
|
"upload"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"description": "Sube un archivo al disco s3 y devuelve el path junto con una temporary_url."
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "Generate Temporary URL",
|
||||||
|
"request": {
|
||||||
|
"method": "GET",
|
||||||
|
"header": [],
|
||||||
|
"url": {
|
||||||
|
"raw": "{{base_url}}/api/storage-test/s3/temporary-url?path={{path}}&expires_in_minutes={{expires_in_minutes}}",
|
||||||
|
"host": [
|
||||||
|
"{{base_url}}"
|
||||||
|
],
|
||||||
|
"path": [
|
||||||
|
"api",
|
||||||
|
"storage-test",
|
||||||
|
"s3",
|
||||||
|
"temporary-url"
|
||||||
|
],
|
||||||
|
"query": [
|
||||||
|
{
|
||||||
|
"key": "path",
|
||||||
|
"value": "{{path}}"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"key": "expires_in_minutes",
|
||||||
|
"value": "{{expires_in_minutes}}"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"description": "Genera una URL temporal para un path ya existente en S3."
|
||||||
|
},
|
||||||
|
"response": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
@ -9,4 +9,5 @@ Route::get('/user', function (Request $request) {
|
||||||
|
|
||||||
require __DIR__.'/../app/Domains/Catalog/routes/api.php';
|
require __DIR__.'/../app/Domains/Catalog/routes/api.php';
|
||||||
require __DIR__.'/../app/Domains/Cart/routes/api.php';
|
require __DIR__.'/../app/Domains/Cart/routes/api.php';
|
||||||
|
require __DIR__.'/../app/Domains/StorageTest/routes/api.php';
|
||||||
require __DIR__.'/../app/Domains/Tenant/routes/api.php';
|
require __DIR__.'/../app/Domains/Tenant/routes/api.php';
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue