5/15 common-code 재설계가 화이트리스트를 8종으로 좁히면서 빠뜨린
운영 DB 데이터 정리. 90787d83 의 화이트리스트 확장 fix 는 회복용
보호막이었고, 본 PR 은 데이터를 표준으로 통합하는 후속 정리.
매핑:
category/select/radio/checkbox/boolean → code
textarea → text
datetime → date
영향: 메타 DB 1,207 row 갱신. 테넌트 DB 들은 비어있어 0 row.
WHERE input_type IN (...) 으로 멱등 (재실행 시 0 row).
화이트리스트 축소는 운영 안정 확인 후 별도 PR.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
테이블 타입관리의 모든 쓰기 API (UNIQUE/NOT NULL 토글, 컬럼 설정 저장,
input-type upsert) 가 500 반환. 원인은 mapper SQL 의
ON CONFLICT (TABLE_NAME, COLUMN_NAME, COMPANY_CODE) 가 매칭할 unique
제약/인덱스가 운영 DB 에 존재하지 않아 PG 가
"there is no unique or exclusion constraint matching the ON CONFLICT
specification" 으로 거부.
- StartupSchemaMigrator MIGRATIONS 에 V025 / RUN_090 (1) (2) 추가:
(1) ROW_NUMBER 로 (table, column, company) 중복 행 정리
(운영 메타 DB 실측 2 그룹 / 4 row — 동일 데이터의 NULL updated_date
옛 row 제거. 테넌트 DB 들은 중복 0건).
(2) UX_TABLE_TYPE_COLUMNS_TCC UNIQUE INDEX 생성 (IF NOT EXISTS — 멱등).
- RUN_090_MIGRATION.md 신설.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
5/15 common-code 재설계(commit 2348800e) 가 mapper SQL 6 군데 컬럼
참조를 CODE_INFO 로 바꾸면서 DB 컬럼 rename 마이그레이션을 빠뜨려,
모든 테넌트 사이트에서 테이블타입관리 > 테이블 클릭 시
GET /api/table-management/tables/{name}/columns 가 500
(column "code_info" does not exist) 을 반환.
- StartupSchemaMigrator MIGRATIONS 에 V024 항목 추가
DO 블록으로 information_schema 확인 후 rename — 멱등.
- RUN_089_MIGRATION.md 신설 (V023 IS_SOLUTION_ONLY 운영 가이드도 합본).
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- AdminController.getAdminMenus 에 Host 헤더 기반 is_management_host 추가
(기존엔 user-menus 만 필터, /admin/menus 는 미적용이라 관리자 모드 사이드바에서 노출됨)
- admin.xml selectAdminMenuList anchor + recursive 양쪽에 IS_SOLUTION_ONLY 필터 추가
- StartupSchemaMigrator: ALTER 외에 UPDATE 추가, 프로비저닝된 테넌트 DB 의
회사관리/서브도메인관리/감사로그 메뉴 행을 부팅 시 IS_SOLUTION_ONLY=TRUE 로 마킹
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
이번 PR 은 invyone 멀티테넌시 SaaS 의 "관리 plane vs 테넌트 plane" 격리를
4 영역(PR #A~D) 에서 강화하고, 별도로 진행 중이던 부서관리 후속 작업을 포함한다.
# 보안 (plane 격리)
PR #A — controller/CompanyManagementController 인증 누락 패치
/api/company-management/* 가 JWT/role/host 체크 없이 외부에서 누구나 회사 삭제
+ 디스크 통계 호출 가능했던 critical 누수 막음. SuperAdminGuard.enforce() 적용.
PR #C — cross-tenant 컨트롤러 호스트 격리 + 감사 로그
CrossTenantContext.requireManagementHost() 헬퍼 추가, 5 컨트롤러
(CrossTenantContext/Controller/UserController/RoleController/DeptController) 모두
테넌트 호스트에서 호출 시 403. CompanyAuditLogService 에 cross-tenant write 4종
(USER_CREATE/DELETE, PW_RESET, ROLE_UPDATE) audit action 추가.
SuperAdminGuard.isTenantHost 가시성 public static 으로 승격.
PR #B — 프론트 솔루션 전용 admin 페이지 가드
admin/* 페이지 전수 분류 결과 솔루션 전용 3건 식별:
subdomainList / companyList / audit-log. 각 페이지에 isManagementHost
useEffect 가드 + redirect 추가. 사이드바도 같이 숨김.
PR #D — MENU_INFO.IS_SOLUTION_ONLY 컬럼 + DB-driven 메뉴 필터
V023 마이그레이션으로 컬럼 추가 + 솔루션 메뉴 3개 마킹.
admin.xml selectUserMenuList 에 호스트 기반 필터 추가, AdminController.getUserMenus
가 Host 헤더로 is_management_host 결정. 프론트 MANAGEMENT_ONLY_MENU_URLS
하드코딩 set 폐기 (DB 가 대신함). 페이지 자체 가드는 defense in depth 로 유지.
StartupSchemaMigrator 에 V023 등록되어 모든 테넌트 DB 부팅 시 자동 적용.
# 부서관리 후속 (이전 PR #18/#19 follow-up)
DepartmentController/Service + frontend deptMngList/department.ts 의 추가 작업분.
이번 격리 작업과 무관하지만 같이 정리.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
- security: syncManagers 가 user_id 의 회사 격리·실존 검증 (cross-tenant injection 차단)
- bug: base_date 필터에 START_DATE IS NULL 조건 추가 — 옛날 데이터가 기준일 켜자마자 사라지는 문제 해결
- bug: PUT partial update — 매니저 키 없으면 sync skip (단일 필드 수정 시 매니저 보존)
- bug: 페이지 로드 시 단일 컬럼 backfill — PR #19 이전 데이터가 chip UI 에서 사라지는 문제 해결
- validation: Controller base_date YYYY-MM-DD regex (잘못된 형식 시 400)
- validation: 프론트 handleSave start_date > end_date 체크
- robustness: parseManagersJson 64KB max + log.warn (catch silent swallow 제거)
- ops: StartupSchemaMigrator 실패 테넌트 DB 명단 부팅 종료 시 log.error 집계
- V021 Flyway: ALTER TABLE BATCH_MAPPINGS ADD COLUMN MAPPING_CONFIG JSONB
- StartupSchemaMigrator: 같은 ALTER 를 idempotent 항목으로 추가 (모든 활성 테넌트 DB 부팅 시 동기화)
- batch.xml: getBatchMappingsByConfigId SELECT 에 MAPPING_CONFIG::TEXT cast,
insertBatchMapping VALUES 에 #{mapping_config,jdbcType=OTHER}::jsonb
- BatchService: ObjectMapper 주입, parseJsonField/stringifyJsonField 유틸,
syncMappings 는 INSERT 전 직렬화, attachMappings 는 SELECT 후 Map 으로 역직렬화
- RUN_087_MIGRATION.md: 운영용 마이그레이션 runbook (사전 점검/사후 검증/롤백)
conditional 매핑(when/then/default) 룰을 행 단위 저장하는 컬럼.
direct/fixed 는 NULL. Phase 2~3 에서 프런트/엔진이 이 컬럼을 읽고 쓴다.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
invyone 의 정상 마이그레이션 패턴(StartupSchemaMigrator)에 RUN_086 의 7개 idempotent
DDL/DML 등록. 다음 backend 부팅 시 메타 DB + 모든 활성 테넌트 DB 에 자동 적용됨.
- btree_gist 확장 (CREATE EXTENSION IF NOT EXISTS)
- USER_SUBSTITUTES 테이블 (PK, CHECK 2, EXCLUDE 제약, 인덱스 2)
- SYSTEM_AUDIT_LOG ALTER (PROCESSOR_ID/PROCESSOR_NAME ADD COLUMN IF NOT EXISTS)
- APPROVAL_PROXY_SETTINGS → USER_SUBSTITUTES idempotent 데이터 복사
호환성 보정:
- EXCLUDE 의 daterange lower 에 COALESCE(.., CURRENT_DATE) 사용 불가 (IMMUTABLE 만 허용)
→ daterange(START_DATE, END_DATE, '[]') 단순 형식. NULL lower = -infinity 자연 처리.
- APPROVAL_PROXY_SETTINGS 의 START_DATE/END_DATE 가 varchar → DATE cast 추가
- 원본 메타데이터(created/updated) 컬럼명 환경별 차이 회피 → 'migration_086' + NOW() 고정
실측: meta+3 tenants 모두 OK (test01/test02/siflex)
Flyway V017 은 메타 DB 만 갱신하지만 MENU_INFO 의 슈퍼관리자 메뉴
(COMPANY_CODE='*') 는 회사 프로비저닝 때 각 테넌트 DB 로 복사되어
박혀있다. StartupSchemaMigrator 에 동일 UPDATE 를 넣어 부팅 시 메타 +
모든 활성 테넌트에 자동 동기화. SEQ 만 갱신하므로 멱등.