website: 어드민 메일 링크 기본 도메인(admin.likelion-khu.com→likelion-khu.com) 수정
prod에 관리자 4명을 시딩하면서 받은 비밀번호 재설정 메일 링크가 `https://admin.likelion-khu.com/admin/reset-password/...`로 와 있었다. 존재하지 않는 도메인이었다.
infra/docs/dns.md를 확인해보니 실제로 브라우저가 접속할 수 있는 도메인은 likelion-khu.com, www, dev 셋뿐이었다. 어드민 전용 서브도메인 같은 건 애초에 없었다. 프론트는 단일 Next 앱이고 어드민 화면은 그 안의 /admin/* 경로(frontend/src/app/admin/reset-password/[token]/page.tsx)로 서빙되고 있었으니, admin.likelion-khu.com이라는 값 자체가 애초부터 실체가 없는 주소였던 셈이다.
원인을 따라가 보니 application.yml의 frontend-base-url 기본값이 옛 설계(어드민을 별도 서브도메인으로 분리하려던 구조)를 그대로 남기고 있었다. 그리고 infra/.env.stage.example과 .env.prod.example 어디에도 FRONTEND_BASE_URL을 명시적으로 채우도록 안내하는 항목이 없었다. 그러니 실제 stage/prod .env에도 이 값이 오버라이드되지 않은 채로 남아 있었고, 결과적으로 메일 링크는 없는 도메인에 /admin 경로까지 중복(.../admin/reset-password/...)으로 붙어 나가고 있었다.
이 부분을 보다가 예전 기억이 떠올랐다. PUBLIC_SITE_URL도 똑같은 종류의 문제를 #124에서 겪은 적이 있었다. 그때 stage에서 보낸 메일이 기본값(prod 주소)으로 빠져서 prod 사이트를 가리키는 버그가 있었고, 그걸 고치면서 .env.stage.example/.env.prod.example에 값을 명시적으로 채워두는 패턴으로 정리했었다. 그 흔적은 application.yml 주석에도 남아 있었다("public-site-url을 쓸 것(#124 리뷰에서 혼용 발견)"). 문제는 그 조치가 PUBLIC_SITE_URL에만 적용되고 FRONTEND_BASE_URL에는 똑같이 적용되지 않았다는 것이다. 같은 클래스의 버그를 한 번 고쳐놓고, 옆에 있는 동일한 구조의 값은 놓친 셈이다.
고치는 방향은 자연스럽게 정해졌다. PUBLIC_SITE_URL을 고쳤던 방식을 그대로 FRONTEND_BASE_URL에도 적용하면 됐다.
# admin.likelion-khu.com은 실제로 존재하는 도메인이 아니다(별도 서브도메인 없음, 프론트는 단일
# Next 앱이고 어드민은 그 안의 /admin 경로일 뿐 — infra/docs/dns.md 기준). 이 기본값이 옛 설계를
# 그대로 남기고 있어 stage/prod .env에 오버라이드가 없으면 초대·재설정 메일 링크가 존재하지 않는
# 도메인(게다가 경로까지 /admin/admin/...으로 중복)을 가리키던 버그를 여기서 고친다.
frontend-base-url: ${FRONTEND_BASE_URL:https://likelion-khu.com}
기본값 자체를 likelion-khu.com으로 바꾸고, infra/.env.stage.example에는 dev.likelion-khu.com, .env.prod.example에는 likelion-khu.com을 명시적으로 채워 넣었다. prod의 경우 기본값과 값이 같지만, PUBLIC_SITE_URL 때도 그랬듯 stage와 값이 갈린다는 걸 코드로 드러내기 위해 굳이 명시적으로 적었다.
이 PR 자체는 코드 기본값과 .env.example 템플릿만 고친다. 서버에만 존재하고 gitignore되어 있는 실제 infra/.env.stage·.env.prod에 값을 채우고 backend-stage/backend-prod 컨테이너를 재기동해야 실제로 반영되는데, 그건 이 PR 밖의 작업으로 남겨뒀다. 테스트는 Admin*/EmailService*/AuthEmailHttpEndToEnd* 관련 유닛/통합 테스트를 돌렸고, 링크 경로 자체를 검증하는 테스트는 도메인이 아니라 경로만 보기 때문에 이번 변경과는 무관하게 통과했다. Docker가 필요한 통합테스트 6개는 로컬 환경에 Docker가 없어 스킵했다.
PR은 생성되고 4분 만에 dev 브랜치로 병합됐다. 값 하나 잘못 박혀 있는 게 원인이라 수정 자체는 크지 않았지만, PUBLIC_SITE_URL에서 이미 한 번 겪었던 패턴이 다른 값에는 적용 안 된 채 남아 있었다는 점은 눈여겨볼 만했다. 비슷한 성격의 설정값이 여러 개 있을 때 하나를 고치면서 나머지도 같이 점검하는 습관이 필요하다는 걸 다시 확인했다.