/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 Request —
fcm_token누락 orplatform값 부정 - 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 Request —
fcm_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 레벨에서 차단.
Upsert 는 ON 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 내부 가드: _refreshListenerAttached 로 onTokenRefresh.listen 중복 등록 방지.
토큰 만료 처리 (FCM HTTP v1 errorCode)
SendToTokens 가 expired 토큰 목록을 반환 → 호출자가 DeleteTokens 로 정리. UNREGISTERED 만 만료 분류 — INVALID_ARGUMENT 는 페이로드 버그여도 발생 가능해 정상 토큰 대량 삭제 위험. SENDER_ID_MISMATCH 등 나머지는 일시/설정 오류 가능성 두고 보존.
발송 트리거
backend/cmd/server/main.go 의 DisclosureMonitor.onNew 콜백:
pushRepo.FindUserIDsByCorpCode(corpCode)—watch_stocks WHERE corp_code = $1 AND notify_enabled = truepushRepo.FindTokensByUserIDs(userIDs)fcmSender.SendToTokens(tokens, title, body, data)— 데이터 페이로드:{rcept_no, corp_code}- 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 없이 빌드 가능)