Skip to content

[TMT-223] feat: 리뷰 공유 집합 실구현 — GET·PUT 전체 교체·파생 집계 반영 - #82

Open
wnsvy607 wants to merge 5 commits into
feat/TMT-222from
feat/TMT-223
Open

[TMT-223] feat: 리뷰 공유 집합 실구현 — GET·PUT 전체 교체·파생 집계 반영#82
wnsvy607 wants to merge 5 commits into
feat/TMT-222from
feat/TMT-223

Conversation

@wnsvy607

Copy link
Copy Markdown
Contributor

TMT-223 — 리뷰 공유 집합 실구현 (그룹 레인 마지막)

스택 PR — base가 feat/TMT-222(#81)입니다. 머지 순서: #79#80#81 → 이 PR.

GET·PUT /v1/groups/{groupId}/review-shares를 실 DB로 옮깁니다. 응답 형태는 mock과 같고, 리뷰 ID 표기만 실구현 표준(rv_)입니다 — mock은 review_ 접두였는데 근처·상세 실구현이 이미 rv_라 카드와 일관되게 맞췄습니다 (계약 문서 §4의 ID 표기 결정 그대로).

설계

  • PUT은 집합 교체 (H §3-2) — NOT IN 삭제 + ON CONFLICT DO NOTHING 삽입. 양쪽 다 멱등이라 재시도가 안전하고, Idempotency-Key가 필요 없는 이유가 코드에 그대로 드러납니다
  • 교체와 집계가 한 트랜잭션 (TX-4) — GroupStatsPort.refreshShareStatsgroup_place를 재구축하고 review_count·place_count를 다시 셉니다. 같은 매장에 공유가 겹쳐도(D3) 행 수와 지표가 어긋나지 않습니다
  • 내 리뷰 검증은 unnest(bigint[]) 한 방 — 남의 리뷰·없는 리뷰가 섞이면 그 id로 REVIEW_NOT_FOUND
  • GroupReviewSharePortshare(가입 자동 공유)·unshareAllByUser(탈퇴 일괄 해제)는 TMT-224에서 이미 구현돼 있어 replaceUserShares만 추가했습니다

검증 — 로컬 E2E (승인 기준 전부)

  • GET: 내 리뷰 3건 + isShared·첫 사진 썸네일·sharedCount
  • PUT [rv_1,rv_3] → 지표 (2,1)·group_place{p1:2} / PUT [rv_2] → rv_1·rv_3 해제, 지표 (1,1) / PUT [] → 전부 해제, 지표 (0,0)
  • 남의 리뷰 섞으면 404 + 집합 불변 (검증이 교체보다 먼저)
  • 미가입 PUT → 403 GROUP_MEMBERSHIP_REQUIRED
  • share_uq 멱등(중복 공유 no-op)·사용자 일괄 해제 동작

./gradlew build 통과 (신규 컨트롤러 테스트 5개). mock review-shares 핸들러 2개·테스트 3개 제거.

과도기 주의 (스택 4장 공통)

  • DB groups가 0행이라 머지 후 목록·상세가 빕니다 — TMT-231(시드 SQL)이 후속 필수
  • mock으로 남은 가입·탈퇴(GroupMembershipMockController)는 mock 인메모리 멤버십을 쓰므로, mock 가입으로는 실구현 PUT의 멤버십 검증(403)을 못 통과합니다. 가입·탈퇴 실구현 티켓이 다음 순서여야 합니다

🤖 Generated with Claude Code

PUT은 집합 교체다 — NOT IN 삭제 + ON CONFLICT DO NOTHING 삽입이라 양쪽 다 멱등. 교체와 GroupStatsPort.refreshShareStats(group_place 재구축·지표 재계산)가 한 트랜잭션이다(TX-4). 내 리뷰 검증은 unnest 배열로 한 번에, 미가입은 GROUP_MEMBERSHIP_REQUIRED(403). GroupReviewSharePort의 share/unshareAllByUser는 TMT-224 기구현이라 replaceUserShares만 추가. mock의 review-shares 핸들러 2개 제거.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>

@mingdodev mingdodev left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[must] 두 건을 라인에 남겼습니다.

groupReviewSharePort.replaceUserShares(groupId, userId, distinct)
groupStatsPort.refreshShareStats(groupId)

val shared = groupShareQueryPort.findSharedReviewIds(groupId, userId)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[must] replace가 돌려주는 sharedCount/sharedReviewIdsfindSharedReviewIds(...) 결과인데, 이 조회에는 review.deleted_at IS NULL 필터가 없습니다.

GET 경로의 countSharedByUser(GroupShareQueryRepository.kt:49)는 필터하고 있어서, 소프트 삭제된 리뷰가 공유에 남아 있으면 PUT 응답과 직후 GET의 값이 어긋납니다. 화면이 PUT 응답으로 카운트를 갱신하면 새로고침에서 숫자가 바뀌는 형태로 보일 것 같습니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영했습니다 (d2a34b6) — findSharedReviewIdsJOIN review ... deleted_at IS NULL을 넣어 countSharedByUser(GET)와 같은 기준으로 맞췄습니다. PUT 응답과 직후 GET이 항상 같은 값을 냅니다.

private val groupReviewQueryPort: GroupReviewQueryPort,
private val groupReviewSharePort: GroupReviewSharePort,
private val groupStatsPort: GroupStatsPort,
@param:Value("\${tmt.media.base-url:}") private val mediaBaseUrl: String,

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[must] 빈 기본값 때문에 프로퍼티가 없으면 thumbnailUrl/{s3Key} 상대경로로 조용히 나갑니다.

TMT-221의 GroupDetailComposer와 같은 형태라 두 곳을 같은 방식으로 정하면 좋겠습니다 — 기동 시 실패 쪽을 제안드립니다.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

반영했습니다 (d2a34b6) — #80에서 신설한 MediaUrlResolver(빈 값이면 기동 실패)로 교체해 두 곳이 같은 방식입니다.

wnsvy607 and others added 2 commits September 2, 2026 01:59
- findSharedReviewIds에 review.deleted_at IS NULL JOIN — countSharedByUser(GET)와 같은 기준으로 PUT/GET 정합
- GroupShareService의 base-url 직접 조립을 MediaUrlResolver(기동 검증)로 교체 — TMT-221과 동일 방식

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@wnsvy607
wnsvy607 requested a review from mingdodev September 1, 2026 17:06
wnsvy607 and others added 2 commits September 2, 2026 10:17
- MediaAttachmentService도 MediaUrlResolver 주입 — tmt.media.base-url을 읽는 곳이 하나가 된다
- 실구현 그룹 컨트롤러 5곳의 Swagger 태그에서 (mock) 제거
- parseGroupId를 PublicIds로 승격 — 접두 표기 규칙이 한 파일에 모인다
- GroupRegionTagRepository의 clearAutomatically 제거 (flush만 필요), @EmbeddedId merge 선행 SELECT는 주석으로 감수 명시

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants