Skip to content

fix: 관리자 권한 역방향 이전 및 인증 로그 보강 - #169

Merged
dennis0405 merged 6 commits into
developfrom
feat/manager-transfer-reversal-fix
Sep 3, 2026
Merged

dennis0405 merged 6 commits into
developfrom
feat/manager-transfer-reversal-fix

Conversation

@dennis0405

@dennis0405 dennis0405 commented Sep 3, 2026 •

Copy link
Copy Markdown
Collaborator

작업 내용

  • club_manager의 (club_id, service_user_id) 전체 이력 UNIQUE 제약을 제거하는 migration을 추가했습니다. soft-delete 이력은 보존하고, deleted_at IS NULL인 행에 대한 기존 partial unique index로 동아리별 활성 관리자 1명 조건을 유지합니다.
  • 실제 PostgreSQL과 TypeORM transaction을 사용하는 회귀 테스트를 추가했습니다. migration 전 A → B → A 실패와 원자적 rollback, migration 후 성공, 동일 수신자 재시도의 멱등성, 활성 관리자 중복 차단을 한 시나리오에서 검증합니다.
  • 관리자 이전 중 알려진 23505 제약 충돌만 안전한 409로 매핑하고, 예상하지 못한 DB 오류는 500으로 유지하면서 로그에는 오류 이름·SQLSTATE·제약 이름만 남기도록 했습니다.
  • 모바일에서 네트워크 무응답, 서버 5xx, 앱 내부 오류 문구를 분리하고 409 안내를 관리자 상태 충돌에도 맞는 일반 문구로 변경했습니다.
  • Kakao access token/profile과 Apple identity token/profile·계정 이메일을 원문 로그에서 제거하고, 인증 콜백 오류 로그를 제한된 오류 이름 메타데이터로 축소했습니다.
  • CI의 검증 job에 일회용 PostgreSQL 16 service를 추가하고, Turbo가 TEST_DATABASE_URL을 테스트 task에 전달하도록 구성했습니다.

변경 이유

  • A → B 이후 A의 기존 club_manager 행은 soft-delete 이력으로 남습니다. 다시 B → A를 수행하면 (club_id, service_user_id) 전체 행 UNIQUE 제약이 과거 A 행과 새 A 행을 충돌시켜 이전 transaction 전체가 실패했습니다.
  • 이 도메인의 실제 불변식은 “같은 사용자가 역사상 한 번만 관리자”가 아니라 “동아리마다 활성 관리자가 한 명”입니다. PostgreSQL partial unique index는 predicate를 만족하는 행에만 유일성을 적용하므로 이력 보존과 활성 관리자 1명 조건을 함께 만족합니다. PostgreSQL partial index 문서
  • PostgreSQL 오류 처리는 로컬라이즈될 수 있는 메시지가 아니라 SQLSTATE와 constraint를 기준으로 분기했습니다. 23505는 unique_violation입니다. PostgreSQL error codes

테스트한 내용

  • RED: 실제 PostgreSQL에서 A → B 뒤 B → A가 club_manager_clubid_serviceuserid 충돌로 실패하고, transaction rollback으로 B만 활성 상태이며 초대가 미수락 상태로 남는 것을 확인했습니다.
  • GREEN: apps/web/sql/260904_club_manager_reversal.sql 적용 뒤 동일 초대를 재시도해 A가 다시 활성 관리자가 되고, 이력 [A 비활성, B 비활성, A 활성]과 알림 1회, 재수락 멱등성을 확인했습니다.
  • 강제 캐시 우회 루트 검증 통과: TEST_DATABASE_URL=... pnpm exec turbo run lint typecheck test --force
    • web: 23 files / 97 tests
    • mobile: 26 suites / 148 tests
    • lint/typecheck: web·mobile 모두 통과
  • pnpm --dir apps/mobile verify:full 통과: 26 suites / 148 tests, Android/iOS JS bundle 성공.
  • pnpm --dir apps/web biome:check 통과.
  • pnpm --dir apps/web build production build 성공. 테스트 파일이 API route로 포함되지 않음을 route 목록으로 확인했습니다.
  • migration·오류 매핑·인증 로그 테스트는 모두 실제 secret-shaped 문자열이 로그에 포함되지 않는 것도 검증합니다.

Migration 적용 순서 / 운영 주의

  1. 애플리케이션 코드는 legacy UNIQUE 제약이 남아 있어도 해당 충돌을 409로 안전하게 처리하므로 먼저 배포해도 호환됩니다.
  2. 역방향 이전 QA 전에 apps/web/sql/260904_club_manager_reversal.sql을 대상 DB에 수동 적용합니다. 이 PR 작업에서는 allclear-dev 및 production DB에 적용하지 않았습니다.
  3. 스크립트는 활성 관리자 중복을 먼저 검사하고, 활성 관리자 partial unique index를 보장한 뒤 legacy UNIQUE 제약만 제거합니다.
  4. ALTER TABLE ... DROP CONSTRAINT는 일반적으로 ACCESS EXCLUSIVE lock을 획득하므로 낮은 트래픽 시간에 적용하고 transaction이 짧게 끝나는지 확인합니다. PostgreSQL ALTER TABLE 문서
  5. 앱 코드 rollback은 새 스키마와 호환되므로 legacy UNIQUE 제약을 즉시 복원할 필요가 없습니다. migration 적용 후 A → B → A가 한 번이라도 완료되면 동일 pair 이력이 중복되므로, DB 제약 rollback은 중복 이력을 별도 보관·정리한 뒤에만 가능합니다. 활성 관리자 partial unique index는 rollback 시에도 제거하지 않습니다.

스크린샷 (선택)

  • DB·서버 오류 처리·문구·로그 변경으로 별도 스크린샷은 없습니다.

체크

  • 정적 검증 및 회귀 테스트 통과
  • UI 레이아웃 변경 없음
  • 새 민감정보 로그가 없고 기존 OAuth 원문 로그 제거
  • 대상 DB migration 적용 및 실기기 QA

남은 수동 QA

  • 별도 승인된 작업에서 allclear-dev DB에 migration 적용 후 실제 두 계정으로 A → B → A를 수행합니다.
  • 각 이전 직후 관리자 화면 접근 권한이 기존 관리자에서 새 관리자로 이동하는지, 활성 관리자가 한 명인지 확인합니다.
  • 동일 초대 재수락 시 완료 알림이 추가 생성되지 않는지 확인합니다.
  • 오프라인/통신 실패 문구와 서버 5xx 문구가 서로 다르게 노출되는지 확인합니다.
  • Kakao·Apple 로그인 성공을 확인하고 서버 로그에 access token, identity token, OAuth profile 및 이메일 원문이 남지 않는지 확인합니다.
  • 이 PR은 migration 적용, dev/prod 배포, 실기기 상태 변경을 수행하지 않습니다.

CI 결과

검사 결과 실행
lint/typecheck/test + PostgreSQL integration 통과 job
Web production build 통과 job
Android JS bundle 통과 job
iOS JS bundle 통과 job

@dennis0405
dennis0405 merged commit e074fca into develop Sep 3, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant