Skip to content

Fix/#169 샘플 도서 예시 대목/의견 미표시 - #170

Merged
Byesol merged 1 commit into
developfrom
fix/#169-sample-opinions-missing
Sep 7, 2026
Merged

Byesol merged 1 commit into
developfrom
fix/#169-sample-opinions-missing

Conversation

@Byesol

@Byesol Byesol commented Sep 7, 2026

Copy link
Copy Markdown
Collaborator

📌 관련 이슈

🚀 개요

📄 작업 내용

  • OpinionGuestSampleSeedRunner: bookRepository.findById(18L) 대신 bookRepository.findByIsbn(SampleLibraryBook.ISBN)으로 샘플 book을 조회하도록 수정 (#166에서 도입한 ISBN 기준 조회 방식과 일치)
  • SampleLibraryBookSeeder(book row 생성)가 OpinionGuestSampleSeedRunner(대목/의견 생성)보다 먼저 실행되도록 두 ApplicationRunner@Order 지정 — 순서 보장 없이는 최초 기동 시 book이 아직 없어 씨딩이 건너뛰어질 수 있음
  • OpinionGuestSampleSeedRunnerTest 신규 작성: 샘플 book 존재 시 대목 2개/의견 2개 생성, book 없을 때 건너뜀, 재실행 시 중복 생성 안 함 검증

📸 스크린샷 / 테스트 결과 (선택)

  • ./gradlew test 통과 (관련 테스트: OpinionGuestSampleSeedRunnerTest 3건, SampleLibraryBookSeederTest 2건, BookServiceTest 27건 모두 통과)
  • 무관한 기존 실패: AladinBookApiClientCacheTest(로컬 Redis 미실행), NotificationRepositoryTest(동시각 정렬 플레이키) — 이번 변경과 무관, 단독 실행 시 통과 확인

✅ 체크리스트

  • 브랜치 전략(GitHub Flow)을 준수했나요?
  • 메서드 단위로 코드가 잘 쪼개져 있나요?
  • 테스트 통과 확인
  • 서버 실행 확인
  • API 동작 확인

🔍 리뷰 포인트 (Review Points)

  • 홈 카드에 표시되는 "대목 13개 / 의견 17개"는 실제 book row와 무관한 고정 표시값(BookService.SAMPLE_LIBRARY_BOOK_PASSAGE_COUNT/..._OPINION_COUNT)이라, 이번 수정 후에도 실제 상세 화면 개수(2/2)와는 다르게 보입니다. 이 고정값을 실제 씨딩 개수에 맞출지는 별도 논의가 필요할 것 같습니다.
  • ApplicationRunner 실행 순서를 @Order로 명시적으로 강제했습니다 — 기존에는 두 러너 모두 순서 미지정이라 최초 배포 시 우연에 의존하고 있었습니다.

🤖 Generated with Claude Code

https://claude.ai/code/session_01CGkkcMzuV3iWsbSRkqeuwv

Summary by CodeRabbit

  • 개선

    • 샘플 도서와 관련 의견 데이터가 정해진 순서에 따라 안정적으로 생성됩니다.
    • 도서를 ISBN 기준으로 조회하여 환경별 식별자 차이로 인한 오류를 줄였습니다.
    • 대상 도서가 없을 경우 안내 후 데이터 생성을 건너뜁니다.
    • 초기화 작업을 반복 실행해도 샘플 데이터가 중복 생성되지 않습니다.
  • 테스트

    • 샘플 데이터 생성, 대상 도서 부재, 중복 실행 상황을 검증하는 테스트를 추가했습니다.

@Byesol Byesol self-assigned this Sep 7, 2026
@coderabbitai

coderabbitai Bot commented Sep 7, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Team

Run ID: f2fd798a-12ee-4a97-9cac-3df27ba8cb3c

📥 Commits

Reviewing files that changed from the base of the PR and between 14bbbe1 and f93d010.

📒 Files selected for processing (3)
  • src/main/java/com/nexters/palang/domain/book/infrastructure/SampleLibraryBookSeeder.java
  • src/main/java/com/nexters/palang/domain/opinion/infrastructure/OpinionGuestSampleSeedRunner.java
  • src/test/java/com/nexters/palang/domain/opinion/infrastructure/OpinionGuestSampleSeedRunnerTest.java

Included review availability: Your plan provides up to 1 included review per hour; 0 remain after this review.


Walkthrough

샘플 도서 시더의 실행 순서를 지정했습니다. 의견 시더는 고정 도서 ID 대신 SampleLibraryBook.ISBN으로 도서를 조회합니다. 도서 존재 여부와 중복 실행에 대한 테스트를 추가했습니다.

Changes

샘플 데이터 시딩

Layer / File(s) Summary
샘플 시더 실행 순서
src/main/java/com/nexters/palang/domain/book/infrastructure/SampleLibraryBookSeeder.java, src/main/java/com/nexters/palang/domain/opinion/infrastructure/OpinionGuestSampleSeedRunner.java
SampleLibraryBookSeederORDER@Order를 추가했습니다. OpinionGuestSampleSeedRunner는 샘플 도서 시더 다음에 실행됩니다.
ISBN 기반 의견 시딩
src/main/java/com/nexters/palang/domain/opinion/infrastructure/OpinionGuestSampleSeedRunner.java, src/test/java/com/nexters/palang/domain/opinion/infrastructure/OpinionGuestSampleSeedRunnerTest.java
고정 PK 18L 조회를 SampleLibraryBook.ISBN 조회로 변경했습니다. 샘플 도서가 없으면 시딩하지 않습니다. 샘플 계정과 대목·의견 생성 및 재실행 시 중복 방지를 테스트합니다.

Estimated code review effort: 2 (Simple) | ~10 minutes

Merge Risk: ⚪ Minimal · up to f93d0

샘플 도서는 ISBN으로 안정적으로 조회되고 도서 생성 후 예시 대목과 의견이 시딩됩니다. 중복 생성도 방지되어 현재 변경은 병합 가능한 상태입니다.

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Docstring coverage is scoped to functions touched by this diff. Analyzed 6 functions across 3 files. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Linked Issues check ✅ Passed [169]의 모든 코딩 목표를 충족합니다. OpinionGuestSampleSeedRunnerSampleLibraryBook.ISBN으로 도서를 조회하고, SampleLibraryBookSeeder의 실행 순서를 보장합니다. 도서 존재 여부, 대목·의견 2개 생성, 중복 생성 방지 테스트도 추가했습니다. 홈 카드의 고정 표시값 차이는 별도 논…
Out of Scope Changes check ✅ Passed 변경 사항은 ISBN 기반 조회, 시딩 실행 순서 보장, 관련 테스트 추가로 제한됩니다. 모든 변경 사항이 [169]의 목표와 직접 관련됩니다.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed 제목은 샘플 도서의 예시 대목과 의견이 표시되지 않는 문제를 수정하는 주요 변경 사항을 정확하게 요약합니다.
  • Fix all pre-merge checks with AI
✨ Finishing Touches 💡 1
📝 Generate docstrings 💡
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/#169-sample-opinions-missing

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Byesol
Byesol merged commit 57bfed8 into develop Sep 7, 2026
2 checks passed
Byesol added a commit that referenced this pull request Sep 7, 2026
* 🧹 chore: 로컬 개발용 CORS 허용 origin에 localhost:3001~3010 추가

#95

* ✨ feat: 도서 내부 검색 - 흔적 있는 도서만 노출 + 이름순/최신순/의견순 정렬 추가

#97

* ✨ feat: 내 서재 보기 - 사용자가 흔적을 남긴 도서 캐러셀 API 추가

close #99

* 🐛 fix: 내 서재 가운데 offset을 홈 캐러셀 중간값 대신 0(최근순 시작)으로 수정

related to #99

* ♻️ refactor: 내 서재 API 경로를 /api/books/my-library로 이동, 가운데 정렬 문구 정리

related to #99

* 🐛 fix: 업로드 이미지 URL을 상대경로 대신 절대 URL로 반환

프론트(dev.pallang.co.kr)와 백엔드(api-dev.pallang.co.kr) 도메인이 달라
상대경로(/images/...)로 응답하면 프론트가 자기 자신의 origin으로 잘못 요청해
이미지가 404로 뜨는 문제를 수정. storage.public-base-url을 신설해 리소스
매핑 경로(storage.base-url)와 클라이언트 응답용 절대 URL을 분리했다.

* ♻️ refactor: 내 흔적 조회 결과에 지은이(author) 필드 추가

* ♻️ refactor: 내 흔적 목록 응답 DTO에 지은이(author) 매핑 추가

* ✅ test: 내 흔적 목록 API에 지은이(author) 필드 검증 추가

* ✨ feat: 내 서재 API에 opinionCountScope 추가, 페이지네이션을 page/size로 표준화

opinionCountScope(ALL/MINE) 파라미터로 마이페이지(내가 남긴 의견 기준)와
홈 화면(도서 전체 의견 기준) 노출 요구사항을 하나의 엔드포인트로 처리하고,
offset/size 캐러셀 방식이던 페이지네이션을 다른 목록 API와 동일한
page/size + PageInfo 방식으로 통일한다.

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

* ✨ feat: 내부 도서 검색에 제목 접두어 우선 정렬 추가

* ✨ feat: 알라딘 도서 검색 결과 Redis 캐싱 적용

* ✨ feat: 외부 도서 검색 최소 글자 수 가드 및 응답 타임아웃 단축

* 🐛 fix: 내 서재 조회 시 도서 전체 집계용 passage 조인을 필터용 조인과 분리

opinionMine의 innerJoin이 특정 passage 행에 걸려 있어, 그 join으로 인해
passage.countDistinct()/opinionAll이 "내가 흔적을 남긴 대목"으로만
제한되고 있었다. opinionMine/opinionAll을 passage.book으로 book에 직접
연결해 도서 전체 대목/흔적 집계가 실제로 도서 전체 기준이 되도록 고친다.

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

* 🐛 fix: 도서 내부 검색에서도 흔적 없는 대목이 passageCount 집계에서 누락되던 문제 수정

my-library에서 발견된 것과 동일한 패턴의 버그가 searchByTitle에도 있었다.
opinion을 특정 passage 행이 아닌 passage.book으로 book에 직접 연결해,
흔적이 없는 대목도 passageCount/opinionCount 집계에 포함되도록 고친다.

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

* ♻️ refactor: 도서 내부 검색 - 흔적 유무와 무관하게 전체 도서 반환

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

* 🐛 fix: 도서 제목 정규화 컬럼 백필 및 로케일 고정

* 🐛 fix: 알라딘 캐시 역직렬화 허용 타입 범위 제한

* 🐛 fix: 캐시 테스트가 Redis write 반영 지연으로 간헐 실패하는 문제 수정

* 🐛 fix: 백필 러너가 전체 도서를 한 번에 조회/트랜잭션 처리하지 않도록 배치화

* ✨ feat: 대목에 남은 흔적이 0개가 되면 대목도 함께 소프트 삭제 (PM 요구사항)

Passage에 deletedAt을 추가하고, OpinionService.removeOpinion에서 삭제
대상 흔적이 그 대목의 마지막 살아있는 흔적이었다면 대목도 함께 삭제한다.
Passage를 조회하는 모든 쿼리(도서 검색/캐러셀/내서재/인기도서, 유사 대목
후보, 페이지 번호/전환 조회)에 deletedAt IS NULL 필터를 추가해 삭제된
대목이 노출되지 않도록 한다.

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

* 🔧 chore: prod 환경도 ddl-auto: update로 변경

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

* ✨ feat: 최근 남긴 도서 목록 API에 keyword 검색 파라미터 추가

홈 화면 검색에서 최근 남긴 도서 중 제목으로 필터링할 수 있어야 해서,
GET /api/books/recent에 선택적 keyword 파라미터를 추가했다.

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

* ✨ feat: 도서 검색(GET /api/books/search)에 알라딘 결과와 DB 등록 도서를 통합 반환

- 알라딘 검색 결과와 서비스 DB에 등록된 도서(수동 등록 포함)를 함께 검색해 반환
- DB 매칭 도서는 흔적(Opinion) 많은 순으로 1페이지 상단에 노출하고 나머지를 알라딘 결과로 채움
- 같은 책이 알라딘/DB 양쪽에 모두 있으면(ISBN 동일) DB 쪽만 남기고 알라딘 쪽은 페이지와 무관하게 제외
- 응답을 기존 내부 검색용 BookSearchResponse/BookSearchListResponse로 통합하고, 더는 쓰지 않는 ExternalBookResponse/ExternalBookListResponse 삭제

* ✨ feat: BookActivityResponse publisher 추가 및 도서 단건 조회 API 신설

내 서재/인기/홈 캐러셀 목록에 출판사를 함께 내려주고, GET /api/books/{bookId}로
도서 메타와 로그인 사용자의 읽기상태(myStatus/myCurrentPage)를 한 번에 조회할 수 있게 한다.

* ✅ test: publisher 추가 및 도서 단건 조회 API 테스트 추가

* ♻️ refactor: 도서 검색이 DB/알라딘 결과를 하나로 이어붙인 목록처럼 페이지네이션하도록 개선

- BookService#searchBooks가 DB 도서(흔적 많은 순)와 알라딘 결과를 하나의 연속된 목록으로 취급해,
  요청받은 페이지 구간에 맞는 offset/limit을 DB/알라딘 양쪽에 정확히 나눠 조회
- DB 매칭이 페이지 크기보다 많아도 페이지를 넘기면 빠짐없이 노출되고, 같은 도서가 페이지마다
  중복 노출되던 문제 해소 (기존엔 DB 매칭을 1페이지에서만 보여줬음)
- totalElements도 근사치가 아니라 DB 전체 매칭 수 + 알라딘 전체 매칭 수로 정확하게 계산
- BookQueryRepository#searchByTitle에 임의 offset/limit을 받는 오버로드 추가 (findCarouselBooks와 동일한 패턴)
- AladinBookApiClient#search가 Pageable 대신 offset/maxResults를 직접 받도록 변경 (page*size로는
  표현할 수 없는 임의 offset이 필요해짐), 캐시 키도 keyword+offset+maxResults로 변경

* 📝 docs: 도서 검색 API 문서를 통합 페이지네이션 동작에 맞게 갱신

- '1페이지에만 DB 노출 / totalElements는 근사치'라는 이전 설명을 실제 동작(DB/알라딘을
  이어붙인 목록으로 정확히 페이지네이션, totalElements도 정확한 합산)에 맞게 수정

* 🐛 fix: 비로그인 도서 상세 응답에서 myStatus/myCurrentPage 필드를 제외

@JsonInclude(NON_NULL)로 null 대신 필드 자체를 응답에서 빼도록 바꾸고,
Swagger 설명과 컨트롤러 테스트를 실제 JSON 계약(필드 제외)에 맞춰 정정한다.

* 🐛 fix: 알라딘 검색이 start를 레코드 offset처럼 잘못 쓰던 문제 수정

- 알라딘 ItemSearch.aspx의 start 파라미터는 레코드 offset이 아니라 페이지 번호(MaxResults 단위로만
  나뉨)인데, 기존엔 offset+1을 그대로 start로 보내고 있어서 offset이 클수록(특히 페이지 크기의
  배수가 아닌 임의 offset일 때) 완전히 엉뚱한 위치의 도서를 조회하고 있었다
- AladinBookApiClient#search(keyword, offset, maxResults)를 없애고 searchAll(keyword)로 교체:
  키워드당 알라딘 결과 전체(최대 200건, 알라딘 한도)를 start=1,2...로 모아서 통으로 캐싱하고,
  실제 offset/limit 슬라이싱은 BookService가 그 리스트를 대상으로 로컬에서 처리
- 이 리팩터로 dedup 순서 문제도 함께 해결됨: 전체 리스트에 dedup을 먼저 적용한 뒤 skip/limit하므로,
  알라딘 배치 안에서 중복이 걸러져도 페이지가 짧아지지 않음
- AladinBookApiClient 관련 테스트를 searchAll 기준으로 재작성하고, 여러 페이지를 이어붙이는 케이스와
  200건 상한에서 멈추는 케이스 테스트 추가

* 📝 docs: 도서 검색 API 문서에 캐시 키/알라딘 200건 상한 반영

- 캐시 키가 keyword+offset+maxResults에서 keyword 하나로 바뀐 걸 문서에 반영
- 알라딘이 키워드당 최대 200건까지만 결과를 제공한다는 제약과 그로 인한 영향을 설명 추가

* ✨ feat: 내 스포일러 대목 목록 - GET /api/users/me/passages 신설

#118

흔적을 남긴 사용자 기준으로 소유자를 판정해, 병합된 대목도 흔적을 남긴
모든 사용자에게 노출되도록 구현. bookId 선택, spoilerOnly 필터 지원.

* 🐛 fix: 내 대목 목록의 createdAt/정렬 기준을 흔적 작성 시점으로 변경

#118

소유자 판정 기준이 "흔적 남긴 사용자"인데도 passage.createdAt(대목 최초
생성 시각)을 쓰고 있어, 병합된 대목에 내가 최근 흔적을 남겨도 옛날 날짜로
표시되고 최신순 정렬에서 밀리는 문제가 있었다. opinion.createdAt의
최댓값 기준으로 수정하고, 관련 리포지토리 테스트를 추가했다.

Swagger summary도 "내 스포일러 대목 목록" -> "내 대목 목록"으로 수정
(spoilerOnly 기본값 false일 때 실제 동작과 불일치 지적 반영).

* ✨ feat: 홈 도서 목록/내 의견 API 비로그인 샘플 응답 추가

- GET /api/books/my-library: 비로그인 시 샘플 도서 1건(bookId=18) 반환, 홈 캐러셀 API는 사용 중단 안내만 추가
- GET /api/users/me/opinions: 비로그인 시 고정 샘플 흔적 3건 반환

closes #119, closes #120

* ✨ feat: 내 흔적/좋아요 목록에 도서 필터 및 좋아요 작성자 닉네임 추가

GET /api/users/me/opinions, /likes에 bookId 필터를 추가하고,
좋아요 목록 응답에 흔적 작성자 식별용 nickname과 MyOpinionResponse와의
정합성을 위한 author를 추가한다. 좋아요/스포일러 관리 화면 공용
"전체 책 보기" 드롭다운을 위한 GET /api/users/me/filter-books를 신설한다.

Closes #123

* ✨ feat: 모임 도메인 엔티티 및 에러코드 추가

* ✨ feat: 모임 요청/응답 DTO 추가

* ✨ feat: 모임 Repository 추가

* ✨ feat: 모임 목록/멤버 조회용 QueryDSL Repository 추가

* ✨ feat: 모임 생성/조회/수정/삭제 서비스 추가

* ✨ feat: 모임 API 추가

* ✅ test: 모임 엔티티 단위 테스트 추가

* ✅ test: 모임 서비스 테스트 추가

* ✅ test: 모임 컨트롤러 테스트 추가

* ✅ test: 모임 QueryDSL Repository 테스트 추가

* ✨ feat: 대목 스포일러 설정 변경 API 추가

PATCH /api/passages/{passageId}/spoiler를 신설해 생성 시점에만
정해지던 isSpoiler를 이후에도 변경(해제/재설정)할 수 있게 한다.
권한은 findMyPassages와 동일하게 이 대목에 흔적을 남긴 사용자
기준이며, 소유자가 아니면 403(PASSAGE_403_1)을 반환한다.

Closes #128

* 📝 docs: 모임 기간 종료가 접근을 막지 않음을 명시

* 🐛 fix: 모임 목록 마지막 페이지 초과 조회 시 전체 건수 0으로 반환되는 문제 수정

* ✅ test: 모임 목록 빈 페이지 전체 건수 회귀 테스트 추가

* ✨ feat: 모임 초대 코드 재발급 기능 및 초대 관련 에러코드 추가

* ✨ feat: 초대 링크 조회/가입용 Repository 추가

* ✨ feat: 초대 링크 응답 DTO 추가

* ✨ feat: 초대 링크 발급/미리보기/가입 서비스 추가

* ✨ feat: 초대 링크 발급/미리보기/가입 API 추가

* ✅ test: 모임 초대 코드 재발급 단위 테스트 추가

* ✅ test: 초대 링크 발급/미리보기/가입 서비스 테스트 추가

* ✅ test: 초대 링크 발급/미리보기/가입 컨트롤러 테스트 추가

* 🐛 fix: 방 설정 변경과 초대 가입 간 정원 초과 race condition 수정

* ✅ test: 방 설정 변경 시 잠금 조회 사용 검증 테스트 추가

* ✨ feat: 대목에 모임 소속(group) 필드 및 관련 에러코드 추가

* ✨ feat: 모임원 검증 공통 컴포넌트 추가

* ✨ feat: 대목/의견 Repository에 모임 스코프 조회 추가

* ✨ feat: 대목/의견 조회·작성에 모임원 검증 로직 추가

* ✨ feat: 흔적/유사문장 요청에 groupId 파라미터 추가

* ✨ feat: 대목/흔적 API에 모임 스코프 groupId 파라미터 추가

* ✅ test: 대목 모임 스코프 서비스 테스트 추가

* ✅ test: 대목 Repository 모임 스코프 조회 테스트 추가

* ✅ test: 대목 컨트롤러 모임 스코프 테스트 추가

* ✅ test: 흔적 서비스 모임 스코프 테스트 추가

* ✅ test: 흔적 컨트롤러 모임 스코프 테스트 추가

* ✨ feat: 댓글 조회/작성에 모임원 검증 로직 추가

* ✨ feat: 좋아요 토글에 모임원 검증 로직 추가

* ✅ test: 댓글 모임 스코프 테스트 추가

* ✅ test: 좋아요 모임 스코프 테스트 추가

* ✨ feat: 읽기상태 완독 값 추가 및 해제 API 신설

* 🐛 fix: 모임 흔적 생성 시 존재하지 않는 groupId를 404로 우선 처리

* ✅ test: 존재하지 않는 groupId 404 처리 및 groupId 요청 바인딩 검증 추가

* 📝 docs: Group 관련 API Swagger 명세에 에러 응답 구체화

* ✨ feat: 마이페이지 후속 API 요청 4건 반영

- 내 대목 목록(MyPassageResponse)에 opinionId 추가
- 내 서재 수집 기준에 읽기 상태만 설정한 도서 포함
- ReadingStatus에서 미사용 PLANNED 제거
- 도서 필터 목록(filter-books)에 page/size 페이지네이션 추가

* ✨ feat: 비로그인 내 의견 미리보기 샘플 계정 실데이터로 전환

하드코딩된 샘플 3건 대신 실제 계정(GuestSampleAccount)의 실제 흔적을 반환하도록 바꿔 내가 남긴 의견 1건만 노출되게 하고, 상세 화면 꾸밈까지 자연스럽게 재현되도록 OpinionGuestSampleSeedRunner로 33p/70p 샘플을 씨딩한다.

Close #139

* Feat/#131 좋아요/댓글/책 신규 의견 알림 (인앱 저장 + FCM 푸시) (#136)

* ✨ feat: 좋아요/댓글/책 신규 의견 알림 인앱 저장 및 FCM 푸시 추가

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

* 🐛 fix: FCM 발송 실패가 알림 저장을 롤백시키지 않도록 분리

NotificationPushDispatcher.dispatch()가 NotificationCreationService.create()와
같은 트랜잭션에 묶여 있어, FCM 발송 중 예외가 나면 방금 저장한 Notification까지
롤백됐다. dispatch를 REQUIRES_NEW로 분리하고 create()에서 예외를 흡수한다.

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* 🐛 fix: Group 응답 매핑 시 발생하는 LazyInitializationException 수정

open-in-view: false 환경에서 GroupMapper가 컨트롤러 단(트랜잭션 종료 후)에서
Group.book/host(LAZY)를 읽어 500이 나던 문제를 join fetch로 해결한다.

* ✅ test: OpinionLikeServiceTest의 passage 누락으로 인한 NPE 수정

opinion(Long id, User owner) 헬퍼가 passage를 채우지 않아 toggleLike의
validateGroupAccess에서 opinion.getPassage()가 null이라 NPE가 나던 문제를
Passage를 함께 생성하도록 고쳐 해결한다.

* 🧹 chore: CI Docker 빌드에서 Gradle 이중 컴파일 제거

builder 스테이지 결과물을 --build-context로 미리 빌드된 jar로
치환해 컨테이너 안 재컴파일을 건너뛴다. Dockerfile 구조는
그대로라 cd.yml의 실제 배포 이미지 빌드는 영향받지 않는다.

related: #145

* ✨ feat: 모임 상세 조회 응답에 isHost 필드 추가

* ✨ feat: 모임 목록 조회 응답에도 isHost 필드 추가

* 🚀 deploy: Dev MySQL 컨테이너에 루프백 포트 바인딩 추가

* 🐛 fix: 샘플 데이터 의견 개수 수정

Removed one opinion from SAMPLE_OPINIONS_70.

* Feat/#153 신규 가입 계정 마이페이지/서재 샘플 데이터 노출 (#154)

* ✨ feat: 신규 가입 계정 마이페이지/서재에도 샘플 데이터 노출

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01864F4MmbxnihYG6S4MHafZ

* 🐛 fix: 내 서재 샘플 도서의 MINE 흔적 수/페이지 반복 노출 수정

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01864F4MmbxnihYG6S4MHafZ

---------

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

* Feat/#155 관리자 페이지 - 테스트 유저 데이터 삭제 기능 (#156)

* ✨ feat: 관리자 페이지 - 테스트 유저 데이터 삭제 기능 추가

prod에 쌓인 테스트/QA 계정을 SQL 없이 안전하게 정리할 수 있도록 관리자 API와
서버에 함께 배포되는 정적 관리자 페이지(/admin)를 추가한다.

- 인증: 기존 로그인 JWT + admin.user-ids 화이트리스트(AdminAccessGuard)
- GET /api/admin/users, DELETE /api/admin/users/{userId}
- 삭제는 하드 삭제이며, 다른 멤버가 있는 모임의 호스트이거나 다른 유저의 의견이
  달린 흔적을 작성한 경우 409로 차단한다(AdminUserService)
- FK 의존관계를 고려한 삭제 순서를 위해 여러 레포지토리에 deleteAllBy* 메서드 추가
- AppException에 동적 detail 메시지를 실을 수 있도록 확장(차단 사유 안내용)

이슈 #155

* 🐛 fix: 관리자 페이지 /admin, /admin/ 접속 시 500 에러 수정

정적 리소스 핸들러가 디렉터리 경로 요청을 index.html로 자동 연결해주지 않아
NoResourceFoundException이 GlobalExceptionHandler를 통해 500으로 응답되고 있었다.
/admin, /admin/ 요청을 /admin/index.html로 리다이렉트한다.

* Feat/#157 관리자 페이지 - 콘텐츠 관리 + 독립 로그인 (#158)

* ✨ feat: 관리자 페이지 - 대목/의견/댓글/모임/도서 조회·수정·삭제 기능 추가

유저 삭제만 가능했던 관리자 페이지(#155)를 대목/의견/댓글/모임/도서까지 확장한다.
대목/의견/댓글은 하드 삭제, 도서 삭제는 연관 모임/대목/의견/댓글까지 전부 cascade
삭제한다. 여러 서비스가 공유하는 cascade 로직은 AdminCascadeDeleter로 공용화했다.

* ✨ feat: 관리자 페이지 전용 독립 로그인(아이디/비밀번호) 추가

관리자 페이지 접근을 기존 유저 JWT + userId 화이트리스트에서, 일반 유저
계정과 완전히 분리된 아이디/비밀번호 로그인으로 바꾼다. admin.login-username/
password/jwt-secret이 설정되지 않으면 로그인이 항상 실패하도록(안전한 기본값)
설계했다. 발급된 토큰은 별도 비밀키로 서명해 유저 토큰과 서로 위조할 수 없다.

* 🐛 fix: 관리자 페이지 로그인 403 - CORS 허용 목록에 API 자기 자신 오리진 누락 (#160)

/admin이 API 서버 자기 자신(api-dev/api.pallang.co.kr)에서 서빙되는데,
cors.allowed-origins에 그 자신이 빠져 있어 브라우저가 보내는 Origin 헤더를
Spring Security의 CorsFilter가 "Invalid CORS request"로 막고 있었다.
curl은 Origin 헤더를 안 보내 재현되지 않아 이전 검증에서 놓쳤다.

* 🐛 fix: 관리자 페이지 검색어 없으면 목록이 안 보이는 문제 수정 (#162)

빈 검색어일 때 API 호출 자체를 막던 프론트 가드를 제거했다. 모든 관리자
검색 API가 Containing 쿼리라 빈 문자열도 전체 목록에 매치되므로 서버는
이미 지원하고 있었다. 탭을 열거나 로그인 직후에는 검색어 없이도 바로
목록이 보이도록 switchTab에서 자동 조회하게 했다.

* 🐛 fix: 관리자 페이지 대목/의견/댓글/모임 조회 500 오류 수정 (#164)

open-in-view: false인 dev/prod 환경에서 AdminXxxMapper가 컨트롤러 단(트랜잭션
밖)에서 book/creator/group/passage/user/host 등 지연 로딩 연관관계를 참조해
LazyInitializationException이 발생했다. 검색/단건 조회 쿼리에 EntityGraph로
필요한 연관관계만 미리 로딩하도록 수정.

Close #163

* 🐛 fix: 샘플 도서 404 - 하드코딩된 로컬 book id 대신 ISBN으로 조회 (#167)

close #166

* 🐛 fix: 샘플 도서 예시 대목/의견 미표시 - ISBN으로 book 조회하도록 수정 (#170)

Claude-Session: https://claude.ai/code/session_01CGkkcMzuV3iWsbSRkqeuwv

Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>

---------

Co-authored-by: jinnieusLab <kitty14904@naver.com>
Co-authored-by: Claude Sonnet 5 <noreply@anthropic.com>
Co-authored-by: jm <106303671+jinnieusLab@users.noreply.github.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.

fix: 샘플 도서 예시 대목/의견 미표시 - OpinionGuestSampleSeedRunner가 옛 하드코딩 bookId 참조

1 participant