Skip to content

온보딩 데이터 모델

한국어 번역본

이 문서는 원문(영어)의 한국어 번역이다. 정본은 원문이며, 계약·게이트 판단이 갈리면 원문을 따른다. 원문: docs/data/onboarding-data-model.md

범위

온보딩 v1은 인증된 계정과 그 최소 profiles 행을 제품 온보딩 완료와 분리한다. requires_onboarding이 아직 true인 상태에서도 프로필은 존재할 수 있다.

UI는 표현 전용으로 유지된다. UI는 앱 컨테이너로부터 상태와 액션을 받고, 앱 컨테이너가 BarbelicApi와 인증/프로필 도메인을 호출한다.

프로필 상태

profiles는 재개 가능한 상태를 저장한다:

  • onboarding_version: 현재 데이터 계약 버전. 최초 버전은 1이다.
  • onboarding_step: profile이나 consents 같은 안정적인 의미 단계 키.
  • onboarding_draft: 진행 저장마다 병합되는 크기 제한 JSON 객체.
  • onboarding_completed_at: 현재 온보딩이 완료되기 전까지 null.
  • onboarding_completion_source: user 또는 legacy_backfill.
  • onboarding_updated_at: 마지막 진행 쓰기 시각.

완료 없이 진행만 쓰는 공개 RPC는 없다. 완료 필드를 설정할 수 있는 것은 complete_onboarding뿐이다.

닉네임 정책

display_name은 눈에 보이는 표시 이름이며, 전역 고유 계정 핸들이 아니다. v1에서는 중복을 의도적으로 허용한다. 고유한 공개 핸들은 표시 이름을 소급해서 고유하게 만들지 않고 별도 컬럼(profiles.handle, 아래)에 둔다.

아이디(@handle) — 2026-08-23 (20260821470000)

profiles.handle은 친구가 나를 찾는 공개 아이디다(피드 친구 검색이 display_name과 함께 매칭한다). 사용자가 정하기 전까지 null이고, 유일하며(profiles_handle_key), set_profile_handle_v1(p_handle) 또는 complete_onboarding(payload.handle)로만 쓴다.

  • 정규화(normalize_profile_handle_v1): 선두 @·바깥 공백 제거, 소문자. 결과는 ^[a-z0-9_]{3,20}$여야 한다(아니면 22023)
  • 다른 프로필이 쓰는 아이디는 23505 Handle already taken으로 거부한다. complete_onboarding 안에서는 완료 전체가 롤백된다
  • 같은 아이디 재설정은 멱등이다. 내 정보 탭에서 나중에 설정·변경할 수 있다 (클라이언트 문구 코드: LG_PROFILE_HANDLE_TAKEN / LG_PROFILE_HANDLE_INVALID)

서버 검증은 다음 규칙을 적용한다:

  • 바깥쪽 공백을 잘라내고 내부 공백은 하나로 합친다
  • 정규화 후 1자 이상 24자 이하
  • 제어 문자를 거부한다
  • 온보딩 완료 시점에 플레이스홀더 새 리프터를 거부한다

상태 RPC는 같은 정책을 nickname_policy로 반환하므로, UI는 정본(source of truth)이 되지 않으면서도 즉각적인 클라이언트 측 피드백을 줄 수 있다.

동의 이력

user_consents는 추가분을 보존하는 이력 테이블이다. 사용자·동의 유형·문서 버전 조합마다 활성 행이 하나 존재할 수 있다. 철회는 활성 행에 시각을 기록하며, 다시 동의하면 이전 이력을 지우는 대신 새 행을 삽입한다.

현재 Barbelic 동의 요구사항은 다음 식별자를 공표한다. 동의 문서 버전은 온보딩 데이터 계약 버전과 독립적이다:

동의 유형버전필수
terms_of_servicev1
privacy_policyv1
sensitive_data_processingv1
profile_sharingv1아니오
sensitive_sharingv1아니오

(marketing은 출시 시점에 받지 않는다. 요구 목록의 원천은 onboarding_consent_requirements(), 출시판 전환 Barbelic#1640, 앱 v0.19.4.)

필수 동의 레코드는 불변이며 공개적으로 접근 가능한 문서에 대응한다:

동의 유형버전시행일공개 문서
terms_of_servicev12026-09-15/legal/terms-v1.html
privacy_policyv12026-09-15/legal/privacy-v1.html
sensitive_data_processingv12026-09-15/legal/consent-sensitive-v1.html
profile_sharingv12026-09-15/legal/consent-sharing-v1.html
sensitive_sharingv12026-09-15/legal/consent-sharing-sensitive-v1.html

출시판 v1(Barbelic#1640, 2026-09-15)이 출시 전 초안 v1~v6를 대체했고, 앱은 이 파일을 public/legal/에서 직접 제공한다(Barbelic#1643). 클라이언트는 get_onboarding_state()가 반환한 정확한 필수 버전을 해석해 사용하며, 다른 버전으로 조용히 대체하지 않는다. 초안 버전에 동의한 계정은 테스트 계정뿐이며 다음 로그인 때 다시 동의를 받는다. 이들의 profiles를 다시 쓰거나 user_consents 행을 지어내는 마이그레이션은 없다.

이 대응 관계는 src/react/legalDocuments.ts에 집중되어 있다. 개정된 문안을 공개하려면, 이미 동의된 버전 뒤의 내용을 교체하는 대신 새 문서 버전과 그에 맞는 동의 요구사항이 필요하다.

현재 필수인 동의를 철회하면 사용자가 완료한 온보딩이 무효가 되고 사용자는 consents 단계로 돌아간다. 소급 인정(grandfathered)된 사용자에게 지어낸 동의 행을 부여하지 않는다.

기존 사용자 백필

2026-07-13 00:00:00+09 이전에 생성된 프로필은, 비어 있지 않고 플레이스홀더가 아닌 표시 이름을 이미 가진 경우에만 완료로 표시된다. 이들의 완료 출처는 legacy_backfill이며, 약관 동의는 추론하지도 삽입하지도 않는다.

고정된 컷오프 덕분에 마이그레이션을 다시 실행해도 온보딩 v1 도입 이후 가입한 사용자를 실수로 완료 처리하지 않는다. 쓸 만한 프로필 정보가 없는 기존 사용자는 미완료 상태로 남고 profile 단계에서 재개할 수 있다.

RPC 계약

get_onboarding_state()

auth.uid()에 대한 프로필 진행 상황, 완료 상태, 닉네임 정책, 현재 동의 요구사항, 그리고 최대 50개의 동의 이력 행을 반환한다.

회수됨: save_onboarding_progress(payload)

20260820120000에서 drop됐다(오너 결정 2026-08-20 — 온보딩은 짧아서 중단되면 재개하지 않고 처음부터 다시 한다). complete_onboarding이 같은 페이로드 형태를 받으므로, 그 형태를 여기 남긴다:

json
{
  "display_name": "Barbelic User",
  "current_step": "consents",
  "draft": { "preferred_unit": "kg" },
  "consents": [
    {
      "consent_type": "terms_of_service",
      "document_version": "v2",
      "accepted": true
    }
  ]
}

draft 객체는 내부 헬퍼가 병합하므로, 나중의 부분 페이로드가 이미 저장된 draft를 지우지 않는다. 전체 페이로드는 32 KiB, draft는 16 KiB로 상한이 정해져 있다.

complete_onboarding(payload)

동일한 선택적 진행 페이로드를 적용하고, 닉네임과 현재 필수인 모든 동의 버전을 검증한 뒤 온보딩을 완료로 표시한다. 이미 완료된 프로필에 대해서는 반복 호출이 멱등이다.

호출자 본인의 활성 일치 동의만 철회한다. 활성 행이 없으면 멱등이다.

모든 공개 RPC는 소유권을 auth.uid()에서 도출하며 user-id 파라미터를 받지 않는다. 내부 헬퍼는 security definer이고, 빈 search_path를 사용하며, authenticated 실행 권한이 없다.