Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 9 additions & 3 deletions static/06_데이터모델_및_무결성.md
Original file line number Diff line number Diff line change
Expand Up @@ -92,16 +92,22 @@ id BIGINT PK
member_id BIGINT FK -> member
provider VARCHAR(20) -- GOOGLE / KAKAO / NAVER
provider_user_id VARCHAR(255)
email VARCHAR(255) NULL
email VARCHAR(255) NOT NULL
created_at TIMESTAMPTZ NOT NULL
deleted_at TIMESTAMPTZ NULL
```

- `provider_user_id`는 공급자가 발급한 식별자입니다(Google `sub`, Kakao `id`, Naver `response.id`). 식별 기준으로는 이메일이나 닉네임을 사용하지 않습니다.
- 공급자 간 값이 충돌할 수 있으므로 유니크는 `(provider, provider_user_id)` 복합입니다.
- 숫자로 보이는 값도 문자열로 저장합니다.
- `email`은 설정 화면 표시용으로 저장합니다. 공급자가 제공하며, 미동의·미제공 시 `null`일 수 있습니다. 식별키가 아니므로 유니크를 걸지 않습니다.
- `email`은 설정 화면 표시용으로 저장하며 **필수입니다.** 식별키가 아니므로 유니크는 걸지 않지만, 값이 없는 행은 두지 않습니다. 설정 화면이 이 값을 반드시 표시해야 하고, 값이 없는 계정은 그 화면을 채울 수 없습니다.
- **1차 보장은 공급자 콘솔의 필수 동의 설정입니다.** 세 공급자 모두 이메일을 필수 동의로 설정해 두었으므로, 사용자가 이메일만 거절하고 진행하는 선택지가 동의 화면에 나오지 않습니다. 동의를 거부하면 로그인이 취소되어 애초에 가입 요청이 오지 않습니다.
- **서버도 이메일 없는 응답을 거절합니다.** 위 설정에 의존하는 상태를 코드로 확인하지 않으면, 전제가 깨졌을 때 값 없는 계정이 조용히 만들어집니다. 전제가 깨지는 경우는 콘솔 설정이 선택 동의로 되돌려지는 것, 공급자 응답 형식이 바뀌는 것, 앱의 이메일 수집 자격(카카오 비즈 앱 등)이 상실되는 것입니다.
- 즉 이 경로는 **사용자가 일상적으로 밟는 흐름이 아니라 방어선**입니다. 발생하면 가입이 되지 않고 로그인 실패로 처리되며, 컬럼의 `NOT NULL`이 마지막 방어선입니다.
- 탈퇴 시 `provider_user_id`와 `email`을 마스킹합니다. 개인정보이므로 파기 대상입니다.
- **마스킹은 치환이며 `NULL`로 만드는 것이 아닙니다.** 두 컬럼 모두 `NOT NULL`이라 `NULL`을 넣을 수 없습니다. 원본을 식별 불가한 값으로 덮어쓰는 것이 마스킹입니다.
- 치환값이 회원끼리 겹쳐도 됩니다. 유니크는 `(provider, provider_user_id)`이고 **활성행만**(`WHERE deleted_at IS NULL`) 대상이므로, 마스킹 시점에는 이미 `deleted_at`이 채워져 인덱스 밖입니다. 유니크를 피하려고 회원별로 다른 값을 만들 필요가 없습니다.
- 구체적 치환값은 탈퇴 구현이 정합니다. 이 문서가 정하는 것은 **`NULL`이 아니라는 것**과 **원본을 되돌릴 수 없어야 한다는 것**입니다.
- `member`와 별도로 `deleted_at`을 둡니다. 부분 유니크 인덱스의 `WHERE` 절이 자기 테이블 컬럼만 참조할 수 있기 때문이며, 이 컬럼이 없으면 탈퇴 후 동일 소셜 계정으로 재가입할 수 없습니다.

### 2.3 place
Expand Down Expand Up @@ -578,7 +584,7 @@ Collection 소프트 삭제

```text
member 소프트 삭제
→ social_account 소프트 삭제 + provider_user_id 마스킹
→ social_account 소프트 삭제 + provider_user_id·email 마스킹
→ Record·Context 소프트 삭제
→ Collection·CollectionRecord 소프트 삭제
→ 해당 User가 생성한 Follow 소프트 삭제
Expand Down
2 changes: 1 addition & 1 deletion static/07_ERD.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ erDiagram
bigint member_id FK
varchar provider "GOOGLE/KAKAO/NAVER"
varchar provider_user_id "탈퇴 시 마스킹"
varchar email "설정 표시용, 탈퇴 시 마스킹, nullable"
varchar email "설정 표시용, 탈퇴 시 마스킹"
timestamptz created_at
timestamptz deleted_at
}
Expand Down
4 changes: 4 additions & 0 deletions static/08_API_명세.md
Original file line number Diff line number Diff line change
Expand Up @@ -322,6 +322,9 @@ Set-Cookie: logged_in=1; Secure; SameSite=Lax; Path=/

- 복귀 경로는 **서버 설정값**이며 요청 파라미터로 받지 않는다. 임의 URL을 받으면 open redirect 취약점이 된다.
- 로그인 이전 화면으로 되돌아가는 처리는 클라이언트가 담당한다(로그인 시작 전 경로를 `sessionStorage` 등에 보관).
- **공급자 응답에 이메일이 없으면 가입하지 않고 실패로 처리한다**(`email`은 필수다 — [06 §2.2](06_데이터모델_및_무결성.md)). 세 공급자 콘솔이 이메일을 필수 동의로 두고 있어 사용자가 이메일만 거절하고 진행할 수는 없으므로, 이 실패는 **정상 흐름이 아니라 그 설정이 깨졌을 때의 방어선**이다.
- 복귀는 다른 실패와 같은 **`error=OAUTH_FAILED`** 다. **사유별로 `error` 값을 가르지 않는다** — 위 규칙대로 클라이언트는 고정된 값 하나만 보고 재로그인을 유도하고, 원인은 서버 로그에서 찾는다. 따라서 클라이언트에 분기를 더할 필요가 없다.
- 사유를 노출하지 않는 이유: 이 실패는 사용자가 우리 화면에서 고칠 수 있는 것이 아니다(동의는 공급자 쪽에 있다). 값을 가르면 클라이언트에 **발생하지 않는 분기**가 남는다.

## 3.3 토큰 재발급

Expand Down Expand Up @@ -374,6 +377,7 @@ GET /api/core/v1/me/summary
```

- 카운트는 모두 활성 데이터 기준 집계다.
- **`email`은 항상 있다.** 이메일 없는 계정은 가입 단계에서 걸러지므로(3.2, [06 §2.2](06_데이터모델_및_무결성.md)) 이 필드가 생략되는 경우는 없다. 클라이언트에 값 없음 대비가 필요하지 않다.
- 팔로워·팔로잉 목록은 제공하지 않는다. 수치는 본인만 볼 수 있다.
- `memberId`는 반환하지 않는다. 개인 API는 서버가 쿠키로 사용자를 식별하므로 클라이언트가 자신의 내부 ID를 알 필요가 없다(1.1).

Expand Down