← 개발 로그 목록

website: 글쓰기를 1회용 토큰 링크에서 로그인 세션으로 바꾸고, 블로그 관리 화면을 새로 얹다

/ 4분 분량 / 개발 로그

멤버 글쓰기 기능을 손보다가 로그인 흐름 자체를 바꿨고, 그 김에 관리자 쪽에 글 숨김·재게시 화면을 새로 만들었다. 알림 구독 폼에는 개인정보 동의 체크박스를 넣었고, 배포 파이프라인에서는 예전에 한 번 실제로 겪었던 롤백 무력화 버그를 다시 손봤다.

원래 글쓰기(WriteForm)는 운영진이 발급한 1회용 토큰 링크로 접근하는 방식이었다. token prop을 받아서 checking / no-token / valid / expired / used / error 상태를 하나하나 분기 처리하고, 임시저장 키도 feed-draft:${token} 식으로 토큰에 묶어뒀었다. 그런데 멤버 로그인 시스템이 자리를 잡으면서 이 방식이 오히려 번거로워졌다. 링크를 매번 새로 발급하고 전달하는 과정 자체가 불필요한 단계였고, 이미 멤버 로그인이 있는데 왜 별도의 토큰 체계를 유지해야 하나 싶었다.

그래서 WriteForm을 세션 기반으로 바꿨다. getCurrentMember()로 로그인 여부를 확인하고, 401이면 /member/login으로 리다이렉트, 403이면 "멤버 전용이에요" 안내를 띄우는 식으로 상태를 checking / ready / error 세 가지로 단순화했다. 토큰별로 나뉘어 있던 임시저장 키도 DRAFT_KEY 하나로 통합했다. 상태 분기가 5개에서 2개로 줄어드니 컴포넌트 자체가 읽기 편해졌다. 다만 getCurrentMember 성공 응답 안에서 로컬스토리지의 임시저장 draft를 복원하는 부분은 effect 내부의 비동기 콜백이라 렌더링 타이밍이 헷갈릴 수 있어서, "await 이후라 이 안의 setState는 effect에 동기적이지 않다"는 코멘트를 남겨뒀다. 나중에 다시 봤을 때 헷갈리지 않으려고.

글쓰기를 세션 기반으로 바꾸고 나니 자연스럽게 관리자 쪽에서도 글을 게시 후에 숨기거나 다시 노출할 수 있어야 한다는 게 눈에 들어왔다. 그전까지는 게시된 글을 내리는 수단이 없었다. 그래서 BlogManager.tsx를 새로 만들었다. AdminDashboard에서 이미 쓰던 refreshSession 가드 패턴을 그대로 가져와서 401·UNAUTHENTICATED·INVALID_REFRESH_TOKEN이면 로그인 화면으로 보내고, 글 목록을 불러와 상태별(DRAFT/PUBLISHED/HIDDEN) 배지를 붙였다. adminApi.ts에는 getAdminPosts(공개 목록과 달리 상태로 필터링하지 않는 전체 목록 조회)와 updatePostStatus(PATCH로 상태 전이) 두 함수를 추가했다. 이 두 개면 숨기기/재게시 UI를 충분히 만들 수 있어서 API를 더 늘리지 않고 여기서 끝냈다.

알림 구독 폼(NotificationForm)도 같이 손봤는데, 여기는 기능이 아니라 법적 요구사항 쪽이었다. 개인정보보호법 제15조가 요구하는 수집 목적·항목·보유 기간·동의 거부 가능 여부 네 가지 고지를 체크박스와 함께 넣었다. 버튼에 disabled={!agreed}로 막아두긴 했지만, 폼이 엔터 등 다른 경로로 제출될 가능성도 있어서 handleSubmit 안에도 if (!agreed) return을 한 번 더 넣었다. 이런 건 한 군데만 막아두면 나중에 꼭 구멍이 생긴다.

마지막으로 CD 파이프라인(cd.yml)에서 예전에 실제로 겪었던 문제를 다시 손봤다. 롤백용 마커 파일(.prev_backend_tag_*)을 헬스체크 통과 직후에 지우고 있었는데, 이러면 그다음 단계인 스모크 테스트가 실패해도 롤백 스텝이 "마커가 없다"며 그냥 스킵돼버린다. dev→main 승격 중 prod에서 이 문제로 롤백이 무력화된 적이 있어서(#133), 이번엔 마커 정리를 별도 스텝으로 분리해서 스모크 테스트 이후에 실행하도록 옮겼다. 스모크 테스트가 실패하면 job 자체가 실패 처리되니 이 스텝은 자동으로 스킵되고, 마커는 그대로 남아 롤백이 정상 작동한다. 헬스체크는 "앱이 떴다"만 보장하고 실제 클라이언트 경로(DNS·TLS·nginx 라우팅)까지는 스모크 테스트가 커버한다는 걸 다시 한번 확인한 셈이다.

토큰 링크 방식을 없애면서 코드가 꽤 정리됐고, 배포 쪽 버그는 한 번 겪었던 문제라 재발 방지 차원에서라도 이번에 확실히 고쳐두는 게 맞다고 판단했다. 상태 관리는 분기 개수를 줄일수록 나중에 읽기 편하다는 걸 다시 확인한 작업이었다.