feat(posts): 글쓰기 진입점 POST_WRITE 권한 조건부 노출
포스팅 목록 헤더 링크·빈 상태 버튼을 canWrite 로 감싸 권한 없는 세션엔 노출되지 않도록 함. 신규 권한 로직 없이 create()/edit 에서 이미 쓰던 gate.has(session, PermissionKeys.POST_WRITE) 를 list() 에서도 재사용. docs/graph/index.md: PostController.java 변경분 incremental 반영 (1477→1507 노드). JSP 는 여전히 그래프 스캔 범위 밖(기존 한계). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01UqyLwTxF3FmW1eJCWN8yAo
This commit is contained in:
parent
6154826a82
commit
02a1b758a0
|
|
@ -0,0 +1,57 @@
|
||||||
|
# Work Session Report
|
||||||
|
|
||||||
|
schema_version: 2
|
||||||
|
sid: 20260724-092601
|
||||||
|
started_at: 2026-07-24T09:26:01+09:00
|
||||||
|
user_request: "포스팅은 권한있는 사람만 작성 가능한거니까 글쓰기는 권한있는 사람만 나타나게해줘"
|
||||||
|
|
||||||
|
# Advisor Invocation Decision Log
|
||||||
|
# 각 advisor 호출/스킵 판단 즉시 1줄 append
|
||||||
|
- advisor: requirements-advisor
|
||||||
|
decision: skip
|
||||||
|
rationale: '요구 명확 — 기존 POST_WRITE 서버측 게이트를 뷰에 그대로 반영하는 단순 UI 노출 조건'
|
||||||
|
checked_at: 2026-07-24T09:26:01+09:00
|
||||||
|
- advisor: research-advisor
|
||||||
|
decision: skip
|
||||||
|
rationale: 'PostController.requireWritePermission/gate.has(session, PermissionKeys.POST_WRITE) 기존 코드로 충분 — 신규 조사 불필요'
|
||||||
|
checked_at: 2026-07-24T09:26:01+09:00
|
||||||
|
- advisor: design-advisor
|
||||||
|
decision: skip
|
||||||
|
rationale: '영향 파일 3개(PostController.java, posts-list.jsp, posts-empty.jspf) 확정, 계약(canWrite model attr) 자명 — 마이크로 스코프'
|
||||||
|
checked_at: 2026-07-24T09:26:01+09:00
|
||||||
|
- advisor: implementation-advisor
|
||||||
|
decision: skip
|
||||||
|
rationale: '파일 3개·경합 없음, orchestrator 직접 구현 (SKILL §5.1 마이크로 편집 예외)'
|
||||||
|
checked_at: 2026-07-24T09:26:01+09:00
|
||||||
|
|
||||||
|
## Invocations
|
||||||
|
[]
|
||||||
|
|
||||||
|
## Summary
|
||||||
|
포스팅 목록(`/posts`) 글쓰기 진입점 2곳(헤더 링크, 빈 상태 버튼)을 `canWrite` 조건부로 감싸
|
||||||
|
POST_WRITE 권한 없는 세션엔 노출되지 않도록 변경. 서버측 신규 게이트 로직 없음 —
|
||||||
|
기존 `PostController.requireWritePermission`/`create()`가 쓰던
|
||||||
|
`gate.has(session, PermissionKeys.POST_WRITE.name())`를 `list()`에서도 재사용해
|
||||||
|
`canWrite` model attribute로 view에 전달.
|
||||||
|
|
||||||
|
## Decisions
|
||||||
|
- canWrite 판정은 `PermissionGate.has(session, PermissionKeys.POST_WRITE.name())` 재사용 (신규 로직 없음)
|
||||||
|
- posts-empty.jspf 는 `<%@ include %>` static include라 posts-list.jsp 스크립틀릿 변수(canWrite)가
|
||||||
|
그대로 보임 — 별도 request attribute 전달/재계산 불필요
|
||||||
|
|
||||||
|
## Verified_by_me
|
||||||
|
- L1 typecheck: `docker compose exec app mvn -o -P dev -DskipTests compile` → BUILD SUCCESS
|
||||||
|
- 통합 스모크(수동, 실행 중인 dev 앱 대상):
|
||||||
|
- 익명 세션 `GET /posts` → "글쓰기" 텍스트 0건 (헤더 링크·빈상태 버튼 모두 미노출)
|
||||||
|
- `admin@bibimbap.local` 로그인 세션 `GET /posts` → 헤더 `<a class="posts-write">글쓰기</a>` +
|
||||||
|
빈상태 `+ 글쓰기` 버튼 둘 다 노출
|
||||||
|
- L2: skip (외부 의존 계약 변경 없음)
|
||||||
|
|
||||||
|
## Needs_user_verification
|
||||||
|
- (없음) — dev 컨테이너 재시작으로 즉시 반영 확인 완료. 운영 배포 시엔 일반 배포 절차(재빌드/재시작)만 필요.
|
||||||
|
|
||||||
|
## graph_refresh
|
||||||
|
decision: handled_inline
|
||||||
|
judgment: partial-stale
|
||||||
|
scopes_processed: [full]
|
||||||
|
reason: '변경 4파일(≤5) → B-1 인스턴스 내 즉시 처리. incremental update로 1477→1507 노드(고스트 중복 20 exact+11 fuzzy 제거), 76 커뮤니티 불변이라 기존 라벨 재사용. docs/graph/index.md 메타 갱신 완료. JSP/JSPF 확장자는 graphify 스캔 범위 밖이라 posts-list.jsp/posts-empty.jspf 변경은 그래프 미반영(기존 한계, 이번 세션 이슈 아님).'
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
---
|
---
|
||||||
kind: graphify-meta
|
kind: graphify-meta
|
||||||
last_generated_at: 2026-07-01T14:35:00+0900
|
last_generated_at: 2026-07-24T09:43:00+0900
|
||||||
source_commit: 86b0528
|
source_commit: 6154826
|
||||||
scopes:
|
scopes:
|
||||||
- full
|
- full
|
||||||
---
|
---
|
||||||
|
|
@ -39,7 +39,7 @@ scope 예시: `full`, `src`, `docs`, `src-features` 등.
|
||||||
|
|
||||||
| scope | 마지막 생성 | 소스 커밋 | 대상 경로 | 요약 |
|
| scope | 마지막 생성 | 소스 커밋 | 대상 경로 | 요약 |
|
||||||
| --- | --- | --- | --- | --- |
|
| --- | --- | --- | --- | --- |
|
||||||
| `full` | 2026-07-01 | `86b0528` | `src/` + `docs/` | **incremental update** — 18커밋분 변경(11 파일: db/seed-dev.sql + docs 10건) 재추출 후 `build_merge` 병합 시 고스트 중복 486 exact + 251 fuzzy 추가 제거(2089→1477 노드, 4503 엣지, 76 커뮤니티). game-register.jsp 리디자인 보강·frontend-redesign-coverage-checklist·workflow-patterns 신규 노드 반영. JSP/CSS 정적자산은 여전히 스캔 범위 외. |
|
| `full` | 2026-07-24 | `6154826` | `src/` + `docs/` | **incremental update** — 3커밋분 변경(4 파일: PostController.java + docs 3건) 재추출 후 `build_merge` 병합 시 고스트 중복 20 exact + 11 fuzzy 추가 제거(1477→1507 노드, 4570 엣지, 76 커뮤니티 — 커뮤니티 수 불변이라 기존 라벨 재사용). `posts-list.jsp`/`posts-empty.jspf` 는 canWrite 권한 조건부 렌더 추가했으나 JSP 확장자가 여전히 스캔 범위 밖이라 그래프 미반영. |
|
||||||
|
|
||||||
## 갱신 시 체크리스트
|
## 갱신 시 체크리스트
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -64,6 +64,7 @@ public class PostController {
|
||||||
@RequestParam(name = "categoryId", required = false) Long categoryId,
|
@RequestParam(name = "categoryId", required = false) Long categoryId,
|
||||||
@RequestParam(name = "cursorCreatedAt", required = false) String cursorCreatedAt,
|
@RequestParam(name = "cursorCreatedAt", required = false) String cursorCreatedAt,
|
||||||
@RequestParam(name = "cursorId", required = false) Long cursorId,
|
@RequestParam(name = "cursorId", required = false) Long cursorId,
|
||||||
|
HttpSession session,
|
||||||
Model model
|
Model model
|
||||||
) {
|
) {
|
||||||
OffsetDateTime cursor = parseOffsetDateTime(cursorCreatedAt);
|
OffsetDateTime cursor = parseOffsetDateTime(cursorCreatedAt);
|
||||||
|
|
@ -78,6 +79,7 @@ public class PostController {
|
||||||
model.addAttribute("categories", postCategoriesMapper.listActive());
|
model.addAttribute("categories", postCategoriesMapper.listActive());
|
||||||
model.addAttribute("categoryId", categoryId);
|
model.addAttribute("categoryId", categoryId);
|
||||||
model.addAttribute("hasNext", hasNext);
|
model.addAttribute("hasNext", hasNext);
|
||||||
|
model.addAttribute("canWrite", gate.has(session, PermissionKeys.POST_WRITE.name()));
|
||||||
if (hasNext && !rows.isEmpty()) {
|
if (hasNext && !rows.isEmpty()) {
|
||||||
PostData last = rows.get(rows.size() - 1);
|
PostData last = rows.get(rows.size() - 1);
|
||||||
model.addAttribute("nextCursorCreatedAt", last.getCreatedAt());
|
model.addAttribute("nextCursorCreatedAt", last.getCreatedAt());
|
||||||
|
|
|
||||||
|
|
@ -15,7 +15,9 @@
|
||||||
</svg>
|
</svg>
|
||||||
<h3>아직 등록된 포스트가 없어요</h3>
|
<h3>아직 등록된 포스트가 없어요</h3>
|
||||||
<p>개발 일지, 팁, 질문 무엇이든 좋아요. 커뮤니티의 첫 글을 남겨보세요.</p>
|
<p>개발 일지, 팁, 질문 무엇이든 좋아요. 커뮤니티의 첫 글을 남겨보세요.</p>
|
||||||
|
<% if (canWrite) { %>
|
||||||
<div class="empty-actions">
|
<div class="empty-actions">
|
||||||
<a class="btn btn-primary" href="${pageContext.request.contextPath}/posts/new">+ 글쓰기</a>
|
<a class="btn btn-primary" href="${pageContext.request.contextPath}/posts/new">+ 글쓰기</a>
|
||||||
</div>
|
</div>
|
||||||
|
<% } %>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
if (categoriesAttr instanceof List<?>) { categories = (List<PostCategoryData>) categoriesAttr; }
|
if (categoriesAttr instanceof List<?>) { categories = (List<PostCategoryData>) categoriesAttr; }
|
||||||
Long categoryId = (Long) request.getAttribute("categoryId");
|
Long categoryId = (Long) request.getAttribute("categoryId");
|
||||||
boolean hasNext = Boolean.TRUE.equals(request.getAttribute("hasNext"));
|
boolean hasNext = Boolean.TRUE.equals(request.getAttribute("hasNext"));
|
||||||
|
boolean canWrite = Boolean.TRUE.equals(request.getAttribute("canWrite"));
|
||||||
OffsetDateTime nextCursorCreatedAt = (OffsetDateTime) request.getAttribute("nextCursorCreatedAt");
|
OffsetDateTime nextCursorCreatedAt = (OffsetDateTime) request.getAttribute("nextCursorCreatedAt");
|
||||||
Long nextCursorId = (Long) request.getAttribute("nextCursorId");
|
Long nextCursorId = (Long) request.getAttribute("nextCursorId");
|
||||||
%>
|
%>
|
||||||
|
|
@ -227,7 +228,9 @@
|
||||||
<p class="posts-hero__eyebrow">POSTING</p>
|
<p class="posts-hero__eyebrow">POSTING</p>
|
||||||
<h1 id="posts-title">포스팅</h1>
|
<h1 id="posts-title">포스팅</h1>
|
||||||
</div>
|
</div>
|
||||||
|
<% if (canWrite) { %>
|
||||||
<a class="posts-write" href="<%= ctx %>/posts/new">글쓰기</a>
|
<a class="posts-write" href="<%= ctx %>/posts/new">글쓰기</a>
|
||||||
|
<% } %>
|
||||||
</section>
|
</section>
|
||||||
|
|
||||||
<nav class="posts-tabs" aria-label="카테고리">
|
<nav class="posts-tabs" aria-label="카테고리">
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue