group(function () { Route::get('/dashboard', function () { return view('dashboard'); })->name('dashboard'); }); // R2 정적 이미지 프록시 (인증 불필요, 캐시 1일) Route::get('/images/{path}', function (string $path) { $r2Path = 'images/'.$path; if (! Storage::disk('r2')->exists($r2Path)) { abort(404); } $mime = Storage::disk('r2')->mimeType($r2Path); $stream = Storage::disk('r2')->readStream($r2Path); return response()->stream(function () use ($stream) { fpassthru($stream); if (is_resource($stream)) { fclose($stream); } }, 200, [ 'Content-Type' => $mime, 'Cache-Control' => 'public, max-age=86400', ]); })->where('path', '.*'); // Swagger v1 JSON (api-docs-v1.json 직접 제공) Route::get('/docs/api-docs-v1.json', function () { $path = storage_path('api-docs/api-docs-v1.json'); if (! file_exists($path)) { abort(404); } return response()->file($path, ['Content-Type' => 'application/json']); }); // R2 파일 프록시 (file_id 기반, 인증 불필요, 캐시 1일) Route::get('/files/{id}/view', function (int $id) { $file = \App\Models\Commons\File::find($id); if (! $file || ! $file->file_path) { abort(404); } if (! Storage::disk('r2')->exists($file->file_path)) { abort(404); } $mime = Storage::disk('r2')->mimeType($file->file_path); $stream = Storage::disk('r2')->readStream($file->file_path); return response()->stream(function () use ($stream) { fpassthru($stream); if (is_resource($stream)) { fclose($stream); } }, 200, [ 'Content-Type' => $mime, 'Cache-Control' => 'public, max-age=86400', ]); }); // Swagger 설정 Route::get('/docs/api-docs.json', function () { return response()->file(storage_path('api-docs/api-docs.json')); }); Route::middleware('swagger.auth')->group(function () { Route::get('/api/documentation', function () { return response()->file(public_path('swagger-ui/index.html')); }); });