Skip to content

✨ 어드민 API 환경(stage/prod) 플로팅 스위처 추가 - #619

Merged
manNomi merged 3 commits into
mainfrom
feat/admin-api-environment-switcher
Aug 1, 2026
Merged

✨ 어드민 API 환경(stage/prod) 플로팅 스위처 추가#619
manNomi merged 3 commits into
mainfrom
feat/admin-api-environment-switcher

Conversation

@manNomi

@manNomi manNomi commented Aug 1, 2026

Copy link
Copy Markdown
Contributor

요약

이메일 기반 환경 판별 로직(#615, #616)을 제거하고, 우하단 플로팅 버튼으로 어드민이 stage/prod API 환경을 명시적으로 전환할 수 있도록 했습니다. 어드민 배포를 main 브랜치 하나로 통합하는 작업(#618)의 후속입니다.

관련 이슈: #615, #616, #618

동작 방식

  • 플로팅 버튼(FAB) 클릭 → confirm 다이얼로그로 재확인 → 세션 초기화 후 새 환경 값 저장 → /auth/login으로 전체 페이지 이동 (react-query 캐시 등 메모리 상태를 확실히 비우기 위한 하드 리다이렉트)
  • 로그인 페이지의 ensureSessionToken()이 새로 저장된 환경 기준으로 reissue를 시도합니다. 해당 환경에 유효한 refresh 쿠키가 있으면 자동 로그인되고, 없으면 로그인 폼이 표시됩니다.
  • 환경 네이밍을 devstage로 전환했습니다. 레거시로 저장돼 있던 "dev" 값은 "stage"로 해석하여 하위 호환을 유지합니다.
  • VITE_API_SERVER_URL로 로컬 오버라이드가 설정된 경우 FAB은 LOCAL 표시와 함께 비활성화됩니다(전환 불가).

행동 변화 (주의)

기존에는 로그인 이메일이 dev 계정 패턴이면 자동으로 stage API로 라우팅됐지만, 이제는 그렇지 않습니다. 로그인 전에 FAB으로 원하는 환경을 먼저 선택해야 합니다. 기본값은 PROD입니다.

리뷰 반영 (2차 커밋)

  • EnvironmentBanner.tsxEnvironmentSwitcherFab.tsx에 복붙돼 있던 environmentStyles/environmentLabels/resolveDisplayedEnvironment/DisplayedEnvironment 타입을 apps/admin/src/components/layout/environmentDisplay.ts 공용 모듈로 추출하여 드리프트 위험 제거 (렌더 결과 동일)
  • environment.test.ts에 섞여 있던 switchAdminApiEnvironment 테스트(대상: ./session)를 session.test.ts로 분리, environment.test.ts에는 getApiBaseUrlForEnvironment 테스트만 남기고 불필요해진 mock/hoisted 선언 정리

실사용 버그 수정 (3차 커밋)

STAGE로 전환 후 로그인하면 요청이 여전히 prod(https://api.solid-connection.com/admin/auth/sign-in)로 나가는 문제가 실사용 중 발견되어 수정했습니다.

재현 순서

  1. FAB으로 STAGE 전환: clearSession()saveAdminApiEnvironment("stage")/auth/login 이동 (여기까지는 정상)
  2. /auth/login 마운트 시 ensureSessionToken()이 자동 실행되어 reissue를 시도 (stage로 정상 요청)
  3. stage에는 아직 refresh 쿠키가 없어 reissue 실패 → reissueAccessTokenIfPossible의 catch가 clearSession()을 호출 → 이 안에 있던 removeAdminApiEnvironment()가 방금 저장한 "stage" 값을 지워버림
  4. 로그인 시 resolveActiveApiBaseUrl()이 저장값을 못 찾고 prod로 폴백 → prod로 로그인 요청이 나감

근본 원인: clearSession()이 환경 저장 키까지 함께 지우는 동작은 환경이 로그인 이메일에서 파생되던 #615 시절의 잔재였습니다. 지금은 환경이 FAB으로 관리하는 독립적인 사용자 선택이므로, 로그아웃/401/reissue 실패 등 세션 정리 경로에서도 살아남아야 합니다.

수정 내용

  • apps/admin/src/lib/auth/session.ts: clearSession()에서 removeAdminApiEnvironment() 호출 제거(관련 import 정리). 이제 세션 정리는 access token만 지우고 환경 선택은 건드리지 않습니다. switchAdminApiEnvironment의 JSDoc도 더 이상 사실이 아닌 "clearSession이 환경 키까지 지우므로 순서가 중요하다"는 설명을 제거하고 실제 동작만 남겼습니다.
  • apps/admin/src/lib/utils/localStorage.ts: 더 이상 쓰이지 않는 removeAdminApiEnvironment 함수 자체를 삭제.
  • apps/admin/src/lib/auth/session.test.ts: 기존 순서 검증 테스트를 걷어내고, **"저장된 어드민 API 환경 값은 clearSession() 이후에도 유지된다"**는 회귀 테스트를 추가(실제 jsdom localStorage로 검증).
  • apps/admin/src/lib/utils/localStorage.test.ts: 삭제된 removeAdminApiEnvironment 관련 테스트 제거.

검증

$ pnpm --filter @solid-connect/admin test
 Test Files  9 passed (9)
      Tests  34 passed (34)

$ pnpm --filter @solid-connect/admin ci:check
Checked 77 files in 147ms. No fixes applied.   # biome check
tsc --noEmit                                    # 통과 (에러 없음)

🤖 Generated with Claude Code

manNomi added 3 commits August 2, 2026 01:17
로그인 이메일로 dev/prod를 자동 판별하던 방식을 제거하고, 저장된 환경 값을
"stage"/"prod"로 통일했다. 레거시로 저장된 "dev" 값은 loadAdminApiEnvironment에서
"stage"로 해석해 기존 사용자 선택을 보존한다. 환경 전환은 이후 FAB이 호출할
switchAdminApiEnvironment(clearSession → saveAdminApiEnvironment → 리다이렉트)로
일원화한다.
어드민 배포가 main 하나로 통합됨에 따라, 로그인 이메일 대신 화면 우하단
플로팅 버튼(FAB)으로 stage/prod API 환경을 명시적으로 전환하도록 했다.
클릭 시 confirm으로 로그아웃 경고 후 switchAdminApiEnvironment를 호출해
세션을 정리하고 환경을 저장한 뒤 /auth/login으로 전체 페이지 이동한다.
로컬 오버라이드(VITE_API_SERVER_URL) 상태에서는 LOCAL로 표시하고 전환을
비활성화한다. 로그인 전에도 환경을 고를 수 있도록 루트 레이아웃에 마운트했다.
@manNomi
manNomi requested a review from wibaek as a code owner August 1, 2026 16:25
@vercel

vercel Bot commented Aug 1, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
solid-connect-university-web Ready Ready Preview Aug 1, 2026 4:27pm
solid-connect-web-admin Ready Ready Preview Aug 1, 2026 4:27pm
1 Skipped Deployment
Project Deployment Actions Updated (UTC)
solid-connection-web Skipped Skipped Aug 1, 2026 4:27pm

@github-actions github-actions Bot added the admin label Aug 1, 2026
@coderabbitai

coderabbitai Bot commented Aug 1, 2026

Copy link
Copy Markdown

Warning

Review limit reached

@manNomi, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 45 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews.

How do review limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 69678ebd-9bb7-431d-894f-65e49b0a22cc

📥 Commits

Reviewing files that changed from the base of the PR and between 43e57e3 and 10e7dcc.

📒 Files selected for processing (13)
  • apps/admin/src/app/layout.tsx
  • apps/admin/src/components/layout/EnvironmentBanner.tsx
  • apps/admin/src/components/layout/EnvironmentSwitcherFab.tsx
  • apps/admin/src/components/layout/environmentDisplay.ts
  • apps/admin/src/lib/api/auth.test.ts
  • apps/admin/src/lib/api/auth.ts
  • apps/admin/src/lib/auth/environment.test.ts
  • apps/admin/src/lib/auth/environment.ts
  • apps/admin/src/lib/auth/session.test.ts
  • apps/admin/src/lib/auth/session.ts
  • apps/admin/src/lib/env.ts
  • apps/admin/src/lib/utils/localStorage.test.ts
  • apps/admin/src/lib/utils/localStorage.ts

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 10e7dcc0e2

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +48 to +50
clearSession();
saveAdminApiEnvironment(next);
redirect("/auth/login");

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Preserve the selected environment through the login check

When the target environment has no refresh cookie, this redirect opens /auth/login, whose effect immediately calls ensureSessionToken(); the failed reissue calls clearSession(), removes the just-saved adminApiEnvironment, and leaves the login form defaulting to PROD. Consequently, an administrator cannot perform the intended first login to STAGE because every switch is undone before credentials are submitted; skip that reissue check after switching or avoid clearing the explicit environment selection on reissue failure.

Useful? React with 👍 / 👎.

useEffect(() => {
setEnvironment(resolveDisplayedEnvironment());

const handleStorageChange = () => setEnvironment(resolveDisplayedEnvironment());

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Reload other tabs when the stored environment changes

When the admin console is open in multiple tabs and one tab switches environments, this handler only changes the badge in the other tabs. Their mounted pages and React Query caches remain populated with data from the old environment, while subsequent requests resolve their base URL from the newly stored environment and the shared access token has been removed, so an old-environment view can issue mutations against the new environment; the storage handler should force the same reload/cache reset used by the initiating tab.

Useful? React with 👍 / 👎.

@manNomi
manNomi merged commit ef19b60 into main Aug 1, 2026
13 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant