Skip to content

feat: Android support, symmetric families, family hand-over and Kakao login - #8

Merged
Frekion2002 merged 23 commits into
mainfrom
feat/kakao-login
Nov 5, 2026
Merged

Frekion2002 merged 23 commits into
mainfrom
feat/kakao-login

Conversation

@Frekion2002

Copy link
Copy Markdown
Collaborator

Ships the Android client's server work in one deploy. Includes #6 and #7 (they close when this merges), plus:

  • feat/family-ownership: when the family creator leaves or deletes their account, the earliest joined member becomes the owner (the family is deleted only when nobody is left)
  • feat/kakao-login: Kakao login via OpenID Connect ID tokens (/v1/auth/kakao/challenge, /v1/auth/kakao), migration 0015. Disabled until KAKAO_APP_KEY is set
  • Production compose defaults GOOGLE_CLIENT_ID to the Collog Firebase web client (public value), so Android Google sign-in works without a host change

Deploy notes

  • Migrations 0011 to 0015 run on deploy (the deploy script backs up the database first). Checked on Postgres 17 with existing data preserved.
  • Consent document moves to 2026-10-26.v5, so existing iOS users consent once more.
  • Android incoming calls need FCM_KEY_FILE in /etc/collog/backend.env plus the Firebase key file on the host. Without it the server starts normally and Android devices simply get no push.
  • The privacy policy update is already live (Collog-Privacy test-ready #2).

Tests: 339 passed, ruff clean.

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.
The creator could not leave (409) and deleting their account deleted
the whole family, cutting off everyone else. Now the creator can leave
by removing themselves, and both leaving and account deletion pass the
family to the member who was invited first; a family with nobody left
is deleted with its pending invitations, as before.
Mirror the Google flow: the app asks for a single-use nonce, signs in
with the Kakao SDK, and the server verifies the ID token against
Kakao's JWKS (issuer, audience = KAKAO_APP_KEY, nonce). New accounts
are role-free and get a family on their first invitation. The shared
challenge and OIDC verification code now serves both providers.
The web client ID is public (it is the audience of Android ID tokens), so the
production compose file now falls back to the Collog Firebase web client when
backend.env does not set GOOGLE_CLIENT_ID. Android sign-in works on the
existing production env without a host change.
@Frekion2002
Frekion2002 merged commit 73a154a 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