templateService->findByIdentifier($templateName); if (! $template) { return $this->notFound('common.not_found'); } $layouts = $this->layoutService->getLayoutsByTemplateId($template->id); // 레이아웃 이름 → 라우트 path 매핑 — 코드 편집기가 파일 선택 시 ?route= URL // 동기화 / 위지윅에서 넘어온 ?route= 로 해당 파일 복원에 사용한다. $routePathMap = $this->templateService->getLayoutRoutePathMap($templateName); $collection = LayoutResource::collection($layouts); $collection->collection->transform( fn (LayoutResource $resource) => $resource->withRoutePathMap($routePathMap) ); return $this->success('common.success', $collection); } /** * 특정 레이아웃 상세 조회 * * @param string $templateName 템플릿 identifier * @param string $name 레이아웃 이름 * @return JsonResponse 레이아웃 상세 응답 */ public function show(string $templateName, string $name): JsonResponse { $template = $this->templateService->findByIdentifier($templateName); if (! $template) { return $this->notFound('common.not_found'); } $layout = $this->layoutService->getLayoutByName($template->id, $name); if (! $layout) { return $this->notFound('common.not_found'); } return $this->success( 'common.success', new LayoutResource($layout) ); } /** * 레이아웃 수정 * * @param UpdateLayoutContentRequest $request 레이아웃 수정 요청 * @param string $templateName 템플릿 identifier * @param string $name 레이아웃 이름 * @return JsonResponse 수정된 레이아웃 응답 */ public function update(UpdateLayoutContentRequest $request, string $templateName, string $name): JsonResponse { $template = $this->templateService->findByIdentifier($templateName); if (! $template) { return $this->notFound('common.not_found'); } try { DB::beginTransaction(); $layout = $this->layoutService->updateLayout($template->id, $name, $request->validated()); DB::commit(); return $this->success( 'common.success', new LayoutResource($layout) ); } catch (ConcurrentModificationException $e) { DB::rollBack(); return $this->error( 'exceptions.concurrent_modification', 409, [ 'error' => 'concurrent_modification', 'current_version' => $e->currentVersion, 'your_version' => $e->expectedVersion, 'resource' => $e->resource, ], ['resource' => $e->resource, 'current' => $e->currentVersion, 'expected' => $e->expectedVersion], ); } catch (\Exception $e) { DB::rollBack(); return $this->error( 'common.failed', 500, ['error' => $e->getMessage()] ); } } /** * 레이아웃의 모든 버전 목록 조회 * * @param string $templateName 템플릿 identifier * @param string $name 레이아웃 이름 * @return JsonResponse 버전 목록 응답 */ public function versions(string $templateName, string $name): JsonResponse { $template = $this->templateService->findByIdentifier($templateName); if (! $template) { return $this->notFound('common.not_found'); } $layout = $this->layoutService->getLayoutByName($template->id, $name); if (! $layout) { return $this->notFound('common.not_found'); } $versions = $this->layoutService->getLayoutVersions($template->id, $name); return $this->success( 'common.success', LayoutVersionResource::collection($versions) ); } /** * 특정 버전의 레이아웃 content 조회 * * @param string $templateName 템플릿 identifier * @param string $name 레이아웃 이름 * @param int $version 버전 번호 * @return JsonResponse 버전 content 응답 */ public function showVersion(string $templateName, string $name, int $version): JsonResponse { $template = $this->templateService->findByIdentifier($templateName); if (! $template) { return $this->notFound('common.not_found'); } $layoutVersion = $this->layoutService->getLayoutVersion($template->id, $name, $version); if (! $layoutVersion) { return $this->notFound('common.not_found'); } return $this->success( 'common.success', // 버전 비교 diff 용 — content 원본 전체(slots/extends 등 포함) 노출. (new LayoutVersionResource($layoutVersion))->withFullContent() ); } /** * 버전 복원 * * @param string $templateName 템플릿 identifier * @param string $name 레이아웃 이름 * @param int $versionId 버전 ID * @return JsonResponse 복원된 버전 응답 */ public function restoreVersion(string $templateName, string $name, int $versionId): JsonResponse { $template = $this->templateService->findByIdentifier($templateName); if (! $template) { return $this->notFound('common.not_found'); } $newVersion = $this->layoutService->restoreVersion($template->id, $name, $versionId); if (! $newVersion) { return $this->notFound('common.not_found'); } return $this->success( 'common.success', new LayoutVersionResource($newVersion) ); } /** * 레이아웃 미리보기 생성 * * 편집 중인 레이아웃 content를 임시 저장하고 미리보기 URL을 반환합니다. * * @param StoreLayoutPreviewRequest $request 미리보기 생성 요청 * @param string $templateName 템플릿 identifier * @param string $name 레이아웃 이름 * @return JsonResponse 미리보기 토큰/URL 응답 */ public function storePreview(StoreLayoutPreviewRequest $request, string $templateName, string $name): JsonResponse { $template = $this->templateService->findByIdentifier($templateName); if (! $template) { return $this->notFound('common.not_found'); } try { $preview = $this->layoutPreviewService->createPreview( $template->id, $name, $request->validated('content'), $request->user()->id ); return $this->success( 'common.success', [ 'token' => $preview->token, 'preview_url' => '/preview/'.$preview->token, 'expires_at' => $preview->expires_at->toIso8601String(), ] ); } catch (\Exception $e) { return $this->error( 'common.failed', 500, ['error' => $e->getMessage()] ); } } }