Skip to content

feat: Android support and the symmetric family model - #7

Merged
Frekion2002 merged 20 commits into
mainfrom
feat/family-symmetric
Nov 5, 2026
Merged

Frekion2002 merged 20 commits into
mainfrom
feat/family-symmetric

Conversation

@Frekion2002

Copy link
Copy Markdown
Collaborator

요약

안드로이드 앱을 운영에서 쓰기 위한 서버 변경을 한 번에 합칩니다.
PR #6(feat/android-port)의 커밋을 모두 포함하므로 이 PR 하나만 합치면 됩니다(합치면 #6 도 병합된 것으로 닫힙니다).

  1. 안드로이드 지원 (PR feat: add Google sign-in and FCM push for the Android client #6): Google 로그인, FCM 통화·리포트 푸시, 기기 등록, Apple·Google 토큰 시계 오차 60초 허용, 취약 의존성 업데이트
  2. 피그마 최종본 질환: 건망증·호흡기 질환·비염 + 자유 서술 concerns
  3. 가족 대칭(B): 역할 구분 없이 가족 누구나 초대·수락·통화, 가입 정보(닉네임·성별·생년월일·이메일), 구성원 삭제·공유 범위·마케팅 동의, 관계 값 확장, 가족에게는 닉네임 우선 표시
    • 설계·API 약속: backend/docs/family-symmetric-plan.md

main 과 충돌 없음, main 뒤 20커밋.

배포 전에 해 주세요 — EC2 /etc/collog/backend.env

GOOGLE_CLIENT_ID=716006474161-cocqrkcm916la6tfbcqtfjclih6urq21.apps.googleusercontent.com
FCM_KEY_FILE=/etc/collog/fcm-service-account.json
  • GOOGLE_CLIENT_ID 는 Firebase collog-2884b 의 웹 클라이언트 ID 라 비밀값이 아닙니다.
  • FCM 키 파일(fcm-service-account.json)은 용진이 따로 전달합니다. 위 경로(호스트 절대 경로)에 두고, APPLE_KEY_FILE 과 같은 방식으로 컨테이너 사용자(uid 10001)가 읽을 수 있게 해 주세요. compose 가 /run/secrets/fcm-service-account.json 으로 읽기 전용 bind 합니다.
  • 두 값이 비어도 서버는 뜹니다. 다만 안드로이드 Google 로그인과 안드로이드 통화 알림이 동작하지 않습니다.

배포 때 일어나는 일

  • 마이그레이션 0011_google_login → 0012_profile_concerns → 0013_user_signup_fields → 0014_family_sharing. deploy.sh 가 적용 전에 pg_dump 백업을 /opt/collog/backups/ 에 남깁니다.
  • 동의 문서 버전이 2026-10-26.v5 로 바뀝니다 → 기존 iOS 사용자는 앱에서 한 번 다시 동의하게 됩니다(문구에 휴대폰 두 플랫폼·Google 로그인 추가).
  • 서버의 역할(부모·자녀) 검사가 빠집니다. iOS 앱은 그대로 동작합니다(아래 확인). iOS 앱은 지금처럼 반대 역할만 연락처에 보여 줍니다.

확인한 것

  • 서버 테스트 331개, ruff check 통과 (aebdadf)
  • Postgres 17(운영과 같은 이미지)에서 0010 → 0014 업그레이드: 모델 차이 없음, 기존 데이터 보존
  • 로컬 실기 통합 시험(안드로이드 휴대폰 + 에뮬레이터): 가입 → 초대 코드로 가족 연결 → 양방향 통화(음성) → 앱이 뒤에 있을 때 FCM 통화 알림으로 받기 → 구성원 관리·공유 범위 저장 → 구성원 삭제 뒤 복구
  • 팀 iOS 앱(Collog-iOS main f738313)을 시뮬레이터에서 이 서버에 붙여 확인: 로그인 → 안드로이드가 만든 초대 코드 수락 → 동의 → 홈(연락처) → 설정 구성원 화면 → 통화 생성(201). 실제 통화는 시뮬레이터에서 CallKit 이 동작하지 않아 아이폰 실기로 배포 뒤 확인합니다.

배포 뒤 확인

  • GET /v1/health 200, /openapi.json 에 /v1/auth/google 이 있는지
  • 안드로이드 앱 Google 로그인 → 가입 → 아이폰과 통화(용진이 확인)
  • 문제가 생기면 deploy.sh 는 실패 시 이전 판을 다시 띄우고, DB 는 위 백업으로 되돌릴 수 있습니다.

Mirror the Apple login flow: a one-time server nonce challenge, Google ID
token verification against Google's JWKS (both issuer spellings, audience =
Web OAuth client ID), and users.google_subject. GOOGLE_CLIENT_ID left empty
keeps both endpoints at 503. Migration 0011 adds the column and the
google_login_challenges table.
Devices registered with platform=ANDROID now receive the incoming-call and
report-ready signals as FCM HTTP v1 data messages. The access token is
minted from a Firebase service account (FCM_SERVICE_ACCOUNT_PATH) and
reused; unset keeps Android delivery disabled. iOS delivery is unchanged.
- An empty FCM_SERVICE_ACCOUNT_PATH (compose default) means unset instead of
  Path('.') crashing the whole backend on start.
- Without FCM, Android rows are left out of report delivery, and report
  retries skip devices that already received the notification.
- The callee receiver check accepts Android devices when FCM is configured.
- Only UNREGISTERED or an INVALID_ARGUMENT naming message.token counts as a
  dead token, and clearing it touches that device row only.
- A 401 drops the cached access token; token_uri must be Google's.
- Report messages collapse per call; service-account JSON is git-ignored.
The compose files passed FCM_SERVICE_ACCOUNT_PATH through as a raw path but
mounted no key file, so a host path in the deploy env made AppContainer fail
at startup and took the API down for every client. Mount FCM_KEY_FILE like the
APNs key and fill the container path only when it is set; when it is empty,
/dev/null is mounted and Android push stays off.
Android also requests question audio from ElevenLabs directly and signs in
with Google, so the iPhone-only wording was not true for Android users. Say
phone instead of iPhone, name both login tokens, and bump the document
version so the change is recorded as a new consent.
pyjwt 2.15.1, aiohttp 3.14.5, urllib3 2.8.0, certifi 2026.10.23 and
multidict 6.9.1 (kept on 6.x). pip-audit reports none afterwards.
The final Figma asks for 건망증 · 호흡기 질환 · 비염 plus a free-text
"추가적으로 걱정되는 부분". Health profiles now accept FORGETFULNESS,
RESPIRATORY and RHINITIS alongside the earlier codes (the iOS app still
sends those) and store an optional `concerns` note of up to 500 chars.

- Migration 0012 adds parent_profiles.concerns.
- PUT keeps the stored note when a client sends only conditions.
- Daily question pools for the three new conditions use open
  questions, as docs/profile-question-report-design.md §5 asks.
S1 of docs/family-symmetric-plan.md. Server decisions no longer look at
users.role; the column stays for responses and for picking the analysis
subject until S3.

- One family per user. Anyone in a family can create or resend an
  invitation. Anyone with no family, or alone in their own family, can
  accept (the solo family is removed); someone already in a family with
  other people gets 409.
- POST /v1/families returns the caller's family or creates it.
- Member list adds acceptedAt, consentGranted and isCreator, lists the
  creator in the same shape (empty relation), sets canInvite for
  everyone and shows invitations to every member.
- Calls work between any two members of the same family. calls.parent_id
  is the analysis subject: the one PARENT if exactly one participant has
  that role (current iOS behaviour), otherwise the callee.
- /parents/{id}/... reads id as the subject user: the subject and
  same-family members may read it; same-family members may also edit
  the profile once the subject has consented.
- Google login no longer needs a role. Without one the account starts
  with no family; clients that send a role (iOS) keep the old flow.
S2 of docs/family-symmetric-plan.md.

- Migration 0013 adds users.nickname (unique), gender, birth_date and
  email, all nullable for existing accounts.
- UserView gains the same fields plus signupCompleted (has a nickname).
- GET /v1/account/nickname-availability returns {available, reason}
  with reason INVALID or TAKEN. Nicknames are 2-10 Hangul syllables,
  Latin letters or digits.
- PATCH /v1/account/profile sets all three fields; a taken nickname is
  409 and a malformed one 422.
- Google login stores the verified email from the ID token.
- The member list shows each person's nickname and birth date.
S4 of docs/family-symmetric-plan.md.

- Migration 0014 adds family_shares (owner_id, viewer_id, report,
  timeline) and users.marketing_opt_in (default false).
- GET/PUT /v1/account/sharing lists one row per connected member of the
  caller's family; with no row everything is shared. Reports, baselines
  and signals check `report`; the call list checks `timeline`. Profiles
  and daily questions stay open to the family.
- DELETE /v1/families/{familyId}/members/{memberId}: any member can drop
  a pending invitation; a connected member can be removed by the family
  creator or leave on their own, which leaves them without a family and
  clears the sharing rows between them and the rest of the family. The
  creator cannot be removed (409).
- The member list gains canRemove.
- PATCH /v1/account/preferences sets marketingOptIn; UserView shows it.
- Account deletion also removes the user's sharing rows.
The family creator showed up to others by their Google/Apple account
name. Members list, family list, incoming call push and LiveKit names
now prefer the sign-up nickname and fall back to the account name, so
accounts without a nickname (iOS) look the same as before.
@Frekion2002
Frekion2002 merged commit aebdadf into main Nov 5, 2026
2 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