Files
invyone/CLAUDE.md
T
gbpark 1761b5d599
Build & Deploy to K8s / build-and-deploy (push) Successful in 7s
추가수정분
2026-04-25 00:41:37 +09:00

322 lines
16 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# INVYONE — Claude 작업 컨벤션
이 파일은 git 에 올라가는 **프로젝트 공용** Claude 가이드입니다. 모든 머신/팀원의 Claude Code 인스턴스가 이 컨벤션을 따라야 합니다.
(개인용 셋업은 `CLAUDE.local.md` — git 추적 제외, syncthing 동기화)
---
## 📝 분석 / 리포트 / 메모 MD 파일 저장 규칙 (★필수)
Claude 가 코드 분석, 보안 감사, 리팩토링 검토, 설계 문서, 회의록 등 **새 MD 파일을 작성**할 때는 다음 위치에 저장합니다.
### 저장 경로
```
notes/{git-user-name}/{YYYY-MM-DD}-{slug}.md
```
- `notes/` — 프로젝트 루트의 메모/리포트 모음 폴더 (이 폴더로 통일)
- `{git-user-name}``git config user.name` 으로 자동 결정 (예: `gbpark`, `park`)
- `{YYYY-MM-DD}-{slug}.md` — 날짜 prefix + 짧은 제목 slug (kebab-case)
**예시:**
```
notes/gbpark/2026-04-08-auth-security-audit.md
notes/gbpark/2026-04-12-component-v2-migration-plan.md
notes/park/2026-04-15-docker-port-conflict-resolution.md
```
### 규칙
1. **사용자 폴더가 이미 있으면 그 안에 넣는다** — 없으면 `mkdir -p notes/{git-user}` 로 생성
2. **파일명은 항상 날짜 + slug 조합** — 시간순 정렬되어 추적 용이
3. **README 나 docs/ 와는 분리**`README.md`, `docs/` 는 사용자/개발자용 공식 문서. `notes/` 는 작업 기록·분석·메모용
4. **MD 외 다른 산출물 (스크립트, JSON 등) 도 같이 둘 수 있음** — 필요하면 `notes/{git-user}/{slug}/` 식 하위 폴더 사용
5. **새 폴더/파일 작성 후엔 git add 권장** — syncthing 도 자동 동기화 (`notes/``.stignore-shared` 에 없음)
### 어디에 안 넣는가
- `_local/`, `_backup/`, `_pipeline/` — syncthing ignore. 머신 로컬용
- `docs/` — 공식 개발 문서. 작업 기록 아님
- 프로젝트 루트 직접 (`./STATUS.md`, `./PLAN.MD` 등) — 이미 기존에 있는 것 외에 새로 만들지 말 것
---
## 컨벤션이 적용되는 시나리오
| 사용자 요청 | 저장 위치 |
|---|---|
| "이 코드 분석해서 md 로 정리해줘" | `notes/{git-user}/{date}-{topic}.md` |
| "보안 감사 리포트 만들어줘" | `notes/{git-user}/{date}-security-audit.md` |
| "리팩토링 플랜 md 로 뽑아줘" | `notes/{git-user}/{date}-refactor-plan.md` |
| "회의 노트 정리해줘" | `notes/{git-user}/{date}-meeting-notes.md` |
| "마이그레이션 가이드 작성" | `notes/{git-user}/{date}-migration-guide.md` |
---
## Claude 사용 시 추가 주의사항
- **이 컨벤션은 사용자 명시 요청 없이도 자동 적용** — 사용자가 "md 만들어줘" 라고만 해도 위 경로에 저장
- **현재 git user 확인이 필요하면** `git config user.name` 실행
- **사용자 폴더가 처음이면** 만들면서 `.gitkeep` 정도만 두지 말고 바로 첫 노트 작성
---
## 🎨 공통 디자인 시스템 / CSS 참조 규칙 (★★★ 무조건 적용)
UI 작업(컴포넌트 작성, HTML 목업, 새 페이지/화면, 디자인 리빌딩, 스타일 수정 등)을 할 때는 **반드시** 아래 공통 CSS 파일들을 먼저 읽고 그 안의 토큰/클래스 컨벤션을 100% 따라야 합니다. 절대 새 색상/간격/라운드/그림자 값을 즉흥으로 만들지 말 것.
### "v5" 의 정체 (★ 헷갈리지 말 것)
**INVION v5 = 디자인 시안 5번째 = 최종 채택본. 현재 컨셉은 "Solid + Glow" (2026-04-21 개정)**
- 디자이너가 v1~v5 까지 5번 시안을 만들고 그 중 **v5 가 확정**되어 React 로 포팅됨
- 시안 원본 HTML (참고용, 여기엔 아직 glassmorphism 이 남아있음):
- `frontend/invion-layout-v5.html` (973줄, 풀 레이아웃 셸)
- `frontend/invion-preview-v5.html` (1049줄, 미리보기/모션 데모)
- 폐기된 시안: `frontend/invion-preview-v1~v4.html` (참고만, 적용 금지)
- **현재 적용 컨셉 (2026-04-21 개정)**:
- **로그인 페이지**: 우주(별/성운/별똥별/입자) 배경 + 글래스 카드 **유지**
- **메인 화면 이후 전부**: **반투명/blur/cosmic 배경 폐기**. 불투명 솔리드 카드 + primary-color 글로우 + 보라(`#6c5ce7`)/시안(`#00cec9`)/핑크(`#fd79a8`) 액센트
- v5 토큰이 옮겨진 곳: `frontend/styles/v5-layout.css`, `frontend/app/(auth)/login/login.css`
⚠️ **POP 디자이너의 "v5 그리드 시스템"** (`PopRenderer.tsx`, `pop-layout.ts` 등) 은 **별개 의미** — POP 화면 데이터 포맷의 5번째 버전. UI 디자인 v5 와 무관. 혼동 금지.
### 항상 먼저 읽어야 하는 파일
| 파일 | 역할 |
|---|---|
| `frontend/invion-layout-v5.html` | **v5 디자인 원본 (시안)** — 모든 v5 토큰/클래스의 진실의 원천. 포팅된 css 와 다르면 이게 정답 |
| `frontend/styles/v5-layout.css` | **INVION v5 React 포팅 메인**`--v5-*` CSS 변수, 헤더/사이드바/탭/모달 등 모든 v5- 컴포넌트 클래스 정의. UI 작업 전 무조건 먼저 읽기 |
| `frontend/app/globals.css` | shadcn/Tailwind 토큰 (`--background`, `--primary`, `--foreground` 등 HSL), 다크모드 변수, 전역 reset |
| `frontend/app/(auth)/login/login.css` | **로그인 전용** 코스믹 배경(별/성운/입자) + 글래스 카드. 이 컨셉은 **로그인에만** 적용 — 메인 화면에 옮기지 말 것 |
| `frontend/components/layout/AppLayout.tsx` | v5 클래스가 실제로 어떻게 조립되는지 — 헤더/사이드바/탭/플라이아웃 사용 예 |
### 필수 준수 사항
1. **디자인 토큰은 무조건 변수 사용**`--v5-primary`, `--v5-cyan`, `--v5-surface-solid`, `--v5-glow-sm/md/lg`, `hsl(var(--primary))` 등. 즉흥 hex/rgb 금지
2. **클래스명은 v5- 접두사 컨벤션 따르기** — 새 컴포넌트도 `.v5-card`, `.v5-btn`, `.v5-bdg` 처럼 같은 네이밍. shadcn 컴포넌트 사용 시 그대로 사용
3. **반투명/블러 금지 (★2026-04-21 신규)** — 메인 화면 이후 전 영역에서 `backdrop-filter: blur(...)`, `var(--v5-glass)`, `var(--v5-glass-strong)` 사용 **금지**. 카드/모달/사이드바/헤더 배경은 `var(--v5-surface-solid)` (라이트 `#ffffff` / 다크 `#11102a`) 를 쓰고, 테두리는 `border-border` 또는 `var(--v5-border)`. 예외: `frontend/app/(auth)/login/``frontend/styles/builder-ide.css` 는 별도 스코프라 기존 유지
4. **글로우는 유지** — 그림자는 검은 drop-shadow 대신 `var(--v5-glow-sm/md/lg)` (primary-color glow) 사용. 모달/강조 카드에 liberal 하게 사용 가능
5. **다크/라이트 모드는 둘 다 동작**`.dark` 변형 잊지 말 것. 다크에서 `--v5-surface-solid``#11102a`, 라이트는 `#ffffff`. 별/입자/별똥별/성운은 **로그인에만** 존재, 메인은 평범한 단색 배경
6. **컴팩트 폰트 사이즈 유지** — v5 는 0.55~0.85rem 의 컴팩트 UI. 새로 만들 때도 같은 스케일 따를 것
7. **새 UI 패턴은 v5-layout.css 에 합치는 것을 기본 방향으로** — 일회성 inline `<style>` 보다 공통화 우선
### 작업 순서 (UI 작업 시 반드시)
```
1) frontend/styles/v5-layout.css 읽기
2) 필요하면 globals.css, login.css 도 함께 읽기
3) 기존 v5- 클래스 중 재사용 가능한 것 찾기
4) 모자라는 부분만 같은 토큰/네이밍으로 추가
5) 작업 결과를 사용자에게 보여줄 때 "어떤 v5 토큰/클래스를 따랐는지" 명시
```
### 예외
- 단발성 디버그/실험 페이지(`debug-*`, `test-*`)는 임시 스타일 허용
- `notes/` 안의 1회성 HTML 목업은 v5 토큰을 inline 으로 가져와 standalone 이어도 됨 (단 토큰 값은 반드시 v5-layout.css 와 동일해야 함)
이 규칙은 사용자가 명시 요청하지 않아도 모든 UI 작업에 자동 적용됩니다.
---
## 🔧 백엔드 코딩 규칙 — 스타일 (★★★ 절대 규칙)
### 아키텍처: 3레이어 (Mapper Interface 금지)
```
Controller (@RestController)
Service (extends BaseService) — sqlSession 직접 사용
XML (resources/mapper/[module].xml) — 소문자 파일명
```
- **@Mapper 인터페이스 생성 금지** — `sqlSession.selectList("namespace.queryId", params)` 직접 호출
- Service는 반드시 `BaseService` 상속, `@Slf4j`, `@Autowired CommonService`
### 데이터: Map<String, Object> — DTO/엔티티 클래스 금지
로우코드 ERP: 테이블/컬럼이 런타임에 결정됨. DTO 클래스 사전 생성 불가.
- 모든 파라미터: `Map<String, Object>`
- 모든 응답: `Map<String, Object>` 또는 `List<Map<String, Object>>`
- `ApiResponse`만 유일한 DTO
### 네이밍 컨벤션 (절대 규칙)
| 위치 | 컨벤션 | 예시 |
|---|---|---|
| **Java 코드** | camelCase | `getOrderList()`, `String companyCode` |
| **Map 키 (params.put, row.get)** | snake_case | `params.put("company_code", ...)`, `row.get("table_name")` |
| **#{파라미터}** | snake_case | `#{company_code}`, `#{table_name}` |
| **SQL (키워드/테이블/컬럼)** | UPPER_SNAKE | `SELECT COMPANY_CODE FROM TEMPLATES` |
| **SELECT 쉼표** | 앞에 | `, COLUMN_NAME` |
| **XML 파일명** | 소문자, Mapper 안 붙임 | `meta.xml`, `template.xml` |
| **XML namespace** | 파일명과 동일 | `namespace="meta"` |
| **OGNL test** | 바깥 작은따옴표 | `test='company_code != "*"'` |
### 메서드명 패턴
| 조작 | 패턴 | 예시 |
|---|---|---|
| 목록 | `get[Module]List` | `getTemplateList` |
| 카운트 | `get[Module]ListCnt` | `getTemplateListCnt` |
| 단건 | `get[Module]Info` | `getTemplateInfo` |
| 등록 | `insert[Module]` | `insertTemplate` |
| 수정 | `update[Module]` | `updateTemplate` |
| 삭제 | `delete[Module]` | `deleteTemplate` |
**List API는 반드시 Count 쿼리 동반** (`getXxxList` + `getXxxListCnt` = 한 세트)
### common 레이어 필수
```xml
<include refid="common.companyCodeFilter"/>
<include refid="common.dynamicOrderBy"/>
<include refid="common.pagination"/>
```
---
## 🌐 프론트엔드 데이터 타입 규칙 (★★★ 절대 규칙)
### Record<string, any> — 별도 인터페이스 정의 금지
백엔드가 `Map<String, Object>`이므로 프론트도 `Record<string, any>`.
```typescript
// ❌ 금지 — 불필요한 인터페이스 정의
interface TableInfo {
table_name: string;
column_count: number;
}
// ✅ 올바름
const tables: Record<string, any>[] = await getTableList();
```
### 유일한 예외: invyone-component.ts 규격 타입
`frontend/types/invyone-component.ts`에 확정된 타입만 예외:
- `FieldConfig`, `FieldType`, `FieldRef`
- `Component`, `ComponentType`, `Position`
- `DataPort`, `Connection`
- `Template`, `ViewConfig`
-`ComponentTypeConfig` (TableConfig, FormConfig 등)
이것들은 시스템의 핵심 계약이므로 타입 유지. **그 외 전부 `Record<string, any>`.**
---
## 📐 INVYONE 로우코드 핵심 구조 (★ 모든 세션이 알아야 할 것)
### VEX → INVYONE 관계
INVYONE은 이미 운영 중인 로우코드 플랫폼 VEX(Node.js)의 2세대 리뉴얼(Java/Spring).
**핵심 원칙: 더 단순한 구조로 재설계하되, VEX 운영 기능은 전부 계승. 단순화 ≠ 기능 삭제.**
### FieldConfig 단일 규격
VEX의 ColumnConfig(354줄) + FilterConfig + FormField → **FieldConfig 하나(~30줄)로 통합**.
테이블/폼/검색 전부 같은 FieldConfig를 공유하며, 렌더러가 type을 보고 각자 다르게 렌더.
### 대시보드 = 메뉴
사이드바 메뉴 항목 = 대시보드. 별도 대시보드 UI가 아님.
`대시보드 생성("수주관리") → 사이드바 메뉴에 자동 등록 → 템플릿 카드 배치 → 화면 완성`
### 구현 순서
1. DB 메타 읽기 → FieldConfig 변환
2. 규격 기반 컴포넌트 (FcTable/FcForm/FcSearch)
3. 개발자 빌더 (수동 템플릿 구성)
4. 대시보드(=메뉴) 시스템
5. 제어 모드 (비즈니스 룰)
6. 자동생성/프리셋 (맨 마지막)
### 설계 문서 위치
| 문서 | 위치 |
|---|---|
| 컴포넌트 규격 v1.0 | `notes/gbpark/2026-04-08-invyone-component-spec.md` |
| 아키텍처 결정 | `notes/gbpark/2026-04-09-invyone-architecture.md` |
| 로우코드 플랫폼 SPEC | `notes/gbpark/2026-04-08-lowcode-platform-spec.md` |
| Phase 1~5 구현 설계 | `notes/gbpark/2026-04-10-phase{1~5}-*.md` |
| mockup (시각적 진실의 원천) | `notes/gbpark/2026-04-08-invyone-mockup/` |
| FieldConfig TS 타입 | `frontend/types/invyone-component.ts` |
---
## 🏢 멀티테넌시 · 서브도메인 라우팅 (★★★ 아키텍처 핵심)
INVYONE 은 **DB-per-tenant** 구조. 회사 하나 = 전용 PostgreSQL DB 하나 = 전용 서브도메인 하나.
`qnc.invyone.com` 접속 → `SubdomainResolverFilter` 가 Host 헤더 파싱 → `TenantRoutingDataSource``qnc_invyone` DB 로 자동 라우팅.
**전체 설명·플로우·배포 가이드는 반드시 읽을 것:**
- **`docs/MULTI_TENANCY_ARCHITECTURE.md`** ← 이거 하나만 읽으면 끝 (환경별 도메인 전략, CORS 패턴, 배포 체크리스트 포함)
### 절대 건드리면 안 되는 3가지
1. **테넌트 도메인에서 `NEXT_PUBLIC_API_URL=/api` 같은 Next rewrite 쓰지 말 것.**
Rewrite 가 Host 헤더를 변조해 `*.invyone.com` 서브도메인 파싱이 실패함.
`frontend/lib/api/client.ts` 의 "`.invyone.com` 이면 직접 `:8083/api`" 분기가 NEXT_PUBLIC_API_URL 체크보다 **앞에** 와야 함.
2. **CORS 는 `setAllowedOriginPatterns`** (`setAllowedOrigins` 아님). 와일드카드 매칭 필요.
**YAML 의 `[*]` 는 반드시 따옴표로 감쌀 것** (sequence 로 해석됨).
`CORS_ALLOWED_ORIGINS``.env` 에 있고 `.gitignore` 대상 → **서버마다 수동 세팅**.
3. **회사별 Hikari 풀은 반드시 `minIdle=0`.**
회사 N개 × minIdle=2 하면 Postgres `max_connections` 금방 초과.
`TenantDataSourceFactory.createTenant(...)` 만 사용할 것.
### 회사 생성 = API 호출 한 번
```
POST /api/admin/provisioning/companies
→ 6단계 자동 실행 (DB 생성 → pg_dump 스키마 복제 → 템플릿 데이터 → 관리자 → 메타 등록)
→ 13~15초 내 완료, 바로 서브도메인 접속 가능
```
필수 필드 4개만: `company_code`, `company_name`, `subdomain`, `db_prefix`.
나머지(사업자번호·대표자·이메일 등) 전부 optional.
### 환경별 도메인 — 요약
| 환경 | 접속 | 추가 설정 |
|---|---|---|
| **운영** | 실 `*.invyone.com` (+ 메인 `solution.invyone.com`) | **세팅 완료 (2026-04-24)** — Porkbun 와일드카드 DNS + Traefik v2.11 + Let's Encrypt DNS-01 와일드카드 TLS |
| **로컬 (직접 `docker up`)** | `*.localhost` 자동 | 0 (RFC 6761, Chrome/Firefox 기본 지원) |
| **원격 공유 개발서버** | `nip.io` or `hosts` 편집 | 경우 따라 |
### 새 환경 붙일 때 체크리스트
- [ ] `.env``CORS_ALLOWED_ORIGINS` 에 해당 도메인 패턴 포함 (`http://*.invyone.com:[*]` 등)
- [ ] DB 마이그레이션 `RUN_079/080/081.md` 운영 DB 에서 1회 실행
- [ ] `docker compose build --no-cache backend-spring` (`postgresql16-client` 포함)
- [ ] 프로덕션은 `tenant.provisioning.require-super-admin=true`
### 관련 코드 맵
```
backend-spring/src/main/java/com/erp/
├── tenant/ # 라우팅 (Filter / Holder / CompanyResolver / RoutingDataSource / Factory / DataSourceConfig / TenantController)
└── provisioning/ # 회사 생성 (Service / Registry / DatabaseCreator / SchemaCopier / DataCopier / AdminAccountCreator / Controller / StatsService)
backend-spring/src/main/resources/mapper/
├── tenant.xml # resolveDbNameBySubdomain
└── provisioning.xml # exists / insertCompanyWithTenant / listCompaniesForUi / updateDbStatus
frontend/
├── lib/api/provisioning.ts
├── lib/tenant/subdomain.ts # 호스트 파싱 + 예약어 제외
├── components/TenantGuard.tsx # 미등록 서브도메인 차단 (layout wrap)
├── components/admin/provisioning/ # 메인 화면 (accordion / stats strip / status dot / sparkline)
│ └── wizard/ # 4-step 마법사 (force_password_change 옵션 포함)
├── app/tenant-not-found/page.tsx # v5 solid+glow 에러 페이지
└── app/(main)/admin/sysMng/subdomainList/ # 회사 프로비저닝 페이지 엔트리
db/migrations/RUN_079/080/081_MIGRATION.md
notes/gbpark/2026-04-24-traefik-wildcard/ # 운영 Traefik 설정 산출물 (2026-04-24)
```