feat: R2 presigned URL 엔드포인트 추가

- FileStorageController에 presignedUrl() 메서드 추가 (30분 유효)
- GET /api/v1/files/{id}/presigned-url 라우트 추가
- 파일 프록시 스트리밍 대신 R2 직접 접근 지원

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-03-20 00:36:48 +09:00
parent ce4a61e0ce
commit 844a0458ad
2 changed files with 24 additions and 0 deletions

View File

@@ -9,6 +9,7 @@
use App\Http\Requests\Api\V1\ShareLinkRequest;
use App\Services\FileStorageService;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Storage;
class FileStorageController extends Controller
{
@@ -122,6 +123,28 @@ public function view(int $id, Request $request)
return $file->download(inline: true);
}
/**
* R2 Presigned URL 발급 (30분 유효)
*/
public function presignedUrl(int $id, Request $request)
{
$this->ensureContext($request);
$service = new FileStorageService;
$file = $service->getFile($id);
if (! $file->file_path || ! Storage::disk('r2')->exists($file->file_path)) {
abort(404, 'File not found in storage');
}
$url = Storage::disk('r2')->temporaryUrl(
$file->file_path,
now()->addMinutes(30)
);
return ApiResponse::handle(fn () => ['url' => $url]);
}
/**
* Soft delete file
*/

View File

@@ -22,6 +22,7 @@
Route::get('/{id}', [FileStorageController::class, 'show'])->name('v1.files.show'); // 파일 상세
Route::get('/{id}/download', [FileStorageController::class, 'download'])->name('v1.files.download'); // 파일 다운로드
Route::get('/{id}/view', [FileStorageController::class, 'view'])->name('v1.files.view'); // 파일 인라인 보기 (이미지/PDF)
Route::get('/{id}/presigned-url', [FileStorageController::class, 'presignedUrl'])->name('v1.files.presigned-url'); // R2 Presigned URL 발급
Route::delete('/{id}', [FileStorageController::class, 'destroy'])->name('v1.files.destroy'); // 파일 삭제 (soft)
Route::post('/{id}/restore', [FileStorageController::class, 'restore'])->name('v1.files.restore'); // 파일 복구
Route::delete('/{id}/permanent', [FileStorageController::class, 'permanentDelete'])->name('v1.files.permanent'); // 파일 영구 삭제