Skip to content

/api/v1/push/fcm-token

의도

기기별 FCM 토큰을 사용자에 매핑하고, 로그아웃/계정 전환 시 매핑을 정리. 신규 공시 발생 시 관심 종목 구독자에게 푸시 알림 발송.

POST /api/v1/push/fcm-token — 등록/갱신

인증

JWT 필요 (Authorization: Bearer $TOKEN)

입력

json
{
  "fcm_token": "eFP_o_e-...:APA91bF...",
  "platform": "ios"
}
  • platform: "ios" | "android" 만 허용

응답

  • 204 No Content — 등록/갱신 성공
  • 400 Bad Requestfcm_token 누락 or platform 값 부정
  • 401 Unauthorized — JWT 누락/만료

curl

bash
curl -s -X POST http://localhost:8000/api/v1/push/fcm-token \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"fcm_token":"eFP_o_e-...","platform":"android"}'

DELETE /api/v1/push/fcm-token — 해제

인증

JWT 필요

입력

json
{ "fcm_token": "eFP_o_e-...:APA91bF..." }

응답

  • 204 No Content — 삭제 성공 (없어도 204)
  • 400 Bad Requestfcm_token 누락
  • 401 Unauthorized

curl

bash
curl -s -X DELETE http://localhost:8000/api/v1/push/fcm-token \
  -H "Authorization: Bearer $TOKEN" \
  -H "Content-Type: application/json" \
  -d '{"fcm_token":"eFP_o_e-..."}'

비즈니스 메모

토큰 단일 소유권 (migration 00013)

push_subscriptions.fcm_token 에 글로벌 UNIQUE 제약. 동일 토큰이 여러 user_id 에 매핑되는 race / 잔여를 DB 레벨에서 차단.

UpsertON CONFLICT (fcm_token) DO UPDATE SET user_id = EXCLUDED.user_id 패턴 — 같은 기기에서 A → B 로 재로그인 시 A 의 매핑이 원자적으로 B 로 교체됨. 이로써 A 가 로그아웃 후에도 A 의 알림이 B 에게 전달되는 정보 누출 차단.

클라이언트 측 등록 시점

  • 로그인 성공 시 (handleAuthToken)refetch() 가 사용자 정보 받은 후 PushService.init() 호출 → 권한 요청 + 토큰 발급 + 등록
  • 세션 복원 시 (refetch() 성공) — 동일하게 PushService.init() 호출. iOS 의 requestPermission 은 이미 응답한 사용자에겐 다이얼로그 띄우지 않음
  • onTokenRefresh — Firebase 가 토큰 갱신할 때마다 자동 재등록
  • 로그아웃 (logout())PushService.unregister() → DELETE 호출로 서버 매핑 정리

PushService 내부 가드: _refreshListenerAttachedonTokenRefresh.listen 중복 등록 방지.

토큰 만료 처리 (FCM HTTP v1 errorCode)

SendToTokensexpired 토큰 목록을 반환 → 호출자가 DeleteTokens 로 정리. UNREGISTERED 만 만료 분류INVALID_ARGUMENT 는 페이로드 버그여도 발생 가능해 정상 토큰 대량 삭제 위험. SENDER_ID_MISMATCH 등 나머지는 일시/설정 오류 가능성 두고 보존.

발송 트리거

backend/cmd/server/main.goDisclosureMonitor.onNew 콜백:

  1. pushRepo.FindUserIDsByCorpCode(corpCode)watch_stocks WHERE corp_code = $1 AND notify_enabled = true
  2. pushRepo.FindTokensByUserIDs(userIDs)
  3. fcmSender.SendToTokens(tokens, title, body, data) — 데이터 페이로드: {rcept_no, corp_code}
  4. expired 토큰 즉시 삭제

앱 내 라우팅 (Flutter)

알림 데이터 rcept_no 가 있으면 /disclosures/$rceptNo 로 이동:

  • Foreground/Background: FirebaseMessaging.onMessageOpenedApp 리스너
  • Terminated → tap 진입: FirebaseMessaging.instance.getInitialMessage()runApp() 직후 한 번 반환. _DartBriefAppState.initState 에서 소비 → setState(_skipSplash = true) 로 스플래시 건너뛰고 즉시 라우팅

환경변수 (backend)

  • FIREBASE_SERVICE_ACCOUNT_JSON_B64 — base64 인코딩된 Firebase 서비스 계정 JSON. 빈 값이면 푸시 비활성화 (개발 환경에서 FCM 없이 빌드 가능)