포털콘솔 리팩토링 계획 (2026-10-17)
관리자 콘솔(DirectoryAdmin.tsx)과 스키마 계층의 구조 부채를 동작 변경 없이 줄이는 계획이다. 측정 근거와 우선순위는 2026-10-17 코드·테스트 실측에 기반한다.
측정 (실측)
| 지표 | 값 | 비고 |
|---|---|---|
| 규모 | 284 files · 37,089 LOC | server·client·shared |
| 최대 파일 | client/src/pages/DirectoryAdmin.tsx 4,905 → 951줄 | 이번 슬라이스들로 3,954줄 분리(약 81%) |
any/as unknown as | 96 (server·shared) | 이중 스키마 타입 캐스팅이 주 원인 |
tx: any | 5 | 트랜잭션 공통 타입 부재 |
| 스키마 정의 | MySQL 616 + PG 618 + 어댑터 70 = 1,304줄 | 같은 테이블을 두 번 정의 |
ensure-*.mjs | 9 | 마이그레이션과 병존하는 두 번째 진화 경로 |
| server 루트 | 평면 72 files | 도메인 폴더 없음 |
| 테스트 | 105 files · 574 통과 (2 skipped) | 안전망은 정상 |
원칙
- 동작을 바꾸지 않는다 — 순수 이동·추출만. 동작 변경은 별도 커밋.
- 테스트를 먼저 통과시킨다 — 리팩토링 전후 모두
pnpm verify:full. - 소스-계약 테스트를 함께 옮긴다 —
client/src/lib/directoryAdminStyles.test.ts는DirectoryAdmin.tsx를readFileSync로 읽어 문자열을 검사한다. 컴포넌트를 옮기면 그 assert도 새 파일을 읽도록 같은 커밋에서 바꿔야 한다(아래 선례 참고). - 한 번에 한 컴포넌트/한 도메인. 큰 diff를 한 커밋에 넣지 않는다.
단계
P0 — DirectoryAdmin.tsx 분해 (진행 중)
client/src/pages/DirectoryAdmin.tsx는 관리자 앱 전체를 담은 단일 파일이다. 아래 순서로 client/src/features/ 또는 client/src/components/로 하나씩 옮긴다.
| 순서 | 대상(현재 줄 범위 기준) | 이동 위치(제안) |
|---|---|---|
| ✅ 1 | PortalKeyRegistrationGuide + 위치 예시 상수/snippet | components/PortalKeyRegistrationGuide.tsx |
| ✅ 2 | PortalKeyIssueDialog (+ ScopePresetRow·scope 상수 → 공용 모듈) | features/api-keys/PortalKeyIssueDialog.tsx · features/api-keys/portalScopes.tsx |
| ✅ 3 | PortalApiKeyScopeDesk (+ formatLastUsed, 발급 팝업 호출부) | features/api-keys/PortalApiKeyScopeDesk.tsx |
| ✅ 4 | PortalAccessDesk | features/portal-access/PortalAccessDesk.tsx |
| ✅ 5 | PortalSettingsDesk + PortalSectionTabs (+ PORTAL_SECTIONS·PortalSection) | features/portal-settings/* |
| ✅ 6 | OrganizationBulkDialog · OrganizationTree | features/organization/* |
| ✅ 7 | UserDesk · UserFields · UserProfileControls (+ departmentHeadRoleMessageKeys·PERSONAL_THEME_ACCENTS) | features/users/* |
| ✅ 8 | OperationsDesk · AdminAccountsDesk | features/operations/* |
P0 완료. DirectoryAdmin.tsx 에는 라우팅·권한 게이트·탭 조합·데스크 배치만 남는다(951줄).
각 단계마다:
bash
pnpm check
npx vitest run client/src/lib/directoryAdminStyles.test.ts
pnpm verify:full선례(1·2단계에서 처리):
PortalKeyRegistrationGuide를 옮기면서directoryAdminStyles.test.ts의portal-key-registration-guide·portal-key-secret·PORTAL_CONSOLE_URL=·브라우저 코드…assert를 새 파일로 재지정했다.portalDeliverySecretName.contract·portalIntegrationTerms계약 테스트도 같은 파일을 보도록 바꿨다.PortalKeyIssueDialog를 옮기면서ScopePresetRow와 scope 상수(11개·라벨·프리셋)를features/api-keys/portalScopes.tsx로 함께 분리했다(등록 폼·scope 데스크·발급 팝업이 공유).trpc.settings.issuePortalCredential·secret: result.secret계약은 발급 팝업 파일로 재지정.PortalApiKeyScopeDesk(676줄)를 옮기면서formatLastUsed도 함께 갔다. 발급 팝업은 이제 데스크가 렌더하므로<PortalKeyIssueDialog·title="STEP 2 키 등록"·회전 문구 계약은 데스크 파일을 본다. 반대로portalApiKeys.useQuery는 페이지가 소유한다(데스크에 props로 내려준다).directoryAdminStyles.test.ts에portalApiKeyScopeDesk(정규화본 포함) 소스를 추가해 이동한 마크업 계약을 한 곳에서 검사한다.
P1 — 스키마 단일화 (별도 계획 필요, 리스크 중간~높음)
drizzle/schema.ts가 런타임에 PG 테이블을 고르면서 타입은 MySQL로 캐스팅한다 (selected.portalApiKeySettings as typeof mysql.portalApiKeySettings). 이 구조가 any/tx: any 96건의 뿌리다.
- PG를 정본으로 선언하고 MySQL은 생성/어댑터로 파생
schema.ts의 교차 캐스팅 제거, 공통DbTransaction타입 도입- 전 테이블 회귀(마이그레이션·조회·쓰기) — 배포 DB 스냅샷으로 검증
운영 DB 스키마가 걸린 작업이므로 감독 하에 진행한다. 자동 반영 금지.
P2 — 보정 스크립트 은퇴 (리스크 낮음) — 1단계 완료
scripts/ensure-*.mjs 9개는 마이그레이션 저널 누락을 응급 보정하던 장치다. 저널 계약 테스트(server/migrationsJournal.contract.test.ts)가 생겼으므로:
- ✅ 1단계(완료): 9개 스크립트에 "진단·응급 보정용" 헤더를 넣어, 신규 스키마 변경은 마이그레이션 + 저널 등록으로 추가하도록 명시했다(동작 변경 없음).
- 2단계(남음): 각 스크립트를 진단 전용(현재 상태 출력, 변경 없음)으로 축소하거나 마이그레이션 + 계약 테스트로 흡수한 뒤 삭제한다. 온프레미스(폐쇄망) 배포 절차 문서와 함께 갱신해야 하므로 배포 절차를 아는 사람과 같이 진행한다.
P3 — 서버 모듈 경계 (리스크 낮음)
server/평면 72 files →server/portal/,server/credentials/,server/directory/server/portalRoutes.ts(642줄) → 등록 함수 단위 분리 (registerPkceTokenRoute,registerPortalCryptoRoutes,registerPortalReadRoutes…)- 각 단계 후
server/portalRoutes.test.ts+pnpm check
하지 않을 것
- 전면 재작성 — 계층·테스트 자산을 버리는 이득이 없다.
- 워크플로우 맵 재구성 — 이 저장소가 아니라 ProcessCenter의 과제다.
- P1을 자동(무인)으로 배포 — DB 스키마 회귀 위험이 있다.
검증 명령
bash
pnpm check # tsc
pnpm test # vitest 전체
pnpm verify # check + build
pnpm verify:full # test + check + build