--- name: content-db-engineer description: "지오정보기술 홈페이지(zioinfo-web) 정적 콘텐츠를 DB로 전환하는 구현 스킬. FAQ, 파트너사, 레퍼런스, CEO인사말, 조직도, KPI통계, 솔루션 소개 등 하드코딩 데이터를 Spring Boot JPA Entity + React API 훅으로 전환한다. 다음 상황에서 반드시 사용: (1) '홈페이지 XXX를 DB로 관리', 'FAQ DB화', '파트너사 관리자 추가' 요청; (2) 신규 Entity/Repository/Controller 구현; (3) 프론트 하드코딩 → API 연동 전환; (4) DataInitializer 초기 데이터 시딩; (5) 다시 실행, 업데이트, 수정, 보완 요청." --- # 홈페이지 콘텐츠 DB 전환 스킬 ## 기술 스택 | 레이어 | 기술 | |--------|------| | Backend | Spring Boot 3.2.5 + JPA (Hibernate 6) | | DB | H2 (dev) / 서버 내장 | | Frontend | React 18 + Vite | | 인증 | JWT Bearer (관리자) / 없음 (공개 API) | ## 표준 구현 순서 ``` 1. JPA Entity → model/ 디렉토리 2. JpaRepository → repository/ 디렉토리 3. ApiController → GET /api/{resource} (공개) 4. AdminController → CRUD /api/admin/{resource} (JWT 필요) 5. DataInitializer → initXxx() 메서드 추가 6. React 훅 → useXxx() in Company.jsx 또는 해당 페이지 7. 컴포넌트 → API 데이터 사용, 정적 배열 제거 ``` ## 파일 위치 규칙 ``` backend/src/main/java/kr/co/zioinfo/web/ ├── model/ ← Entity (CompanyHistory.java 참조) ├── repository/ ← JpaRepository ├── controller/ │ ├── ApiController.java ← 공개 GET 추가 │ └── AdminController.java ← CRUD 추가 └── config/ └── DataInitializer.java ← initXxx() 추가 frontend/src/pages/ ├── {Page}.jsx ← useXxx() 훅 + API 데이터 사용 └── admin/ └── Admin{Name}.jsx ← 관리자 CRUD 페이지 ``` ## 우선순위별 DB화 항목 ### HIGH — 즉시 구현 | 항목 | 파일 | 엔티티 | 공개 API | |------|------|--------|---------| | 구축 레퍼런스 | Business.jsx | `Reference` (tb_reference) | GET /api/references | | FAQ | Support.jsx | `Faq` (tb_faq) | GET /api/faqs | | 파트너사 | Business.jsx | `Partner` (tb_partner) | GET /api/partners | | KPI 통계 | Home.jsx | `KpiStat` (tb_kpi_stat) | GET /api/stats | ### MEDIUM — 단계적 구현 | 항목 | 파일 | 엔티티 | 공개 API | |------|------|--------|---------| | CEO 인사말 | Company.jsx | `CeoGreeting` (tb_ceo_greeting) | GET /api/ceo-greeting | | 핵심 가치 | Company.jsx | `CoreValue` (tb_core_value) | GET /api/core-values | | 조직도 | Company.jsx | `OrgDept` (tb_org_dept) | GET /api/org-depts | | 솔루션 설명 | SolutionPage.jsx | `SolutionFeature` (tb_solution_feature) | GET /api/solutions/{type}/features | ## Entity 공통 필드 ```java @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private boolean visible = true; private int sortOrder = 0; @CreatedDate private LocalDateTime createdAt; @LastModifiedDate private LocalDateTime updatedAt; ``` ## 프론트엔드 훅 패턴 ```jsx function useXxx(FALLBACK = []) { const [data, setData] = useState([]); const [loading, setLoading] = useState(true); useEffect(() => { fetch('/api/xxx') .then(r => r.json()) .then(d => setData(d.length > 0 ? d : FALLBACK)) .catch(() => setData(FALLBACK)) .finally(() => setLoading(false)); }, []); return { data, loading }; } ``` ## AdminController 패턴 ```java // 목록 @GetMapping("/admin/{resource}") public List list() { return repo.findAllByOrderBySortOrderAsc(); } // 생성 @PostMapping("/admin/{resource}") public Entity create(@RequestBody Entity body) { body.setId(null); return repo.save(body); } // 수정 @PutMapping("/admin/{resource}/{id}") public ResponseEntity update(@PathVariable Long id, @RequestBody Entity body) { return repo.findById(id).map(e -> { // 필드 복사 return ResponseEntity.ok(repo.save(e)); }).orElse(ResponseEntity.notFound().build()); } // 삭제 @DeleteMapping("/admin/{resource}/{id}") public ResponseEntity delete(@PathVariable Long id) { repo.deleteById(id); return ResponseEntity.noContent().build(); } ```