Fullstack 103

heo4·2026년 9월 10일

Fullstack

목록 보기
58/70

풀스택

CareMatch 개발 일지

요양보호사 구인구직 플랫폼 "케어매치(CareMatch)" 팀 프로젝트를 진행하면서, 하루 동안
Claude Code와 함께 백엔드 기능 하나를 새로 설계하고, 프로덕션 버그 두 개를 잡고,
팀 공유 문서까지 정리한 기록입니다. 실수도 하나 있었는데 그것도 그대로 남깁니다.

오늘 한 일 요약

  1. Render 계정 복구 후 배포 상태 점검
  2. "요양보호사 등록 시 전화번호 인증" 기능 설계·구현
  3. 프론트 버그 2건 발견 및 수정 (구·군 데이터 누락, main 빌드 깨짐)
  4. git rm -rf 사고 한 번, 안전한 복구 방법 재시도
  5. 팀 공유 브랜치에 회의용 "미완수 사항" 문서 작성 + 코드 기준 재검증

1. Render 계정 복구 체크리스트부터

어제 Render 대시보드 로그인이 막혀서 배포 상태를 아무도 확인할 수 없었던 사고가 있었다.
계정이 복구된 김에 만들어둔 체크리스트(docs/RENDER_ACCESS_RECOVERY.md)를 하나씩 따라갔다.

  • 배포된 커밋 해시를 대시보드 없이 확인할 방법이 없다는 게 마음에 걸려서, /actuator/info에
    배포 커밋을 노출하는 작업을 추가했다. Docker 빌드 컨텍스트엔 .git이 없어서 Gradle
    git-properties 플러그인은 못 쓰고, 대신 Render가 런타임에 자동 주입하는
    RENDER_GIT_COMMIT 환경변수를 읽어서 노출하는 쪽으로 갔다. 별도 설정 없이도 다음부턴
    curl .../actuator/info 한 줄로 배포 반영 여부를 확인할 수 있다.

2. "카카오 로그인만 쓰면 본인인증 필요 없는 거 아냐?"

이 질문에서 시작해서 실제로 코드를 봤더니, 소셜 로그인 가입자는 이미
verified(true)로 인증을 건너뛰도록 돼 있었다. 근데 카카오는 전화번호를 안 준다는 게 문제.
요양보호사 구인구직 특성상 시설(고용주)이 연락할 번호는 결국 필요하다.

논의 끝에 방향을 이렇게 잡았다:

  • 가입 시점엔 전화번호를 요구하지 않는다 (카카오는 애초에 못 줌 → 가입을 가볍게 유지)
  • 요양보호사로 등록(자격증 등록)하는 시점에 전화번호 입력 + 인증을 요구한다
  • 실제 SMS/이메일 발송 방식은 나중에 결정 — 지금은 게이트 로직만 구성

구현은 기존에 이미 있던 VerificationService.assertVerified()를 재사용해서 생각보다 간단했다.
CertificateService.register()에 한 줄 추가하는 것으로 게이트를 걸었다.

@Transactional
public CertificateDetailResponse register(Long memberId, CreateCertificateRequest req) {
    JobSeekerProfile profile = jobSeekerProfileRepository.findByMemberId(memberId)
            .orElseThrow(...);
    verificationService.assertVerified(VerificationChannel.PHONE, profile.getMember().getPhone());
    // ...
}

전화번호를 나중에 입력할 수 있게 PUT /api/members/me/phone 엔드포인트도 하나 추가했다.
테스트 전부 통과 확인하고 PR 올렸는데, 리뷰 붙이기 전에 팀원이 다른 PR(자격증 종류 enum
전환)을 먼저 머지해버려서 병합 충돌이 났다 — 근데 이게 단순 문서 충돌이 아니라
CertificateService.java 자체가 두 기능이 겹치는 지점이었다. 다행히 git이 자동 머지에
성공해서(건드린 줄이 안 겹침) 실제 충돌은 API 문서 한 곳뿐이었다.

3. rm -rf 사고 — 그리고 안전하게 다시 하기

팀 파일 공유용으로 share라는 브랜치를 만들고 "안의 내용 다 지워달라"는 요청을 받았다.
git rm -r .로 추적 파일을 지운 다음, 남은 빈 폴더를 정리한다고 rm -rf backend frontend docs를
실행했는데 — 이게 .gitignore에 걸려 있던 로컬 전용 파일(.env.local, 로컬 시드 SQL 등)까지
같이 날려버렸다.

다행히 main 자체는 전혀 영향받지 않았다(브랜치는 독립적인 스냅샷이라 커밋도 안 한 상태였다).
git reset --hard로 추적 파일은 바로 복구됐고, 사라진 로컬 파일 2개 중 하나는
.env.example에서 금방 재생성했다. 실수를 인정하고, 두 번째 시도는 격리된 git worktree에서
진행해서 메인 작업 디렉터리를 아예 안 건드리는 방식으로 안전하게 마쳤다.

배운 것: "정리"라는 말이 나오면 rm -rf 대신 git rm(추적 파일만 지움)을 먼저 떠올리기.
그리고 위험한 브랜치 조작은 아예 별도 worktree에서.

4. 지역 선택 버그 — "구멍 난" 수정 vs 제대로 된 수정

"서울/경기 빼고 지역 선택하면 시군구가 안 나온다"는 제보를 받고 코드를 봤더니,
DISTRICT_OPTIONS라는 프론트 상수에 시·도 3개(서울/경기/인천) 데이터만 들어있었다.
나머지 14개 시·도는 아예 값이 없어서 셀렉트박스가 비활성화되는 구조였다.

1차로 시·도당 대표 지역 5~8개씩 채워서 고쳤는데, "아직도 빠진 지역이 너무 많다"는
피드백을 받고 전국 행정구역 전체(서울 25개 구, 경기 31개 시·군의 세부 구까지)로
다시 채웠다. 반복되는 .map() 코드는 헬퍼 함수 하나로 정리했다.

const toOptions = (names: string[]): SelectOption[] =>
  names.map((name) => ({ value: name, label: name }))

5. main이 조용히 깨져 있었다

작업하다가 습관적으로 git pull 했는데, 그 김에 프론트 타입체크를 한번 돌려봤더니
main이 아예 빌드가 안 되고 있었다. 원인은 다른 PR을 머지하는 과정에서 병합 충돌을
해결하다가 마커 잔여물이 그대로 커밋된 것 — >>>>>>> main 같은 줄 일부가 코드
중간에 낀 채로 남아있었다.

export function payTypeToApi(payType: string): 'HOURLY' | 'DAILY' | 'MONTHLY' {
  return LOCAL_PAY_TYPE_TO_API[payType] ?? 'MONTHLY'
=======          # <- 이게 그대로 남아있었다

백엔드 테스트는 이 파일과 무관해서 안 걸렸고, 프론트 typecheck/build를 CI에서
자동으로 안 돌리다 보니 아무도 모른 채 main에 들어가 있었다. 저장소 전체를
충돌 마커 패턴으로 재검색해서 이 파일 하나뿐인 걸 확인하고 고친 뒤, 급하게 PR 올려서
바이패스 승인으로 바로 병합했다.

이 사고 덕분에 "CI에 프론트 빌드 체크가 없다"는 게 그냥 아는 걸 넘어서 실제로
뼈아프게 느껴졌다. 다음 우선순위 후보.

6. 팀 회의용 문서 — 그리고 "확인해봤더니 틀렸던" 순간

하루 마무리로 지금까지 나온 미완수 항목들을 정리해서 팀 공유 브랜치(share)에
회의용 문서를 올렸다. "R2 스토리지 아직 안 붙었다"고 적었는데, 팀원이 "근데 R2 연동은
됐는데?"라고 지적해서 다시 확인해보니 — 진짜였다. 내가 갖고 있던 메모가 며칠 전
기준으로 멈춰 있었던 것. 실제 운영 API를 직접 호출해서 진짜 R2 presigned URL이
나오는 것까지 확인하고 문서를 정정했다.

팀원들이 같은 공유 브랜치에 올려둔 프롬프트 파일들("내 주변 일자리" 기능 요청,
전체 기능 요구사항 정리)도 분석해서 기존 코드와 대조했다. 그 중 하나는 흥미로운
발견이었는데 — 팀원이 카카오맵 기반으로 새로 만들자고 프롬프트를 올렸는데,
백엔드에는 이미 다른 스펙으로 비슷한 기능(GET /api/job-postings/nearby,
가변 반경)이 구현돼 있었고, 프론트는 의도적으로 그 API를 안 쓰고 다른 방식(지역 선택)으로
만들어져 있었다. 그대로 새 프롬프트를 실행했으면 기존 구현과 완전히 어긋날 뻔했다.

그리고 공유 파일 중 하나에 평문 자격증명(R2 키, 관리자 비밀번호 등)이 그대로 올라가
있는 걸 발견해서 재발급을 권고하는 항목도 문서 맨 위에 올렸다.

문서를 올린 뒤에도 팀원들의 PR이 계속 머지되면서 몇 시간 만에 항목 3개가 저절로
"완료"로 바뀌었다 — 전체 코드를 다시 훑어서 재검증하고 문서를 다시 갱신했다.
회의 자료는 한 번 쓰고 끝나는 게 아니라 계속 코드 상태를 따라가야 한다는 걸 새삼 느꼈다.


오늘의 숫자

  • PR: 6개 (#79, #80, #83, #84, #88, #90) + 팀원 PR 여러 개 리뷰/추적
  • 발견한 프로덕션 버그: 1개 (main 빌드 깨짐, 병합 충돌 마커 잔여물)
  • 스스로 낸 실수: 1개 (rm -rf로 로컬 파일 삭제) — git으로 즉시 복구
  • 정정한 내 착각: 1개 (R2 스토리지 상태)
  • 발견한 보안 이슈: 1개 (공유 브랜치에 평문 자격증명)

느낀 점

하루 종일 코드를 "믿지 말고 확인하자"는 태도가 제일 크게 도움이 됐다. 문서에 적힌
내용, 내 기억, 팀원이 준 프롬프트 — 전부 한 번씩은 틀려 있었다. 실제로 grep 한 줄,
curl 한 번으로 진실을 확인하는 습관이 오늘 하루에만 몇 번씩 방향을 바로잡아줬다.

0개의 댓글