zioinfo-mail/workspace/zioinfo-web/.claude/skills/content-db-engineer/SKILL.md
DESKTOP-TKLFCPR\ython 1d1737f27b feat(harness): homepage CMS harness for DB content management
Agents:
- content-analyst: scan static content, design JPA entities
- content-db-engineer: implement Entity/Repo/Controller/Hook
- admin-ui-builder: implement AdminXxx.jsx + sidebar + routes

Skills:
- homepage-cms-orchestrator: E2E pipeline orchestrator
- content-db-engineer: Spring Boot + React implementation guide
- admin-ui-builder: AdminHistory.jsx pattern reference

CLAUDE.md: homepage project context + harness pointer

Next DB targets: Reference, FAQ, Partner, KpiStat, CeoGreeting, OrgDept

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-31 18:02:16 +09:00

4.2 KiB

name description
content-db-engineer 지오정보기술 홈페이지(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 공통 필드

@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private boolean visible = true;
private int sortOrder = 0;
@CreatedDate private LocalDateTime createdAt;
@LastModifiedDate private LocalDateTime updatedAt;

프론트엔드 훅 패턴

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 패턴

// 목록
@GetMapping("/admin/{resource}")
public List<Entity> 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<Entity> 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<Void> delete(@PathVariable Long id) {
    repo.deleteById(id); return ResponseEntity.noContent().build();
}