# 회원가입 정보 구조 이 문서는 `users`와 `user_auth_identities` 테이블 구조를 기준으로, 회원가입 시 필요한 정보와 저장 방식을 정리한다. ## 기본 개념 `users`는 서비스 내부의 사용자 계정이다. `user_auth_identities`는 사용자가 어떤 방식으로 로그인했는지를 저장한다. 예를 들어 게스트 로그인, 구글 로그인, 카카오 로그인은 모두 이 테이블에 저장된다. 하나의 `users` 계정에는 여러 로그인 방식이 연결될 수 있다. 예시: ```text users.id = 1 - guest - google - kakao ``` 단, 현재 구조에서는 같은 로그인 제공자는 하나만 연결할 수 있다. 예를 들어 하나의 사용자 계정에 구글 계정 2개를 동시에 연결할 수는 없다. ## 회원가입 공통 입력값 회원가입 요청은 로그인 제공자와 무관하게 아래 정보를 기준으로 처리한다. | 필드 | 필수 | 설명 | | --- | --- | --- | | `provider` | 필수 | 로그인 제공자. `guest`, `google`, `email`, `kakao`, `naver`, `github`, `apple` 중 하나 | | `providerUserId` | 필수 | 로그인 제공자가 내려준 사용자 고유 ID | | `displayName` | 필수 | 서비스에서 표시할 사용자 이름 | | `email` | 선택 | 로그인 제공자에서 받은 이메일 | | `avatarUrl` | 선택 | 로그인 제공자에서 받은 프로필 이미지 URL | ## 게스트 회원가입 게스트 회원가입은 외부 제공자가 없기 때문에 서버가 `providerUserId`를 생성한다. 추천 입력값: ```json { "provider": "guest", "displayName": "익명" } ``` 서버 처리: | 저장 위치 | 값 | | --- | --- | | `users.display_name` | 요청의 `displayName`, 없으면 `익명` | | `users.canonical_email` | `null` | | `users.avatar_url` | `null` | | `users.role` | `USER` | | `users.status` | `ACTIVE` | | `user_auth_identities.provider` | `guest` | | `user_auth_identities.provider_user_id` | 서버가 생성한 게스트 ID | | `user_auth_identities.email` | `null` | | `user_auth_identities.display_name` | 요청의 `displayName`, 없으면 `익명` | | `user_auth_identities.avatar_url` | `null` | 게스트 ID는 UUID 같은 충돌 가능성이 낮은 값으로 생성하는 것을 권장한다. 예시: ```text guest:550e8400-e29b-41d4-a716-446655440000 ``` ## 소셜 회원가입 소셜 회원가입은 클라이언트가 소셜 로그인 완료 후 받은 사용자 정보를 서버에 전달하거나, 서버가 토큰을 검증한 뒤 사용자 정보를 조회해서 저장한다. 추천 입력값: ```json { "provider": "google", "providerUserId": "109876543210123456789", "displayName": "홍길동", "email": "user@example.com", "avatarUrl": "https://example.com/avatar.png" } ``` 서버 처리: | 저장 위치 | 값 | | --- | --- | | `users.display_name` | 요청의 `displayName` | | `users.canonical_email` | 요청의 `email` | | `users.avatar_url` | 요청의 `avatarUrl` | | `users.role` | `USER` | | `users.status` | `ACTIVE` | | `user_auth_identities.provider` | 요청의 `provider` | | `user_auth_identities.provider_user_id` | 요청의 `providerUserId` | | `user_auth_identities.email` | 요청의 `email` | | `user_auth_identities.display_name` | 요청의 `displayName` | | `user_auth_identities.avatar_url` | 요청의 `avatarUrl` | ## 이메일 기준 처리 `users.canonical_email`은 대표 이메일이다. 소셜 로그인 제공자가 이메일을 내려주면 `canonical_email`에 저장할 수 있다. 주의할 점: - 이메일이 없는 제공자도 있을 수 있다. - 이메일 인증 여부가 불명확한 경우 곧바로 계정 병합 기준으로 쓰면 위험할 수 있다. - 같은 이메일로 이미 가입된 사용자가 있더라도 자동 병합은 신중하게 처리해야 한다. ## 중복 가입 판단 회원가입 또는 로그인 시 먼저 `user_auth_identities`에서 아래 조건으로 기존 연결 정보를 찾는다. ```sql select * from user_auth_identities where provider = :provider and provider_user_id = :providerUserId; ``` 결과가 있으면 신규 회원가입이 아니라 기존 사용자 로그인으로 처리한다. 결과가 없으면 신규 `users`를 생성하고, 이어서 `user_auth_identities`를 생성한다. ## 계정 연결 이미 로그인한 사용자가 추가 소셜 계정을 연결하는 경우에는 새 `users`를 만들지 않는다. 대신 현재 로그인한 `users.id`로 `user_auth_identities`만 추가한다. 예시: ```text 현재 로그인 사용자: users.id = 1 추가 연결 요청: google 처리 결과: user_auth_identities.user_id = 1 user_auth_identities.provider = 'google' ``` 현재 구조에서는 같은 사용자에게 같은 provider를 중복 연결할 수 없다. ## 추천 API 형태 ### 게스트 회원가입 ```http POST /api/auth/guest Content-Type: application/json ``` ```json { "displayName": "익명" } ``` ### 소셜 회원가입 또는 로그인 ```http POST /api/auth/social Content-Type: application/json ``` ```json { "provider": "google", "providerUserId": "109876543210123456789", "displayName": "홍길동", "email": "user@example.com", "avatarUrl": "https://example.com/avatar.png" } ``` 실제 구현에서는 클라이언트가 넘긴 `providerUserId`를 그대로 신뢰하기보다, 가능하면 서버에서 소셜 로그인 토큰을 검증한 뒤 provider 사용자 ID를 확정하는 방식을 권장한다.