Skip to content

Commit 2354dc4

Browse files
committed
docs: update project documentation for session visibility
1 parent 7241b8b commit 2354dc4

9 files changed

Lines changed: 40 additions & 26 deletions

File tree

README.md

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ ReadMates는 정기 독서모임의 세션 준비, 참여 관리, 기록 공개,
55
- Demo: [https://readmates.pages.dev](https://readmates.pages.dev)
66
- Stack: `React 19`, `TypeScript`, `Vite`, `Cloudflare Pages Functions`, `Kotlin`, `Spring Boot`, `Spring Security`, `MySQL`, `Flyway`
77
- Scope: 정기 독서모임의 현재·예정 회차 준비부터 참여 관리, 기록 공개, 피드백 문서 열람까지 아우르는 운영형 서비스
8-
- Highlight: Google OAuth, 서버 측 session cookie, Cloudflare BFF 보안 경계, 역할 기반 권한 제어, 피드백 문서 접근 제어, Playwright E2E, 공개 릴리즈 후보 scan
8+
- Highlight: Google OAuth, 서버 측 session cookie, Cloudflare BFF 보안 경계, 현재/예정 세션 공개 범위, 역할 기반 권한 제어, 피드백 문서 접근 제어, Playwright E2E, 공개 릴리즈 후보 scan
99

1010
이 저장소는 외부 공개를 전제로 정리되어 있습니다. 운영 secret, 실제 멤버 데이터, private deployment state, DB dump, 로컬 경로, OCI OCID는 문서와 예시에 포함하지 않습니다.
1111

@@ -60,10 +60,10 @@ MySQL
6060
- Cloudflare Pages Functions BFF: SPA와 API 호출을 같은 origin으로 묶고, `/api/bff/**` 요청만 Spring `/api/**`로 전달합니다. OAuth 시작과 callback도 Pages Functions proxy를 거쳐 public demo origin에서 자연스럽게 동작합니다.
6161
- BFF secret과 origin/referrer 경계: Spring은 API 요청의 `X-Readmates-Bff-Secret`을 검증합니다. `POST`, `PUT`, `PATCH`, `DELETE` 요청은 허용된 `Origin` 또는 `Referer`도 요구합니다. BFF secret은 Cloudflare Pages Functions와 Spring runtime 설정에만 두고 브라우저 bundle에 넣지 않습니다.
6262
- 역할 기반 접근 제어: `게스트`, `둘러보기 멤버`, `정식 멤버`, `호스트` 상태에 따라 route와 API 권한을 분리합니다. 읽기 가능한 화면과 쓰기 가능한 operation을 별도로 제한합니다.
63-
- 현재/예정 세션 관리: `sessions.state``DRAFT`, `OPEN`, `PUBLISHED` 같은 운영 단계를 구분하고, `sessions.visibility``HOST_ONLY`, `MEMBER`, `PUBLIC` 공개 범위의 DB source of truth입니다. 호스트는 여러 예정 `DRAFT` 세션을 준비할 수 있지만 현재 `OPEN` 세션은 클럽당 하나만 시작할 수 있습니다.
63+
- 현재/예정 세션 관리: `sessions.state``DRAFT`, `OPEN`, `PUBLISHED` 같은 운영 단계를 구분하고, `sessions.visibility``HOST_ONLY`, `MEMBER`, `PUBLIC` 공개 범위의 DB source of truth입니다. 호스트는 여러 예정 `DRAFT` 세션을 준비하고 멤버 공개 여부를 바꿀 수 있지만, 현재 `OPEN` 세션은 클럽당 하나만 시작할 수 있습니다.
6464
- 멤버 프로필 경계: 화면에 쓰는 표시 이름은 API에서는 `displayName`으로 주고받고, 현재 club membership의 `memberships.short_name`에 저장합니다. 본인은 `/api/me/profile`, 호스트는 `/api/host/members/{membershipId}/profile`로 같은 클럽 멤버의 표시 이름을 정리할 수 있고, 같은 클럽 안 중복과 예약어를 서버에서 막습니다.
6565
- 피드백 문서 접근 제어: 호스트가 Markdown 피드백 문서를 업로드하면 서버가 파싱해 typed response로 제공합니다. 호스트는 전체 문서를 관리할 수 있고, 정식 멤버는 본인이 참석한 회차의 피드백 문서만 읽을 수 있습니다. 둘러보기 멤버와 미참석자는 locked state를 봅니다.
66-
- 공개 기록 경계: public route/API에는 공개로 발행된 세션과 공개 가능한 하이라이트/한줄평만 노출합니다. 멤버 홈의 예정 세션은 `/api/sessions/upcoming`에서 `MEMBER`/`PUBLIC` `DRAFT`만 반환하고, `HOST_ONLY` draft는 멤버/둘러보기 화면, archive, notes, public surface에 노출하지 않습니다. 현재 세션 참여, private notes, meeting data, feedback document 본문은 인증과 권한 검사를 통과해야 접근할 수 있습니다.
66+
- 공개 기록 경계: public route/API에는 `public_session_publications.visibility=PUBLIC`인 기록과 공개 가능한 하이라이트/한줄평만 노출합니다. 멤버 홈의 예정 세션은 `/api/sessions/upcoming`에서 `MEMBER`/`PUBLIC` `DRAFT`만 반환하고, `HOST_ONLY` draft는 멤버/둘러보기 화면, archive, notes, public surface에 노출하지 않습니다. 현재 세션 참여, private notes, meeting data, feedback document 본문은 인증과 권한 검사를 통과해야 접근할 수 있습니다.
6767
- 프론트엔드 route-first 구조: React Router route module이 loader/action, API 호출, 모델 조립, UI props assembly를 담당합니다. 실제 source root는 `front/src/app`, `front/src/pages`, `front/features`, `front/shared`이며, feature는 `api`, `model`, `route`, `ui` 책임으로 나누고 UI는 props와 callback만 받아 렌더링합니다.
6868
- 서버 클린 아키텍처 전환: `publication`, `archive`, `feedback`, `session`, `note`, `auth`의 운영 API surface는 `adapter.in.web -> application.port.in -> application.service -> application.port.out -> adapter.out.persistence` 경계를 따릅니다. Shared health endpoint는 `shared.adapter.in.web`에 있고, disabled password/password-reset/dev-invitation endpoint는 `410 Gone` stub으로 남습니다. Boundary test는 전환된 application package의 Spring JDBC/DAO 의존과 web adapter의 persistence 세부 의존을 막습니다.
6969
- 공개 릴리즈 안전성: `scripts/build-public-release-candidate.sh`로 공개 릴리즈 후보를 만든 뒤 `scripts/public-release-check.sh`로 private path, local workstation path, OCI OCID, GitHub token, OpenAI/API-key-shaped token, real-looking DB/BFF/OAuth secret, Gmail 주소 등을 검사합니다.

docs/agents/server.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ Security boundaries:
3333

3434
- Browser traffic should go through Cloudflare/Vite same-origin BFF routes.
3535
- Server API should treat `X-Readmates-Bff-Secret`, session cookies, membership status, role, and attendance as authorization boundaries.
36-
- Public APIs must expose only published public records.
36+
- Public APIs must expose only records whose publication visibility is `PUBLIC`; do not assume `sessions.state=PUBLISHED` is the only public exposure path.
3737
- Password/password-reset routes are disabled operational paths; do not revive them unless explicitly requested.
3838

3939
Checks:

docs/deploy/README.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# ReadMates 배포 문서
22

3-
검토일: 2026-04-24
3+
검토일: 2026-04-25
44

55
이 디렉터리는 ReadMates의 공개 안전 배포 문서 허브입니다. 운영 환경의 목표 구조, 신뢰 경계, secret 보관 원칙, 공개 릴리즈 후보 검증 흐름을 설명하되 계정별 값과 private deployment state는 Git에 두지 않습니다.
66

@@ -68,12 +68,13 @@ Google OAuth 로그인 성공 후 Spring은 `readmates_session` cookie를 발급
6868
ReadMates는 제품 수준에서 invite-only 흐름을 사용합니다.
6969

7070
- 게스트는 공개 홈과 공개 기록만 볼 수 있습니다.
71-
- 초대 없이 Google로 로그인한 사용자는 둘러보기 멤버가 될 수 있습니다.
71+
- 초대 없이 Google로 로그인한 사용자는 둘러보기 멤버가 될 수 있고, 멤버 공개 예정 세션 같은 읽기 전용 멤버 화면 일부를 볼 수 있습니다.
7272
- 호스트는 둘러보기 멤버를 정식 멤버로 전환하거나, 정식 멤버를 현재 세션에서 제외/복구/비활성화/삭제하고 같은 클럽 멤버의 표시 이름을 정리할 수 있습니다.
73+
- 호스트는 여러 `DRAFT` 예정 세션을 준비하고 `HOST_ONLY`, `MEMBER`, `PUBLIC` 공개 범위를 지정할 수 있지만, 같은 클럽에서 현재 `OPEN` 세션은 하나만 시작할 수 있습니다.
7374
- 호스트 API는 활성 `host` role을 요구합니다.
74-
- 멤버 API는 허용된 `member` 상태를 요구하며, 현재 세션 쓰기는 해당 세션 참여 상태도 확인합니다.
75+
- 멤버 API는 허용된 `member` 상태를 요구하며, 현재 세션 쓰기는 해당 세션 참여 상태도 확인합니다. `/api/sessions/upcoming``DRAFT`이면서 `MEMBER` 또는 `PUBLIC`인 세션만 반환합니다.
7576
- 본인 프로필 수정은 인증된 멤버 앱 읽기 가능 상태에서만 허용하고, 같은 클럽 안의 표시 이름 중복과 예약어는 서버에서 막습니다.
76-
- Public API는 공개로 발행된 공개 기록만 반환합니다.
77+
- Public API는 `public_session_publications.visibility=PUBLIC` 공개 기록만 반환합니다.
7778
- 피드백 문서는 권한과 참석 여부를 통과한 정식 멤버 또는 호스트에게만 노출합니다.
7879

7980
## 환경 변수

docs/deploy/cloudflare-pages-spa.md

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44

55
## 배포 형태
66

7-
ReadMates 운영 프론트엔드는 Cloudflare Pages가 `front/dist`의 Vite React SPA를 서빙하고, `front/functions`의 Pages Functions가 같은 origin BFF와 OAuth proxy를 제공합니다. 인증, 멤버십, 현재 세션, 공개 기록, 피드백 문서의 진실은 OCI Spring Boot API와 MySQL에 있습니다.
7+
ReadMates 운영 프론트엔드는 Cloudflare Pages가 `front/dist`의 Vite React SPA를 서빙하고, `front/functions`의 Pages Functions가 같은 origin BFF와 OAuth proxy를 제공합니다. 인증, 멤버십, 현재/예정 세션, 공개 기록, 피드백 문서의 진실은 OCI Spring Boot API와 MySQL에 있습니다.
88

99
## Pages 프로젝트 설정
1010

@@ -94,9 +94,10 @@ https://readmates.pages.dev/login/oauth2/code/google
9494
4. 정식 멤버는 로그인 후 `/app`으로 들어가는지 확인합니다.
9595
5. 초대 없이 들어온 새 Google 사용자는 로그인 성공 후 `/app`으로 redirect되고, 둘러보기 멤버 안내와 읽기 전용 멤버 화면을 볼 수 있는지 확인합니다. `/app/pending`은 둘러보기 멤버 안내용 호환 route로 남아 있어 직접 열어도 동작해야 합니다.
9696
6. 호스트가 `/app/host/members`에서 둘러보기 멤버를 정식 멤버로 전환하고 멤버 표시 이름을 수정할 수 있는지 확인합니다.
97-
7. 정식 멤버가 `/app`을 reload해도 멤버 route에 접근할 수 있는지 확인합니다.
98-
8. 둘러보기 멤버가 피드백 문서 route에 접근할 수 없는지 확인합니다.
99-
9. 피드백 문서 `PDF로 저장` action이 숨겨져 있는지 확인합니다. 현재 `feedbackDocumentPdfDownloadsEnabled=false`라서 print route는 사용자-facing PDF 저장 흐름으로 쓰지 않습니다.
97+
7. 호스트가 `/app/host/sessions/new`에서 `DRAFT` 예정 세션을 만들고, `/app/host/sessions/:sessionId/edit`에서 공개 범위를 `MEMBER` 또는 `PUBLIC`으로 바꾼 뒤 현재 세션으로 시작할 수 있는지 확인합니다.
98+
8. 정식 멤버가 `/app`을 reload해도 멤버 route에 접근할 수 있고, 멤버 공개 예정 세션이 있으면 홈에서 볼 수 있는지 확인합니다.
99+
9. 둘러보기 멤버가 피드백 문서 route에 접근할 수 없는지 확인합니다.
100+
10. 피드백 문서 `PDF로 저장` action이 숨겨져 있는지 확인합니다. 현재 `feedbackDocumentPdfDownloadsEnabled=false`라서 print route는 사용자-facing PDF 저장 흐름으로 쓰지 않습니다.
100101

101102
PDF 저장 흐름을 다시 켜는 경우에는 `front/shared/config/readmates-feature-flags.ts`를 변경한 뒤 `/app/feedback/:sessionId/print`가 데이터를 불러오고 browser print를 한 번 호출하는지 별도로 검증합니다.
102103

docs/deploy/cloudflare-pages.md

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -102,8 +102,8 @@ curl -sS https://readmates.pages.dev/api/bff/api/public/club
102102
- `/app``200`으로 SPA를 반환합니다.
103103
- `/api/bff/api/auth/me`는 BFF를 통해 Spring에 도달합니다. 로그아웃 상태여도 anonymous auth state를 담은 `200`일 수 있습니다.
104104
- `/oauth2/authorization/google`은 Google 또는 Spring OAuth 흐름으로 redirect됩니다.
105-
- `/api/bff/api/public/club`은 공개 기록에 노출 가능한 club 정보만 반환해야 합니다.
106-
- deep route와 legacy route인 `/app/session/current`, `/app/host/members`, `/invite/<token>`, `/reset-password/<token>`은 Cloudflare 404가 아니라 SPA fallback으로 진입해야 합니다.
105+
- `/api/bff/api/public/club``PUBLIC` 공개 범위의 공개 기록에 노출 가능한 club 정보만 반환해야 합니다.
106+
- deep route와 legacy route인 `/app/session/current`, `/app/host`, `/app/host/sessions/new`, `/app/host/sessions/<session-id>/edit`, `/app/host/members`, `/invite/<token>`, `/reset-password/<token>`은 Cloudflare 404가 아니라 SPA fallback으로 진입해야 합니다.
107107

108108
## 비용 상태 확인
109109

docs/development/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ ReadMates를 로컬에서 실행하고, 테스트하고, 구조를 이해하기
1818
## 주요 구조 문서
1919

2020
- 프런트엔드 route-first 경계, feature `api/model/route/ui` 책임, legacy 예외 제거 기준은 [architecture.md](architecture.md)의 "프런트엔드 route-first 경계" 섹션을 기준으로 합니다.
21-
- 서버 current member 해석, 현재/예정 세션 조회, 멤버 세션 쓰기, 호스트 세션 쓰기, 세션 공개 범위, 멤버 프로필/표시 이름 경계는 [architecture.md](architecture.md)의 "서버 내부 구조", "현재/예정 세션과 공개 범위", "멤버 프로필과 표시 이름" 섹션을 기준으로 합니다.
21+
- 서버 current member 해석, 현재/예정 세션 조회, 멤버 세션 쓰기, 호스트 세션 쓰기, 세션/기록 공개 범위, 멤버 프로필/표시 이름 경계는 [architecture.md](architecture.md)의 "서버 내부 구조", "현재/예정 세션과 공개 범위", "멤버 프로필과 표시 이름" 섹션을 기준으로 합니다.
2222
- 작업자는 루트 [../../AGENTS.md](../../AGENTS.md)에서 task별 agent guide를 먼저 고르고, 프런트엔드 패키지 안에서는 [../../front/AGENTS.md](../../front/AGENTS.md)의 패키지 지침도 함께 확인합니다.
2323
- `docs/superpowers` 아래 문서는 과거 설계와 구현 계획의 기록입니다. 현재 동작의 source of truth는 이 디렉터리와 실제 코드, 테스트, 배포 스크립트입니다.
2424

docs/development/architecture.md

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,8 @@ ReadMates의 사용자 상태는 membership status와 role을 함께 봅니다.
115115

116116
| 상태/역할 | 의미 |
117117
| --- | --- |
118-
| `VIEWER` | Google 로그인은 했지만 정식 초대를 수락하지 않은 둘러보기 멤버입니다. 읽기 가능한 일부 멤버 화면은 볼 수 있지만 현재 세션 쓰기, 피드백 문서 열람, 호스트 도구는 제한됩니다. |
119-
| `ACTIVE` + `MEMBER` | 정식 멤버입니다. 현재 세션 참여, RSVP, 체크인, 질문, 한줄평, 장문 서평, 본인이 참석한 회차의 피드백 문서 열람이 가능합니다. |
118+
| `VIEWER` | Google 로그인은 했지만 정식 초대를 수락하지 않은 둘러보기 멤버입니다. 읽기 가능한 일부 멤버 화면과 멤버 공개 예정 세션은 볼 수 있지만 현재 세션 쓰기, 피드백 문서 열람, 호스트 도구는 제한됩니다. |
119+
| `ACTIVE` + `MEMBER` | 정식 멤버입니다. 현재 세션 참여, 멤버 공개 예정 세션 확인, RSVP, 체크인, 질문, 한줄평, 장문 서평, 본인이 참석한 회차의 피드백 문서 열람이 가능합니다. |
120120
| `ACTIVE` + `HOST` | 호스트입니다. 정식 멤버 권한에 운영 권한이 추가됩니다. |
121121
| `SUSPENDED` | 제한된 멤버 상태입니다. route guard와 API authorization에서 쓰기/운영 권한을 제한합니다. |
122122
| `LEFT`, `INACTIVE` | 떠났거나 비활성화된 계정 상태입니다. 멤버 앱과 쓰기 기능에서 제외됩니다. |
@@ -126,9 +126,9 @@ ReadMates의 사용자 상태는 membership status와 role을 함께 봅니다.
126126

127127
## 현재/예정 세션과 공개 범위
128128

129-
ReadMates는 클럽별로 하나의 현재 `OPEN` 세션과 여러 개의 예정 `DRAFT` 세션을 함께 다룹니다. 호스트가 새 세션을 만들면 기본 상태는 `DRAFT`, 기본 공개 범위는 `HOST_ONLY`입니다. 호스트는 `/api/host/sessions``/api/host/sessions/{sessionId}`에서 책/회차 metadata를 만들고 수정하며, `/api/host/sessions/{sessionId}/visibility``HOST_ONLY`, `MEMBER`, `PUBLIC` 중 하나를 저장합니다.
129+
ReadMates는 클럽별로 하나의 현재 `OPEN` 세션과 여러 개의 예정 `DRAFT` 세션을 함께 다룹니다. 호스트가 새 세션을 만들면 기본 상태는 `DRAFT`, 기본 공개 범위는 `HOST_ONLY`입니다. 호스트는 `/api/host/sessions``/api/host/sessions/{sessionId}`에서 책/회차 metadata를 만들고 수정하며, `/api/host/sessions/{sessionId}/visibility``HOST_ONLY`, `MEMBER`, `PUBLIC` 중 하나를 저장합니다. 호스트 세션 목록과 상세 응답은 `state``visibility`를 함께 반환하므로, 프런트엔드는 예정 세션 카드, 현재 세션 카드, 기록 공개 범위 UI를 같은 contract로 조립합니다.
130130

131-
`sessions.visibility`가 세션 공개 범위의 DB source of truth입니다. `public_session_publications.visibility`와 legacy `is_public` 값은 공개 기록 호환 경로를 위해 동기화되지만, archive, notes, upcoming session 조회는 `sessions.visibility`를 기준으로 `HOST_ONLY` 항목을 숨깁니다. `sessions.state`는 운영 단계를 구분합니다. `DRAFT`는 예정 세션, `OPEN`은 현재 참여 세션, `PUBLISHED`는 공개/멤버 기록으로 발행된 세션입니다.
131+
`sessions.visibility`가 세션 공개 범위의 DB source of truth입니다. `PUT /api/host/sessions/{sessionId}/publication`은 공개 요약과 기록 공개 범위를 저장하면서 `sessions.visibility`, `public_session_publications.visibility`, legacy `is_public` 값을 함께 맞춥니다. `public_session_publications.visibility`와 legacy `is_public` 값은 공개 기록 호환 경로를 위해 남아 있지만, archive, notes, upcoming session 조회는 `sessions.visibility`를 기준으로 `HOST_ONLY` 항목을 숨깁니다. `sessions.state`는 운영 단계를 구분합니다. `DRAFT`는 예정 세션, `OPEN`은 현재 참여 세션, `PUBLISHED`는 공개/멤버 기록으로 발행된 세션입니다.
132132

133133
호스트는 `/api/host/sessions/{sessionId}/open`으로 `DRAFT` 세션 하나를 현재 세션으로 시작합니다. 같은 클럽에 이미 `OPEN` 세션이 있으면 다른 draft를 동시에 열 수 없습니다. 이미 열린 세션에 대한 open 요청은 같은 세션 detail을 반환하고, `CLOSED``PUBLISHED` 세션을 현재 세션으로 되돌리지는 않습니다.
134134

@@ -150,7 +150,7 @@ ReadMates에서 멤버를 부르는 앱 표시 이름은 `displayName`입니다.
150150
Public route/API에는 명시적으로 공개된 데이터만 나갑니다.
151151

152152
- 공개 사이트는 `/api/public/club`, `/api/public/sessions/{sessionId}` 같은 public API를 사용합니다.
153-
- 공개 기록에는 발행된 세션, 공개 가능한 하이라이트, 한줄평, 책/회차 정보만 포함합니다.
153+
- 공개 기록에는 `public_session_publications.visibility=PUBLIC` 세션 요약, 공개 가능한 하이라이트, 한줄평, 책/회차 정보만 포함합니다.
154154
- 예정 세션 목록은 멤버 앱의 `/api/sessions/upcoming`에서만 제공하며, `HOST_ONLY` draft는 멤버, 둘러보기 멤버, archive, notes, public route/API에 노출하지 않습니다.
155155
- 현재 세션의 RSVP, 읽은 분량, private notes, meeting data, 피드백 문서 본문은 public API로 노출하지 않습니다.
156156
- 멤버 앱의 `/api/archive/**`, `/api/notes/**`, `/api/sessions/current/**`는 인증과 membership 상태를 확인합니다.

0 commit comments

Comments
 (0)