- 외부 DB id 비교를 strict === 에서 toString() 기반 string 비교로 변경 — number/string 어느 쪽으로 오든 매칭. find 실패로 toConnection=null 되면 auto-select useEffect 가 "내부 DB" 로 강제 복귀시키던 문제 해소
- 연결 변경 시 toTables/fromTables 즉시 초기화 — fetch 실패해도 직전 DB 의 테이블이 잔존하지 않도록
- 배치 파이프라인 / 외부커넥션 멀티 DB 작업 핸드오프 노트 함께 추가
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존엔 PostgreSQL 만 테스트 가능했고, V001 (SERIAL→VARCHAR) 마이그레이션 이후 회사 프로비저닝 시 시퀀스가 max(id) 보다 작은 상태로 남아있어서 새 외부 커넥션 등록 시 duplicate key 가 재발하던 문제 해결.
backend
- build.gradle: MariaDB/MySQL/MSSQL/SQLite JDBC 드라이버 4종 runtimeOnly 추가
- ExternalDbConnectionService.executeConnectionTest: PostgreSQL-only 가드 제거, dbType 별 JDBC URL/props 분기 구현 (postgresql/mysql/mariadb/mssql/sqlite). defaultPort helper 추가
- mapper/externalDbConnection.xml: INSERT/UPDATE 의 port/connection_timeout/query_timeout/max_connections 에 ::varchar 캐스팅 추가 (V001 으로 VARCHAR 가 됐는데 클라가 숫자로 보내서 character varying = bigint 비교 불가로 500 나던 것)
- DataCopier.resetSequences: VARCHAR PK + 시퀀스 의존성이 남은 컬럼도 setval 대상에 포함. MAX(col::bigint) + col ~ '^[0-9]+$' 정규식으로 type mismatch 회피하면서 숫자형 VARCHAR PK 만 안전하게 reset
frontend
- ExternalDbConnectionModal: DialogContent 를 flex 컬럼으로, 본문에 자체 스크롤 + Footer shrink-0 → 길어진 폼에서도 취소/생성 버튼이 항상 보이도록
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
1) 테이블 헤더 (표시명/설명) 편집 진입 방식 변경
- 기존: 텍스트 div 자체가 role="button" + onClick 이라 무심코 클릭 시 input 으로 전환
- 변경: 텍스트는 단순 span, 옆에 작은 Pencil 아이콘 버튼 추가. 그 버튼 클릭해야 편집 모드 진입.
- 연필 아이콘은 평소 muted-foreground/50 톤, hover 시 진해짐 (group-hover 의존 X — Tailwind variant 캐시 회피).
- 편집 모드 동작 (Enter / Esc / blur 커밋) 은 그대로.
2) ColumnGrid: 컬럼 라벨 text-sm → text-xs (14px → 12px)
- 가운데 본문 컬럼 행이 너무 커보이던 문제. 좌측 list 폰트(이전 commit) 와 비례 맞춤.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
오늘 시리즈 후속 UX 다듬기 + 회귀 fix:
1) ColumnDetailPanel: dropdown key 중복 방어
- codeInfoOptions 에 placeholder "none" + 데이터 "none" 중복 시 React 가 'two children
with the same key, none' 으로 거부 → filter 로 사전 제거.
- refTableOpts 도 referenceTableOptions/tables 어디서든 중복 들어오면 같은 증상 →
Set 기반 dedupe.
2) ColumnDetailPanel: hook 순서 위반 수정
- 기존 'if (!column) return null' 이 useMemo(refTableOpts) 앞에 있어서
column null/존재 케이스마다 hook 호출 수가 달라짐 (Rules of Hooks 위반).
overlay 패턴 도입 후 column null 케이스가 자주 들어오면서 드러남.
- early return 을 모든 hook 뒤로 이동.
3) v5-layout.css 탭바: Chrome 식 outline 스타일
- 비활성 탭도 각자 outline 보이게 (border:1px solid var(--v5-border))로 카드처럼 분리.
- 활성 탭은 border + surface-hover 배경 + 위쪽 primary 1px inset 강조선.
- 위 모서리 rounded, margin-bottom:-1px 로 탭바 하단 border 와 seamless 연결.
4) 좌측 테이블 list 폰트 사이즈 축소
- 한글명 16px → 13px, 영문명 12px → 10.5px, 행 padding 7px → 6px.
- 280px 좁은 패널에 맞는 컴팩트 비율로 v5 컨벤션 정렬.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존 UX 이슈 3가지:
- 좌측 테이블 목록에서 체크박스 첫 선택 시 삭제 버튼이 등장하면서
헤더 영역 높이가 변해 아래 리스트가 살짝 밀림 (layout shift).
- 우측 디테일 패널이 conditional render 라 컬럼 클릭 시 가운데 본문이
380px 만큼 좁아져 컬럼명/라벨이 truncate 되며 "찌그러진" 느낌.
- 닫는 방법이 X 버튼뿐이라 토글 직관성 부족.
변경:
- 좌측 헤더 영역에 min-h-9 고정 — 삭제 버튼 등장해도 높이 고정, 리스트 안 흔들림.
- 우측 디테일 패널을 overlay 로 전환: absolute right-0 z-20 + shadow-2xl.
transition-transform + translate-x-{0|full} 로 300ms ease-out slide-in/out.
pointer-events-none 으로 닫혀있을 때 클릭 차단.
- 가운데 본문 width 변동 0 — 컬럼 클릭해도 안 좁아짐.
- 컬럼 토글: 같은 컬럼 재클릭 시 디테일 패널 닫힘. X 버튼/외부 트리거도 그대로 동작.
invyone admin 다른 화면들과의 일관성보다 가운데 본문 공간 보존이 우선이라
overlay 패턴 채택. 다른 화면(screenMngList, deptMngList)은 detail 영역이
처음부터 펼쳐진 2-pane 구조라 별개.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
기존: 중복 그룹 코드 등록 시 PK violation 으로 500 + "서버 내부 오류"
→ 사용자가 왜 안 되는지 알 수 없음.
변경: insertCodeInfo 진입 시 getCodeInfoInfo 로 사전 체크. 이미 존재하면
IllegalArgumentException 으로 던져 GlobalExceptionHandler 가 자동으로
400 + "이미 존재하는 그룹 코드입니다: {코드}" 메시지로 응답.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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>
5/15 common-code 재설계가 화이트리스트를 8종으로 좁히면서 운영 DB 의
옛 input_type 값들을 매핑/정리하는 마이그레이션을 빠뜨려, 컬럼 설정
저장 batch POST 가 한 row 라도 legacy 값(category/select/textarea/
checkbox/radio/datetime/boolean)을 포함하면 400 거부.
운영 메타 DB 실측: 화이트리스트 밖 row 1,207건 (category 886,
select 149, textarea 102, checkbox 55, radio 12, datetime 2, boolean 1).
운영 데이터/UI 의미를 보존하기 위해 매핑이 아닌 화이트리스트 확장
(legacy 7종 추가)으로 회복. legacy 정리는 별도 PR 에서 점진적으로.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
운영 DB 의 SCREEN_LAYOUTS.PROPERTIES 컬럼이 character varying 인데 mapper SQL 은
PROPERTIES->>'...' 와 JSONB_SET(PROPERTIES, ...) 를 그대로 사용해
PG 가 'operator does not exist: character varying ->> unknown' 으로 거부.
이로 인해 syncScreenLayouts 가 던지는 SQLException 이 try-catch 로
무시되긴 하지만 외부 @Transactional 이 이미 aborted 상태가 되어
후속 ensureTableInLabels (insertTableLabelIfNotExists) 가
'current transaction is aborted' 로 연쇄 실패 → 컬럼 설정 저장 500.
- SL.PROPERTIES::JSONB 캐스팅 (WHERE / SET 양쪽)
- JSONB_SET 결과를 ::TEXT 로 캐스팅해 varchar 컬럼에 안전 저장
- 운영 4 DB (invyone, siflex/test01/test02 _invyone) 전수 검증:
invalid JSON row 0건 → 캐스팅 안전
mapper SQL 만 변경. DB 마이그레이션 불필요.
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>
- mapper/externalDbConnection.xml: WHERE ID = #{id} 5곳 + ID != #{exclude_id} 1곳에 ::varchar 캐스팅 추가
(EXTERNAL_DB_CONNECTIONS.ID 가 V001 마이그레이션으로 VARCHAR 인데 long 바인딩되어 character varying = bigint 비교 불가로 500 발생하던 것을 해결)
- exconList: 페이지 overflow-hidden + Tabs/TabsContent 가 flex 컨테이너, ResponsiveDataView scrollContainer 활성화로 테이블 안에서만 sticky header + 자체 스크롤
- exconList/RestApiConnectionList: text-3xl→text-lg/text-sm→text-xs/h-10→h-8 등 컴팩트 폰트로 통일 (배치관리/플로우관리와 톤 매칭)
- RestApiConnectionList: Table divClassName 으로 wrapper 자체에 스크롤 위임 + sticky TableHeader 적용
- ResponsiveDataView: compact 모드일 때 폰트/셀패딩/카드 폰트도 함께 축소, scrollContainer 모드에서 @3xl:block 이 flex 를 덮어쓰던 우선순위 충돌 해결, sticky header 알파 제거
- batchmngList: Pagination 컴포넌트 적용 (RPS batchmngList 참고, 페이지당 10/20/50/100 선택), 컨테이너를 h-full min-h-0 overflow-hidden + 리스트만 자체 스크롤로 변경
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 집계
- 백엔드: selectDepartments 에 base_date <if> 블록 추가 (start_date <= base_date AND (end_date IS NULL OR end_date >= base_date))
- 서비스/컨트롤러에 3-arg overload 와 @RequestParam("base_date") 추가
- 프론트: 사용기간 RadioGroup + 시작일/종료일 Row 의 {false &&} hide 제거
- loadDepartments 가 periodMode === "date" 일 때 baseDate 를 API 에 전달
기존 "헤딩 + 옆에 input box 2개 + 저장 버튼" 구조의 UX 문제:
- 같은 값을 헤딩과 input 양쪽에서 중복 표시
- 항상 폼처럼 보여 컬럼 그리드 시선을 뺏음
- 라벨만 바꾸려는 의도가 "전체 설정 저장" 에 묶여 흐려짐
변경: 헤딩 텍스트 자체를 클릭하면 그 자리에서 input 으로 변신
(Google Docs 문서 제목 / Notion 패턴).
- blur 또는 Enter → PUT /label 즉시 저장
- Esc → 취소
- hover 시 muted/60 배경, cursor: text, title tooltip 으로 affordance
- 설명이 비어 있으면 "+ 설명 추가" 힌트 표시
- 표시명은 비울 수 없게 가드 (toast.error)
"전체 설정 저장" → "컬럼 설정 저장" 으로 책임 분리:
- 헤더 라벨/설명: inline 즉시 저장
- 컬럼 input_type/web_type/detail_settings 등 일괄: 버튼
키보드 접근성:
- Tab 으로 헤딩에 focus → Enter/Space 로 편집 모드
- Esc 로 취소, Enter 로 커밋 (blur 트리거)
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
이전 PR (#15) 의 Java parseColumnArray 분기 추가가 실제 운영 환경에서
빈 배열을 그대로 반환 — MyBatis ↔ PostgreSQL JDBC 의 array 타입 변환이
java.sql.Array 가 아닌 다른 경로로 도착하는 듯.
방식 변경: SQL 단에서 ARRAY_AGG(...)::text 캐스트 → PostgreSQL 가
"{email,phone}" String 으로 반환. parseColumnArray 의 기존 String 분기
(중괄호 제거 + 쉼표 split) 가 자연스럽게 처리.
장점:
- JDBC 드라이버 / MyBatis 변환 동작에 의존하지 않음
- parseColumnArray 코드 단순 복원 (List/String 2분기)
- 한 줄 SQL 변경으로 PK/IDX 두 쿼리 모두 해결
검증:
- gradle compileJava BUILD SUCCESSFUL
- solution.invyone.com 에서 customer_mng PK columns / email IDX columns
비어있지 않음 확인 예정
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
테이블 타입 관리 페이지에서 PK/IDX 토글 후 UI 상태가 갱신되지 않던 버그.
1. Backend: TableManagementService.parseColumnArray
- PostgreSQL ARRAY_AGG 가 java.sql.Array (PgArray) 로 오는데
List/String 만 처리해서 항상 List.of() 반환 → columns 빈 배열
- 조치: java.sql.Array 분기 추가, arr.getArray() → Object[] → List<String>
2. Frontend: loadConstraints
- 백엔드는 result.put("primary_key", ...) 로 snake_case 반환
- 프론트가 data.primaryKey 로 camelCase 로 읽어 undefined → 항상 빈 PK
- 조치: data.primary_key 로 통일
이전 PR (#14) 의 IDX payload 수정과 합쳐, PK/IDX 토글이 API 호출/DB 적용/
UI 반영까지 한 사이클로 동작.
검증:
- gradle compileJava BUILD SUCCESSFUL
- solution.invyone.com 에서 IDX 토글 양방향 확인 예정
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
테이블 타입 관리 페이지에서 IDX 토글 / 테이블 라벨 저장이 400 에러로
조용히 실패하던 버그. 백엔드는 body.get("column_name") / get("index_type")
/ get("display_name") 등 snake_case 로 읽는데 프론트가 camelCase 로 보내고
있었음 (CLAUDE.md Map key snake_case 컨벤션 위반).
- POST /table-management/tables/:t/indexes
{ columnName, indexType, action } → { column_name, index_type, action }
- PUT /table-management/tables/:t/label
{ displayName } → { display_name }
PK 는 다이얼로그 확인 흐름, NN/UQ 는 key 가 맞아 영향 없음.
SUPER_ADMIN 으로 테스트 시 IDX 만 안 되던 증상 일치.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
bug hunt security-reviewer 발견 2건 보안 fix:
1. TableManagementController 인가 누락 (OWASP A01 Broken Access Control)
- 15개 write/DDL endpoint 가 admin role 검증 없이 JWT 만 있으면 호출 가능
- 일반 사용자가 PK 재설정/index 변경/컬럼 수정 가능했음
- 조치:
- DepartmentController 의 isAdmin/isSuperAdmin helper 패턴 복사
- SUPER_ADMIN 전용 (DDL 5건): primary-key, indexes, nullable, unique, log
- admin (COMPANY_ADMIN+) (10건): updateColumnSettings, addTableData, editTableData, deleteTableData, multi-save 등
- read 19건은 그대로 (일반 사용자 접근 유지, company_code 격리만)
2. createLogTable SQL injection (OWASP A03 Injection)
- information_schema.data_type 을 raw concat 으로 DDL 생성
- 조치:
- ALLOWED_LOG_COLUMN_TYPES Set 으로 화이트리스트 (varchar/text/integer/numeric/boolean/date/timestamp/jsonb 등 21개)
- sanitize 빈 식별자 차단 + 원본 테이블에 없는 컬럼 skip
- colDefs empty 시 IllegalArgumentException
- 알 수 없는 type 은 text 로 안전 대체
검증:
- gradle compileJava BUILD SUCCESSFUL
- mapper XML 0건 변경 (READ 경로 보호)
- 별도 미해결 보안 이슈 (k8s secrets git 노출, CORS 와일드카드 등) 는 본 PR scope 외
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
새 배치 (REST API → DB) 진입 시 매번 사용자가 "데이터베이스 커넥션 선택" 셀렉트에서
"내부 DB" 를 직접 골라야 했음. 대부분의 배치가 internal 적재라 디폴트 채움이 자연스러움.
1) TO DB 자동 선택
useEffect 로 batchType === "restapi-to-db" + connections 로드 + toConnection 비어있음
조건 만족 시 handleToConnectionChange("internal") 자동 호출. 사용자가 외부 DB 로 변경하면
toConnection != null 이 되어 더 이상 자동 동작 안 함.
2) Select controlled 화
DB 커넥션/테이블 Select 가 value prop 없는 uncontrolled 상태였음.
setToConnection/setToTable state 가 바뀌어도 Select UI 가 placeholder 그대로 →
programmatic 자동 선택이 시각적으로 반영 안 됨.
→ value prop 추가:
- DB 커넥션: toConnection.type === "internal" ? "internal" : String(toConnection.id)
- 테이블: toTable
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
배경:
- frontend build 의 가장 큰 시간 소비 = runner stage 의 COPY node_modules (12분 16초)
- 전체 21분 34초 중 57%
- next.config.mjs 의 output: "standalone" 가 prod 빌드에서 이미 활성 상태였으나, Dockerfile 의 runner stage 가 .next 통째 + node_modules 통째를 COPY 하느라 standalone 결과물 미활용
조치:
- runner stage 재작성:
- .next 전체 → .next/standalone (server.js + 실제 사용 node_modules)
- .next/static 별도 COPY (standalone 가 자동 포함 안 함)
- public 별도 COPY (standalone 가 자동 포함 안 함)
- node_modules 통째 COPY 제거 (standalone 가 알아서 포함)
- package.json COPY 제거 (server.js 직접 실행)
- CMD: npm start → node server.js
검증:
- frontend 에 dynamic require/import 0건 (정적 import 만) → standalone 의존성 추적 정확
- prisma 가 package.json 에 있으나 코드 import 0건 → 자연 제외, 추가 설정 불필요
예상 효과:
- 빌드 시간 21m 34s → 약 9분 (12분 단축, 57% 감소)
- 이미지 크기 약 1GB → 약 300MB (70% 감소)
- pull 시간 단축
- runtime memory footprint 감소
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
배치 생성 흐름 검증 중 발견된 4가지 이슈 일괄 정정.
1) BatchManagementService.previewRestApiData — camelCase 키 명시 remap
직전 커밋(b752de23)에서 convertCamelToSnake() 호출 추가했지만 그 함수의 실제 구현이
batch_configs 전용 snake→snake remap 이라 사실상 no-op. 프론트의 apiUrl 등 camelCase
가 변환되지 않아 isBlank(api_url)=true → 400.
→ previewRestApiData 진입부에 직접 remap (apiUrl/apiKey/requestBody/dataArrayPath/
paramType/paramName/paramValue/paramSource/authServiceName 9개 키).
2) batchManagement.ts.previewRestApiData — 응답 totalCount 정규화
백엔드는 total_count (snake_case) 로 응답하는데 프론트는 result.totalCount 로 읽음.
토스트가 "2개 필드, undefined개 레코드" 로 표시됨.
→ 응답 normalize: total_count ?? totalCount ?? 0.
3) batch-management-new/page.tsx — root h-full overflow-y-auto
페이지 root 가 overflow 처리가 없어 FROM/TO 카드 아래의 매핑 카드가 탭 컨테이너
밖으로 잘려 사용자가 못 봄.
→ root div 에 h-full overflow-y-auto 추가.
4) RestApiToDbMappingCard — v5 컨벤션에 맞춘 컴팩트화
다른 메뉴들과 톤 통일. CardHeader 패딩 축소, 폰트 size 일괄 다운,
행 padding p-3 → p-2, Select/Input h-9 → h-7 text-xs, 순서 원형 h-6 → h-5,
카드 내부 height 360 → 300px, 매핑 추가 버튼/삭제 버튼 컴팩트.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
PR #9 deploy 단계 실패의 후속 처리.
배경:
- 운영 cluster 의 backend-spring deployment 에 누군가 직접 sslmode=disable 박은 value override 가 있었음 (env[2] SPRING_DATASOURCE_URL value + valueFrom 동시 존재 → k8s reject)
- PR #9 머지 후 수동으로 cluster 의 ConfigMap 에 sslmode=disable patch + deployment 재생성 + 새 image rollout 완료
- 다만 git 의 k8s/configmap.yaml 은 sslmode 없는 상태 → 다음 머지 시 workflow 가 ConfigMap 을 git 값으로 덮어쓰면 sslmode 다시 빠짐 → backend pod 가 SSL 시도 후 DB 연결 실패 가능
조치:
- k8s/configmap.yaml line 9 의 SPRING_DATASOURCE_URL 에 ?sslmode=disable 영구 추가
- 운영 postgres 가 SSL 강제 안 하는 환경이라 disable 가 안전
- cluster 와 git 의 state 일치
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
main 의 최근 8연속 build 실패 (run 113~120) 원인이 docker build 단계의 OOM Killed.
진단:
- wace 호스트 32GB / available 24GB 충분, 시스템 OOM 기록 없음
- act_runner systemd MemoryMax=infinity, config container.options=null (제한 없음)
- 그러나 Next.js build V8 heap spike (5-8GB+) 가 다른 30개 동시 가동 서비스 (k3s/mailu/nextcloud/mattermost 등) 와 충돌
- Committed_AS 21.6 GB / Limit 24.8 GB 한계 — page touch 시 oom-killer 가 build process kill
- 결과: 28분 빌드 후 Killed → 8연속 main 배포 실패
조치:
- builder stage 에 NODE_OPTIONS=--max-old-space-size=4096 명시 → V8 heap 4GB 로 cap
- build process 가 알아서 절제 → OOM killer 트리거 안 됨
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
데이터 불러오고 매핑하기 클릭 시 400 발생.
원인: 프론트는 camelCase (apiUrl/endpoint/method/apiKey/dataArrayPath/paramType/...)
로 body 를 보내는데 백엔드는 snake_case (api_url/endpoint/method/api_key/...) 키로 읽음.
다른 service 진입점 (updateBatchConfig / executeBatchConfig 등) 은 convertCamelToSnake
를 호출해서 자동 변환하는데 previewRestApiData 만 빠져있어 isBlank(apiUrl)=true 가
되며 IllegalArgumentException → 400.
수정: previewRestApiData 진입부에 convertCamelToSnake(body) 한 줄 추가.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>