hjjeong #3
Reference in New Issue
Block a user
Delete Branch "hjjeong"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
SubdomainResolverFilter.extractSubdomain() 가 2파트 {sub}.localhost 호스트도 첫 파트로 파싱 (RFC 6761). 운영 3파트 경로 무변경. 단위 테스트 8건 추가. frontend/lib/api/client.ts 에 *.localhost (bare 제외) 직접 호출 분기 1-b 추가. 8081 로 직결해 Host 헤더 보존. subdomain.ts 도 동일 규칙 적용. application.yml CORS 디폴트에 http://*.localhost:[*] 패턴 추가. docs/MULTI_TENANCY_ARCHITECTURE.md §4.2 (실행 모드 A/B) + §6 (1-b 분기) 갱신. .gitignore 에 .envrc/.direnv 추가, .java-version=21 명시 (jenv 호환). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>CLAUDE.md "notes/{git-user}/{date}-{slug}.md" 규약대로 정리. git config user.name=hjjeong 인데 gbpark/ 에 쌓이고 있던 3개 분리. - 2026-04-27-cross-tenant-admin-aggregation.md (설계, 내용 갱신: Phase A/B/C 체크리스트, §11 검증 시나리오 실 결과 병기, §14 다음 단계) - 2026-04-28-cross-tenant-execution-log.md (실행 로그, 내용 갱신: 커밋·푸시 상태, §4.4 *.localhost 같은 배치 묶음 기재, §5 커밋 분배 의도, §6 권장 단계 ✅/🟡/⏳/❌ 표시) - 2026-04-28-localhost-tenant-routing-handoff.md (순수 이동) cross-tenant 27.md 의 같은 폴더 참조였던 2026-04-24 두 문서는 gbpark/ 에 그대로 남아 ../gbpark/ 로 경로 보정. 외부 참조(../../docs, ../../backend-spring 등) 는 깊이 동일해 무수정. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>설계서 §11.1 (행복 경로) 의 첫 다회사 실증. SUPER_ADMIN 토큰으로 /_active-companies + 4개 fan-out 엔드포인트 직접 curl. 검증 결과 (TEST01 + TEST02 둘 다 active): - /users total=10 q=2 failed=0 by={'*':8, 'TEST01':1, 'TEST02':1} - /roles total=1 q=2 failed=0 by={'TEST01':1} - /batches total=14 q=2 failed=0 by={'TEST01':7, 'TEST02':7} ← 균등 머지 - /lang-keys total=0 q=2 failed=0 by={} ← 회사 DB 시드 없음 발견: - /users 의 '*' 8행은 버그 아님 — listUsers 만 includeMeta=true 호출, 메타 DB SUPER_ADMIN 들을 company_code='*' 로 prepend (의도된 설계). - runFanOut 의 마지막 boolean 은 wrapSearchWithPercent 일 뿐 includeMeta 아님 (시그니처 분리). roles/batches/lang-keys 는 모두 includeMeta=false. - /lang-keys 0건은 회사 DB MULTI_LANG_KEY_MASTER 가 비어있는 것 (failed=0). 이전 §3 의 "TEST01: 646건" 은 시점/컨텍스트 다른 측정으로 추정. 문서 갱신: - 설계 27.md §11: 11.1 ✅, 11.3 fan-out 검증 추가 인용 - 실행 28.md §6 #3 → ✅, §8 활성 회사 TEST02 추가 - 실행 28.md §9 신설 — 결과 표 + 해석 + 검증된 항목 + 미검증 + 재현용 명령 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>테넌트 DB 만 만져도 됨 — 메타 DB 무수정. TEST02 의 USER_INFO 를 임시 RENAME 해서 SELECT 실패 유도 → fan-out 호출 → 즉시 롤백. 메타 DB·다른 테이블 일체 영향 없음. 결과: - RENAME 후 /users → HTTP 200, header X-CrossTenant-Failed: TEST02 total=9 q=2 failed=1 by={'*':8, 'TEST01':1} (TEST02 0) - 롤백 후 /users → total=10 failed=0 by 원복 검증된 항목: - fail-open: 한 회사 실패해도 전체 응답 200 - 회사 격리: TEST01·메타 행 영향 없음 - companies_failed: 1 + failed_company_codes: ["TEST02"] - 응답 헤더 X-CrossTenant-Failed: TEST02 문서 갱신: - 설계 27.md §11.2: ⏳ → ✅ + 실행로그 §9.4 링크 - 실행 28.md §9.4 신설 — 시뮬레이션 SQL + 결과 + 검증 항목 5개 - 실행 28.md §9.5 — 남은 시나리오 (§11.4 락 비획득 / §11.5 캐시 N/A) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>지금까지 cross-tenant 는 READ 전용. admin 도메인에서 사용자 등록하면 JWT.company_code='*' 가 그대로 박혀 메타 DB 에 INSERT 되던 버그 해결. 이제 SUPER_ADMIN 이 폼의 "회사" 드롭다운에서 TEST01/TEST02 등 선택하면 그 회사 DB 에 정확히 INSERT. 신규 백엔드 - crosstenant/CrossTenantExecutor.java — 회사 컨텍스트 임시 전환 헬퍼 (company_code → db_name → ensureTenantPool → set → run → restore) - crosstenant/CrossTenantUserController.java — /api/admin/cross-tenant/users 9개 endpoint (POST/PUT/DELETE/PATCH/with-dept/check-duplicate/단건/이력) - mapper/provisioning.xml — resolveDbNameByCompanyCode (active 회사만) 기존 단일 회사 모드 (POST /admin/users 등) 무수정 — 회사 도메인 컨텍스트에서 회귀 0. 프론트 - lib/api/user.ts — createUser/updateUser/updateUserStatus/checkDuplicateUserId/ saveUserWithDept 가 isCrossTenantMode() 면 새 endpoint + body.company_code 로 분기 - UserFormModal — checkDuplicateId 호출 시 formData.company_code 같이 전달 - useUserManagement — status toggle 시 row 의 company_code 같이 전달 검증 (curl, SUPER_ADMIN 토큰): - 토큰 없음 → 403 super_admin_required - company_code 없음 → 400 "company_code 가 비어있음" - 잘못된 company_code → 400 "등록되지 않았거나 비활성 회사" - check-duplicate: TEST01.test02_admin → not_dup, TEST02.test02_admin → dup ✓ - POST 사용자 → TEST02 USER_INFO +1, TEST01·메타 격리 ✓ - /users fan-out: by={'*':8, 'TEST01':1, 'TEST02':2}, hjtest_ct_001 in TEST02만 ✓ - DELETE → status=inactive (soft) ✓ 미구현 (Phase 1 후속): - 부서 dropdown (cross-tenant department endpoint 별도 필요) - 비밀번호 초기화 모달의 cross-tenant 분기 (UserPasswordResetModal) - Phase 2 권한관리 (별도 커밋) Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Phase 1(사용자관리) 패턴을 권한관리에 동일 적용. 권한 그룹 CRUD, 멤버 토글, 메뉴 권한 토글 모두 회사 컨텍스트 임시 전환 후 처리. 신규 백엔드 - crosstenant/CrossTenantRoleController.java /api/admin/cross-tenant/roles/** — 8개 endpoint · POST — 권한 그룹 생성 (body.company_code 필수) · PUT /{id} — 권한 그룹 수정 (body.company_code 필수) · DELETE /{id}?company_code= — 삭제 · GET /{id}/workspace?company_code= — 그룹 + 멤버 + 메뉴 통합 로드 · GET /menus/all?company_code= — 회사 메뉴 트리 (권한 설정용) · POST /{id}/members/{userId}?company_code= — 멤버 1명 추가 · DELETE /{id}/members/{userId}?company_code= — 멤버 1명 제거 · PATCH /{id}/menu-permissions/{menuObjid} — 토글 CrossTenantExecutor 재사용. 기존 RoleController 무수정 (회귀 0). 중요: @RequestAttribute("user_id") 가 토큰 없을 때 missing 에러로 500 떨어지는 문제 — required=false 로 가드까지 안전하게 도달하도록. 프론트 - lib/api/role.ts — 7개 메서드(create/update/delete/getWorkspace/ getAllMenus/addSingleMember/removeSingleMember/toggleMenuPermission)에 isCrossTenantMode() 분기 + companyCode 인자 추가 - RoleFormModal — update 시 editingRole.company_code 같이 전달 - RoleDeleteModal — delete 시 role.company_code 같이 전달 - rolesList/page.tsx — loadWorkspace / addSingleMember / removeSingleMember / toggleMenuPermission 호출 시 selectedRole.company_code 전달 검증 (curl, SUPER_ADMIN 토큰): - 토큰 없음 → 403 super_admin_required - POST 권한 그룹 (TEST02) → 201, /roles fan-out 에 by={TEST01:1, TEST02:1} - DELETE → 200, fan-out by={TEST01:1} 로 복귀 미구현 (Phase 2 후속, 별도 작업): - 일괄 멤버 추가/제거/diff (PUT/POST /members) - 메뉴 권한 일괄 설정 (PUT /menu-permissions) - 사용자별 권한 그룹 조회 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>Phase 2 검증 중 발견. SUPER_ADMIN 이 admin 도메인에서 회사 필터를 "시연용회사2" 선택해도 TEST01 의 권한 그룹이 그대로 보이던 UI 버그. 원인: roleAPI.getList 의 cross-tenant 분기는 fan-out 으로 모든 회사 그룹을 모아 응답에 company_code 박아 돌려줌. 화면이 그 결과를 그대로 렌더하면서 selectedCompany dropdown 으로 client-side 필터링을 안 했음. 수정: rolesList 의 filteredRoleGroups 가 isSuperAdmin && selectedCompany!="all" 조건일 때 company_code 일치 행만 통과시키도록 한 줄 추가. 알려진 이슈 (별개, Phase 2 후속): - GET /api/admin/cross-tenant/roles/{id}/workspace 가 500 떨어짐. 단일 모드 GET /api/roles/{id}/workspace 도 group=null 빈 결과 — 사전 존재 시드/스키마 mismatch 추정 (RoleService.getRoleWorkspace 의 5개 mapper 중 하나가 깨짐). cross-tenant 분기 자체 (runInCompany) 는 정상. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>증상: SUPER_ADMIN cross-tenant 모드에서 권한 그룹 클릭 시 workspace 500 — NumberFormatException: For input string: "TEST02" 원인: role.xml getRoleNonMemberList 의 if 가 test="company_code != null and company_code != '' and company_code != '*'" 형태로 single-quote 안에 single-quote 가 박혀, OGNL 이 '*' 를 number 로 변환 시도하다 NumberFormatException. 단일 모드 (test01.localhost) 에서는 group.company_code 가 시드 잔여 '*' 라 if 가 false 로 fall-through 해 안 터졌고, cross-tenant 에서 명시적 "TEST02" 가 들어가니 평가 시도 → 깨짐. CLAUDE.md "OGNL test: 바깥 작은따옴표" 컨벤션대로 수정: test='company_code != null and company_code != "" and company_code != "*"' 검증: TEST02 의 '관리자' 그룹 workspace 호출 → HTTP 200, members: 0, nonMembers: 2 (test02_admin, test02), menus: 0 정상 반환. 22, 334번 라인의 다른 if 문은 != '*' 가 없어 OGNL 평가가 numeric coercion 까지 안 가므로 무수정 (회귀 위험 0). Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>증상: 권한있는/없는 직원 영역의 체크박스를 클릭하면 즉시 다시 해제됨 처럼 보이고 (사실은 두 번 토글됨), 체크 상태 자체가 유지 안 됨. 전체선택 버튼 (별개 핸들러) 만 정상 동작. 원인: <li onClick={토글}><Checkbox onCheckedChange={토글} /></li> 구조에서 체크박스 클릭 → onCheckedChange 트리거 (토글 1회) → 이벤트가 li 로 버블 → li onClick 트리거 (토글 1회 더) → 합쳐서 2회 토글 = 원상복귀. 수정: Checkbox 에 onClick={(e) => e.stopPropagation()} 추가해서 li 로 버블 차단. li 빈 영역 클릭 시는 li onClick 만 동작, 체크박스 직접 클릭 시는 onCheckedChange 만 동작 — 둘 다 정확히 1회 토글. 권한있는 직원 + 권한없는 직원 두 곳 모두 수정. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>cross-tenant fan-out 결과에서 회사 A·B 의 동일 objid 가 합본에 들어와 React key 중복 경고 발생 + isSelected 가 회사 구분 못 하던 문제. - li key: role.objid → \`\${company_code}-\${objid}\` 조합으로 unique - isSelected 비교: objid + company_code 둘 다 매칭 - selectedRole 유효성 체크(useEffect)에도 company_code 매칭 추가 추가: - 메뉴 전체 트리구조에 자체 스크롤 (maxHeight: calc(100vh - 32rem)) - thead sticky top-0 + bg-muted (투명도 제거) → 스크롤 시 헤더 가려짐 해소 SUPER_ADMIN cross-tenant 정책 변경 없음 (모든 회사 합본 표시 유지), React 식별만 정확해지는 fix. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>