generated from gc/template-java-maven
feat(config): 시스템 공통 설정 및 샘플 코드 관리
- SnpSystemConfig 엔티티/레포/서비스/컨트롤러 구현
- GET/PUT /api/config/{configKey} 엔드포인트
- 공통 샘플 코드 관리 admin 페이지 (SampleCodePage)
- 프론트엔드 configService 추가
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
부모
a9cdf96481
커밋
ac0f51b816
107
frontend/src/pages/admin/SampleCodePage.tsx
Normal file
107
frontend/src/pages/admin/SampleCodePage.tsx
Normal file
@ -0,0 +1,107 @@
|
|||||||
|
import { useState, useEffect } from 'react';
|
||||||
|
import { getSystemConfig, updateSystemConfig } from '../../services/configService';
|
||||||
|
|
||||||
|
const COMMON_SAMPLE_CODE_KEY = 'COMMON_SAMPLE_CODE';
|
||||||
|
|
||||||
|
const INPUT_CLS =
|
||||||
|
'w-full border border-gray-300 dark:border-gray-600 bg-white dark:bg-gray-700 text-gray-900 dark:text-gray-100 rounded-lg px-3 py-2 focus:ring-2 focus:ring-blue-500 focus:outline-none text-sm font-mono';
|
||||||
|
|
||||||
|
const SampleCodePage = () => {
|
||||||
|
const [sampleCode, setSampleCode] = useState('');
|
||||||
|
const [loading, setLoading] = useState(true);
|
||||||
|
const [saving, setSaving] = useState(false);
|
||||||
|
const [message, setMessage] = useState<{ type: 'success' | 'error'; text: string } | null>(null);
|
||||||
|
|
||||||
|
useEffect(() => {
|
||||||
|
const fetchConfig = async () => {
|
||||||
|
try {
|
||||||
|
const res = await getSystemConfig(COMMON_SAMPLE_CODE_KEY);
|
||||||
|
if (res.success && res.data?.configValue != null) {
|
||||||
|
setSampleCode(res.data.configValue);
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setMessage({ type: 'error', text: '샘플 코드를 불러오는데 실패했습니다.' });
|
||||||
|
} finally {
|
||||||
|
setLoading(false);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
fetchConfig();
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const handleSave = async () => {
|
||||||
|
setSaving(true);
|
||||||
|
setMessage(null);
|
||||||
|
try {
|
||||||
|
const res = await updateSystemConfig(COMMON_SAMPLE_CODE_KEY, sampleCode);
|
||||||
|
if (res.success) {
|
||||||
|
setMessage({ type: 'success', text: '저장되었습니다.' });
|
||||||
|
} else {
|
||||||
|
setMessage({ type: 'error', text: res.message || '저장에 실패했습니다.' });
|
||||||
|
}
|
||||||
|
} catch {
|
||||||
|
setMessage({ type: 'error', text: '저장 중 오류가 발생했습니다.' });
|
||||||
|
} finally {
|
||||||
|
setSaving(false);
|
||||||
|
setTimeout(() => setMessage(null), 3000);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
if (loading) {
|
||||||
|
return (
|
||||||
|
<div className="flex items-center justify-center py-20">
|
||||||
|
<div className="w-8 h-8 border-4 border-blue-500 border-t-transparent rounded-full animate-spin" />
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<div className="max-w-4xl mx-auto">
|
||||||
|
<div className="flex items-start justify-between mb-6">
|
||||||
|
<div>
|
||||||
|
<h1 className="text-2xl font-bold text-gray-900 dark:text-gray-100">공통 샘플 코드 관리</h1>
|
||||||
|
<p className="text-sm text-gray-500 dark:text-gray-400 mt-1">
|
||||||
|
API HUB 상세 페이지에 공통으로 표시되는 샘플 코드를 관리합니다.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
<button
|
||||||
|
onClick={handleSave}
|
||||||
|
disabled={saving}
|
||||||
|
className="px-4 py-2 rounded-lg bg-blue-600 hover:bg-blue-700 disabled:opacity-50 text-white text-sm font-medium transition-colors shrink-0"
|
||||||
|
>
|
||||||
|
{saving ? '저장 중...' : '저장'}
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{message && (
|
||||||
|
<div
|
||||||
|
className={`mb-4 p-3 rounded-lg text-sm ${
|
||||||
|
message.type === 'success'
|
||||||
|
? 'bg-green-50 dark:bg-green-900/30 text-green-700 dark:text-green-400'
|
||||||
|
: 'bg-red-50 dark:bg-red-900/30 text-red-700 dark:text-red-400'
|
||||||
|
}`}
|
||||||
|
>
|
||||||
|
{message.text}
|
||||||
|
</div>
|
||||||
|
)}
|
||||||
|
|
||||||
|
<div className="bg-white dark:bg-gray-800 rounded-lg shadow p-6">
|
||||||
|
<label className="block text-sm font-medium text-gray-700 dark:text-gray-300 mb-2">
|
||||||
|
샘플 코드
|
||||||
|
</label>
|
||||||
|
<textarea
|
||||||
|
value={sampleCode}
|
||||||
|
onChange={(e) => setSampleCode(e.target.value)}
|
||||||
|
rows={20}
|
||||||
|
placeholder="API HUB 상세 페이지에 표시할 공통 샘플 코드를 입력하세요."
|
||||||
|
className={`${INPUT_CLS} resize-y`}
|
||||||
|
/>
|
||||||
|
<p className="mt-2 text-xs text-gray-400 dark:text-gray-500">
|
||||||
|
이 코드는 모든 API 상세 페이지의 '요청 URL 생성' 섹션 하단에 표시됩니다.
|
||||||
|
</p>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default SampleCodePage;
|
||||||
12
frontend/src/services/configService.ts
Normal file
12
frontend/src/services/configService.ts
Normal file
@ -0,0 +1,12 @@
|
|||||||
|
import { get, put } from './apiClient';
|
||||||
|
import type { SystemConfigInfo } from '../types/service';
|
||||||
|
import type { ApiResponse } from '../types/api';
|
||||||
|
|
||||||
|
export const getSystemConfig = (configKey: string): Promise<ApiResponse<SystemConfigInfo>> =>
|
||||||
|
get<SystemConfigInfo>(`/config/${configKey}`);
|
||||||
|
|
||||||
|
export const updateSystemConfig = (
|
||||||
|
configKey: string,
|
||||||
|
configValue: string,
|
||||||
|
): Promise<ApiResponse<SystemConfigInfo>> =>
|
||||||
|
put<SystemConfigInfo>(`/config/${configKey}`, { configValue });
|
||||||
@ -0,0 +1,46 @@
|
|||||||
|
package com.gcsc.connection.common.controller;
|
||||||
|
|
||||||
|
import com.gcsc.connection.common.dto.ApiResponse;
|
||||||
|
import com.gcsc.connection.common.dto.SystemConfigResponse;
|
||||||
|
import com.gcsc.connection.common.dto.UpdateSystemConfigRequest;
|
||||||
|
import com.gcsc.connection.common.service.SystemConfigService;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.PutMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestBody;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 시스템 공통 설정 API
|
||||||
|
*/
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/config")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SystemConfigController {
|
||||||
|
|
||||||
|
private final SystemConfigService systemConfigService;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 설정 값 단건 조회
|
||||||
|
*/
|
||||||
|
@GetMapping("/{configKey}")
|
||||||
|
public ResponseEntity<ApiResponse<SystemConfigResponse>> getConfig(@PathVariable String configKey) {
|
||||||
|
SystemConfigResponse response = systemConfigService.getConfigValue(configKey);
|
||||||
|
return ResponseEntity.ok(ApiResponse.ok(response));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 설정 값 저장 (upsert)
|
||||||
|
*/
|
||||||
|
@PutMapping("/{configKey}")
|
||||||
|
public ResponseEntity<ApiResponse<SystemConfigResponse>> updateConfig(
|
||||||
|
@PathVariable String configKey,
|
||||||
|
@RequestBody UpdateSystemConfigRequest request
|
||||||
|
) {
|
||||||
|
SystemConfigResponse response = systemConfigService.updateConfig(configKey, request);
|
||||||
|
return ResponseEntity.ok(ApiResponse.ok(response));
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,26 @@
|
|||||||
|
package com.gcsc.connection.common.dto;
|
||||||
|
|
||||||
|
import com.gcsc.connection.common.entity.SnpSystemConfig;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
public record SystemConfigResponse(
|
||||||
|
Long configId,
|
||||||
|
String configKey,
|
||||||
|
String configValue,
|
||||||
|
String description,
|
||||||
|
LocalDateTime createdAt,
|
||||||
|
LocalDateTime updatedAt
|
||||||
|
) {
|
||||||
|
|
||||||
|
public static SystemConfigResponse from(SnpSystemConfig config) {
|
||||||
|
return new SystemConfigResponse(
|
||||||
|
config.getConfigId(),
|
||||||
|
config.getConfigKey(),
|
||||||
|
config.getConfigValue(),
|
||||||
|
config.getDescription(),
|
||||||
|
config.getCreatedAt(),
|
||||||
|
config.getUpdatedAt()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,6 @@
|
|||||||
|
package com.gcsc.connection.common.dto;
|
||||||
|
|
||||||
|
public record UpdateSystemConfigRequest(
|
||||||
|
String configValue
|
||||||
|
) {
|
||||||
|
}
|
||||||
@ -0,0 +1,44 @@
|
|||||||
|
package com.gcsc.connection.common.entity;
|
||||||
|
|
||||||
|
import jakarta.persistence.Column;
|
||||||
|
import jakarta.persistence.Entity;
|
||||||
|
import jakarta.persistence.GeneratedValue;
|
||||||
|
import jakarta.persistence.GenerationType;
|
||||||
|
import jakarta.persistence.Id;
|
||||||
|
import jakarta.persistence.Table;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.Builder;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Getter
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PROTECTED)
|
||||||
|
@Entity
|
||||||
|
@Table(name = "snp_system_config", schema = "common")
|
||||||
|
public class SnpSystemConfig extends BaseEntity {
|
||||||
|
|
||||||
|
@Id
|
||||||
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
@Column(name = "config_id")
|
||||||
|
private Long configId;
|
||||||
|
|
||||||
|
@Column(name = "config_key", unique = true, nullable = false, length = 100)
|
||||||
|
private String configKey;
|
||||||
|
|
||||||
|
@Column(name = "config_value", columnDefinition = "TEXT")
|
||||||
|
private String configValue;
|
||||||
|
|
||||||
|
@Column(name = "description", length = 500)
|
||||||
|
private String description;
|
||||||
|
|
||||||
|
@Builder
|
||||||
|
public SnpSystemConfig(String configKey, String configValue, String description) {
|
||||||
|
this.configKey = configKey;
|
||||||
|
this.configValue = configValue;
|
||||||
|
this.description = description;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(String configValue) {
|
||||||
|
this.configValue = configValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -0,0 +1,11 @@
|
|||||||
|
package com.gcsc.connection.common.repository;
|
||||||
|
|
||||||
|
import com.gcsc.connection.common.entity.SnpSystemConfig;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public interface SnpSystemConfigRepository extends JpaRepository<SnpSystemConfig, Long> {
|
||||||
|
|
||||||
|
Optional<SnpSystemConfig> findByConfigKey(String configKey);
|
||||||
|
}
|
||||||
@ -0,0 +1,48 @@
|
|||||||
|
package com.gcsc.connection.common.service;
|
||||||
|
|
||||||
|
import com.gcsc.connection.common.dto.SystemConfigResponse;
|
||||||
|
import com.gcsc.connection.common.dto.UpdateSystemConfigRequest;
|
||||||
|
import com.gcsc.connection.common.entity.SnpSystemConfig;
|
||||||
|
import com.gcsc.connection.common.repository.SnpSystemConfigRepository;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class SystemConfigService {
|
||||||
|
|
||||||
|
private final SnpSystemConfigRepository systemConfigRepository;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 설정 값 단건 조회
|
||||||
|
*/
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public SystemConfigResponse getConfigValue(String configKey) {
|
||||||
|
return systemConfigRepository.findByConfigKey(configKey)
|
||||||
|
.map(SystemConfigResponse::from)
|
||||||
|
.orElse(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 설정 값 저장 (upsert: 없으면 생성, 있으면 수정)
|
||||||
|
*/
|
||||||
|
@Transactional
|
||||||
|
public SystemConfigResponse updateConfig(String configKey, UpdateSystemConfigRequest request) {
|
||||||
|
SnpSystemConfig config = systemConfigRepository.findByConfigKey(configKey)
|
||||||
|
.map(existing -> {
|
||||||
|
existing.update(request.configValue());
|
||||||
|
return existing;
|
||||||
|
})
|
||||||
|
.orElseGet(() -> systemConfigRepository.save(
|
||||||
|
SnpSystemConfig.builder()
|
||||||
|
.configKey(configKey)
|
||||||
|
.configValue(request.configValue())
|
||||||
|
.build()
|
||||||
|
));
|
||||||
|
log.info("시스템 설정 저장: configKey={}", configKey);
|
||||||
|
return SystemConfigResponse.from(config);
|
||||||
|
}
|
||||||
|
}
|
||||||
불러오는 중...
Reference in New Issue
Block a user