$settings 복호화된 설정 * @return array 마스킹된 설정 */ private function maskSensitive(string $identifier, array $settings): array { $plugin = $this->pluginManager->getPlugin($identifier); return $plugin === null ? $settings : SensitiveSettingMask::apply($settings, $plugin->getSettingsSchema()); } /** * 설정 스키마가 public_asset_disk 키를 선언한 플러그인의 응답에 * 공개 자산 디스크 카탈로그(available_public_asset_disks)를 부착합니다. * * 화면이 /api/admin/settings(core.settings.read)를 따로 조회하게 두면 이 * 화면의 권한(core.plugins.read)과 표면이 갈려, 커스텀 역할에서 카탈로그만 * 조용히 비는 비대칭이 생깁니다. 설정 응답 단일 표면에서 함께 내려 권한 축을 * 일치시킵니다 (ecommerce 모듈 설정 응답의 동명 키와 동형 계약). * 저장 시에는 스키마 기반 검증 whitelist 가 이 키를 걸러 설정에 남지 않습니다. * * @param string $identifier 플러그인 식별자 * @param array $settings 응답에 실을 설정 * @return array 카탈로그가 부착된 설정 */ private function withPublicAssetCatalog(string $identifier, array $settings): array { $plugin = $this->pluginManager->getPlugin($identifier); if ($plugin === null || ! array_key_exists('public_asset_disk', $plugin->getSettingsSchema())) { return $settings; } $settings['available_public_asset_disks'] = app(DriverRegistryService::class) ->getAvailableDrivers('public_asset'); return $settings; } /** * 플러그인 설정을 조회합니다. * * @param string $identifier 플러그인 식별자 * @return JsonResponse 설정 데이터 */ public function show(string $identifier): JsonResponse { $settings = $this->pluginSettingsService->get($identifier); if ($settings === null) { return $this->notFound('plugins.not_found'); } return $this->success( 'common.success', $this->withPublicAssetCatalog($identifier, $this->maskSensitive($identifier, $settings)) ); } /** * 플러그인 설정을 업데이트합니다. * * @param UpdatePluginSettingsRequest $request 검증된 요청 * @param string $identifier 플러그인 식별자 * @return JsonResponse 업데이트 결과 */ public function update(UpdatePluginSettingsRequest $request, string $identifier): JsonResponse { // 검증을 통과한 필드만 저장한다. 과거에는 validated() 가 비면 all() 로 폴백했으나, // 그 명분이던 "PluginManager 미등록 플러그인" 은 PluginSettingsService::save() 가 // 이미 false 로 차단하므로 도달할 수 없었고, 실제로는 설정 스키마 밖의 키가 // 그대로 설정 파일에 병합되는 경로로만 동작했다 (mass-assignment). $settings = $request->validated(); $result = $this->pluginSettingsService->save($identifier, $settings); if (! $result) { return $this->error('plugins.settings.update_failed', 500); } // 저장 응답에도 카탈로그 재부착 — 화면 폼 상태가 응답으로 갱신되므로 // 누락 시 저장 직후 선택지가 비어 보인다 (ecommerce 저장 응답과 동형) return $this->success( 'plugins.settings.updated', $this->withPublicAssetCatalog( $identifier, $this->maskSensitive($identifier, $this->pluginSettingsService->get($identifier) ?? []) ) ); } /** * 플러그인 설정 레이아웃을 조회합니다. * * 설정 페이지 UI 구성과 설정 스키마를 반환합니다. * * @param string $identifier 플러그인 식별자 * @return JsonResponse 레이아웃 데이터 */ public function layout(string $identifier): JsonResponse { $layout = $this->pluginSettingsService->getLayout($identifier); if ($layout === null) { return $this->notFound('plugins.not_found'); } return $this->success('common.success', $layout); } }