diff --git a/app/Http/Controllers/Api/Admin/DocumentTemplateApiController.php b/app/Http/Controllers/Api/Admin/DocumentTemplateApiController.php index 7b8eff1e..2042710a 100644 --- a/app/Http/Controllers/Api/Admin/DocumentTemplateApiController.php +++ b/app/Http/Controllers/Api/Admin/DocumentTemplateApiController.php @@ -927,4 +927,39 @@ private function checkLinkedItemDuplicates(?string $category, array $newItemIds, 'errors' => ['linked_items' => $messages], ], 422); } + + /** + * R2 image_path → presigned URL 프록시 (미리보기용) + */ + public function presignedUrlByPath(Request $request): JsonResponse + { + $path = $request->input('path'); + if (empty($path)) { + return response()->json(['url' => null], 400); + } + + $baseUrl = config('services.api.base_url', 'https://api.sam.kr'); + $internalUrl = config('services.api.internal_url'); + $apiKey = config('services.api.key'); + + $headers = [ + 'X-API-KEY' => $apiKey, + 'X-TENANT-ID' => session('selected_tenant_id', 1), + ]; + + if ($internalUrl) { + $headers['Host'] = parse_url($baseUrl, PHP_URL_HOST) ?: 'api.sam.kr'; + $baseUrl = $internalUrl; + } + + $response = Http::baseUrl($baseUrl) + ->withoutVerifying() + ->withHeaders($headers) + ->timeout(10) + ->post('/api/v1/files/presigned-url-by-path', ['path' => $path]); + + return response()->json([ + 'url' => $response->successful() ? $response->json('data.url') : null, + ]); + } } diff --git a/resources/views/document-templates/partials/preview-modal.blade.php b/resources/views/document-templates/partials/preview-modal.blade.php index b2727b00..592f43e6 100644 --- a/resources/views/document-templates/partials/preview-modal.blade.php +++ b/resources/views/document-templates/partials/preview-modal.blade.php @@ -35,27 +35,22 @@ function closePreviewModal() { if (e.key === 'Escape') closePreviewModal(); }); - // 이미지 URL 헬퍼 (file_id 기반 R2 프록시 + image_path R2 presigned URL) + // 이미지 URL 헬퍼 (file_id 기반 R2 프록시 + image_path presigned URL) function _previewImageUrl(section) { if (section.image_url) return section.image_url; if (section.file_id) return '/files/' + section.file_id + '/view'; if (!section.image_path) return ''; if (section.image_path.startsWith('http')) return section.image_path; - // R2 상대경로 — presigned URL 요청 (동기) + // R2 상대경로 — MNG API 경유 presigned URL 요청 (동기) try { const xhr = new XMLHttpRequest(); - xhr.open('POST', '{{ config("services.api.internal_url") ?: config("services.api.base_url") }}/api/v1/files/presigned-url-by-path', false); + xhr.open('POST', '/api/admin/document-templates/presigned-url-by-path', false); xhr.setRequestHeader('Content-Type', 'application/json'); - xhr.setRequestHeader('X-API-KEY', '{{ config("services.api.key") }}'); - xhr.setRequestHeader('X-TENANT-ID', '{{ session("selected_tenant_id", 1) }}'); - @if(config('services.api.internal_url')) - xhr.setRequestHeader('Host', '{{ parse_url(config("services.api.base_url"), PHP_URL_HOST) }}'); - @endif + xhr.setRequestHeader('X-CSRF-TOKEN', document.querySelector('meta[name="csrf-token"]')?.content || ''); xhr.send(JSON.stringify({ path: section.image_path })); if (xhr.status === 200) { const result = JSON.parse(xhr.responseText); - const url = result.data?.url; - if (url) { section.image_url = url; return url; } + if (result.url) { section.image_url = result.url; return result.url; } } } catch (e) { console.warn('Preview image URL fetch failed:', e); } return ''; diff --git a/routes/api.php b/routes/api.php index cfa83364..9c208df4 100644 --- a/routes/api.php +++ b/routes/api.php @@ -894,6 +894,7 @@ Route::post('/{id}/toggle-active', [DocumentTemplateApiController::class, 'toggleActive'])->name('toggle-active'); Route::post('/{id}/duplicate', [DocumentTemplateApiController::class, 'duplicate'])->name('duplicate'); Route::post('/upload-image', [DocumentTemplateApiController::class, 'uploadImage'])->name('upload-image'); + Route::post('/presigned-url-by-path', [DocumentTemplateApiController::class, 'presignedUrlByPath'])->name('presigned-url-by-path'); }); /*