-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 음식점 태그 조회 API 구현 #178
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Conversation
|
Caution Review failedThe pull request is closed. WalkthroughStore에 달린 태그 조회 기능을 추가. StoreController에 /api/shops/{storeId}/tags 및 /images GET 엔드포인트를 노출. StoreService에 getStoreTags 로직과 CheerTagRepository 의존성 추가, 리포지토리에 findAllByCheerStore 메서드 추가. TagsResponse DTO 신설. 관련 서비스/문서/컨트롤러 테스트 및 테스트 픽스처 추가. 불필요한 의존성 일부 제거. Changes
Sequence Diagram(s)sequenceDiagram
autonumber
participant C as Client
participant SC as StoreController
participant SS as StoreService
participant CTR as CheerTagRepository
participant DB as DB
C->>SC: GET /api/shops/{storeId}/tags
SC->>SS: getStoreTags(storeId)
SS->>SS: store 조회 (존재 확인)
SS->>CTR: findAllByCheerStore(store)
CTR->>DB: SELECT CheerTag WHERE cheer_store=?
DB-->>CTR: CheerTag 목록
CTR-->>SS: CheerTag 목록
SS->>SS: TagsResponse.from(...)
SS-->>SC: TagsResponse
SC-->>C: 200 OK (tags)
sequenceDiagram
autonumber
participant C as Client
participant SC as StoreController
participant SS as StoreService
C->>SC: GET /api/shops/{storeId}/images
SC->>SS: getStoreImages(storeId)
SS-->>SC: ImagesResponse
SC-->>C: 200 OK (images)
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Assessment against linked issues
Assessment against linked issues: Out-of-scope changes
Poem
Tip 🔌 Remote MCP (Model Context Protocol) integration is now available!Pro plan users can now connect to remote MCP servers from the Integrations page. Connect with popular remote MCPs such as Notion and Linear to add more context to your reviews and chats. 📜 Recent review detailsConfiguration used: .coderabbit.yaml 💡 Knowledge Base configuration:
You can enable these sources in your CodeRabbit configuration. 📒 Files selected for processing (7)
✨ Finishing Touches
🧪 Generate unit tests
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. 🪧 TipsChatThere are 3 ways to chat with CodeRabbit:
SupportNeed help? Create a ticket on our support page for assistance with any issues or questions. CodeRabbit Commands (Invoked using PR/Issue comments)Type Other keywords and placeholders
Status, Documentation and Community
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actionable comments posted: 0
🧹 Nitpick comments (11)
src/main/java/eatda/repository/cheer/CheerTagRepository.java (2)
10-10: 파라미터 이름이 혼동을 줄 수 있습니다 (Store 타입인데 storeId 명명).메서드 시그니처상 Store 엔티티를 받으므로, 변수명은
store가 적절합니다.아래와 같이 변수명만 정리해 주세요.
- List<CheerTag> findAllByCheerStore(Store storeId); + List<CheerTag> findAllByCheerStore(Store store);
10-10: DB 레벨 DISTINCT + 프로젝션으로 중복/페이로드 최소화 제안현재는 모든 CheerTag 엔티티를 로드한 뒤 응답 단계에서 distinct 처리합니다. 태그 수가 많을 경우 불필요한 엔티티 로딩과 메모리 사용이 발생할 수 있어, DB에서 곧바로 DISTINCT 이름 목록을 가져오는 프로젝션 메서드를 추가하는 방안을 권장합니다.
예시 (Repository에 보강):
// import 필요: // import eatda.domain.cheer.CheerTagName; // import org.springframework.data.jpa.repository.Query; // import org.springframework.data.repository.query.Param; @Query(""" select distinct ct.name from CheerTag ct join ct.cheer ch where ch.store = :store """) List<CheerTagName> findDistinctNamesByCheerStore(@Param("store") Store store);Service 변경 예시:
- List<CheerTag> cheerTags = cheerTagRepository.findAllByCheerStore(store); - return TagsResponse.from(cheerTags); + List<CheerTagName> tagNames = cheerTagRepository.findDistinctNamesByCheerStore(store); + return new TagsResponse(tagNames); // record의 canonical ctor 활용필요하시다면 관련 DTO/테스트까지 포함한 패치 제안 드리겠습니다.
src/test/java/eatda/controller/store/StoreControllerTest.java (1)
157-174: 태그 조회 컨트롤러 테스트 케이스 적절 (정상 시나리오). 추가 시나리오 확장을 제안합니다.현재 케이스는 단일 Cheer에 부착된 2개 태그의 반환을 검증합니다. 다음 보강을 고려해 주세요:
- 동일 가게의 여러 Cheer에 중복 태그가 존재할 때, 응답이 중복 없이 dedup됨을 검증
- 해당 가게에 태그가 하나도 없을 때 빈 리스트를 반환하는지 검증
추가 예시:
@Test void 음식점_태그가_없으면_빈_리스트를_반환한다() { Store store = storeGenerator.generate("상호명", "주소"); TagsResponse response = given() .when() .get("/api/shops/{storeId}/tags", store.getId()) .then() .statusCode(200) .extract().as(TagsResponse.class); assertThat(response.tags()).isEmpty(); } @Test void 여러_응원에_중복_태그가_있어도_중복없이_반환한다() { Member member = memberGenerator.generate("u1"); Store store = storeGenerator.generate("상호명", "주소"); Cheer cheer1 = cheerGenerator.generateCommon(member, store, "img-1"); Cheer cheer2 = cheerGenerator.generateCommon(member, store, "img-2"); cheerTagGenerator.generate(cheer1, List.of(CheerTagName.CLEAN_RESTROOM, CheerTagName.INSTAGRAMMABLE)); cheerTagGenerator.generate(cheer2, List.of(CheerTagName.CLEAN_RESTROOM)); TagsResponse response = given() .when() .get("/api/shops/{storeId}/tags", store.getId()) .then() .statusCode(200) .extract().as(TagsResponse.class); assertThat(response.tags()) .containsExactlyInAnyOrder(CheerTagName.CLEAN_RESTROOM, CheerTagName.INSTAGRAMMABLE); }사소한 사항: GET 요청에
contentType(JSON)지정은 불필요하므로 제거해도 무방합니다.src/main/java/eatda/service/store/StoreService.java (1)
58-63: 기능은 정합. 성능 최적화를 위해 DB에서 DISTINCT 태그명만 조회하는 방안을 고려해 주세요.현재는 모든 CheerTag 엔티티를 읽어와 앱 레이어에서 distinct 처리합니다. 태그/응원 데이터가 많을 때 비용이 커질 수 있습니다.
리포지토리에
findDistinctNamesByCheerStore(Store store)(DISTINCT name 프로젝션)를 추가하고, 아래처럼 단순화할 수 있습니다.- Store store = storeRepository.getById(storeId); - List<CheerTag> cheerTags = cheerTagRepository.findAllByCheerStore(store); - return TagsResponse.from(cheerTags); + Store store = storeRepository.getById(storeId); + List<CheerTagName> tagNames = cheerTagRepository.findDistinctNamesByCheerStore(store); + return new TagsResponse(tagNames);추가적으로, 읽기 전용 메서드와 동일한 일관성을 위해
getStoreImages에도@Transactional(readOnly = true)부여를 고려할 수 있습니다. 필수는 아니며 선택 사항입니다.src/test/java/eatda/service/store/StoreServiceTest.java (1)
161-178: 교차 가게 격리 검증 테스트 추가 제안현재 테스트는 하나의 가게에 한정되어 있어, 다른 가게의 태그가 섞여 들어오지 않는지(리포지토리 where 조건)도 함께 보장하면 좋습니다.
아래와 같은 테스트 추가를 고려해 주세요:
@Test void 다른_가게_태그는_포함되지_않는다() { Member member = memberGenerator.generate("111", "[email protected]", "nickname1"); Store target = storeGenerator.generate("농민백암순대", "서울 강남구 대치동 896-33"); Store other = storeGenerator.generate("석관동떡볶이", "서울 성북구 석관동 123-45"); Cheer cheerInTarget = cheerGenerator.generateCommon(member, target); Cheer cheerInOther = cheerGenerator.generateCommon(member, other); cheerTagGenerator.generate(cheerInTarget, List.of(CheerTagName.INSTAGRAMMABLE)); cheerTagGenerator.generate(cheerInOther, List.of(CheerTagName.ENERGETIC)); TagsResponse response = storeService.getStoreTags(target.getId()); assertThat(response.tags()).containsExactly(CheerTagName.INSTAGRAMMABLE); }src/main/java/eatda/controller/store/TagsResponse.java (1)
3-13: 클라이언트 일관성을 위해 정렬 추가 제안현재 distinct 이후의 순서는 데이터 조회 순서에 의존합니다(쿼리 정렬 미보장 시 비결정적일 수 있음). 응답의 안정성을 위해 enum 이름 기준 정렬을 권장합니다.
다음과 같이 정렬을 포함하는 변경을 제안합니다:
package eatda.controller.store; import eatda.domain.cheer.CheerTag; import eatda.domain.cheer.CheerTagName; import java.util.List; +import java.util.Comparator; public record TagsResponse(List<CheerTagName> tags) { public static TagsResponse from(List<CheerTag> cheerTags) { List<CheerTagName> cheerTagNames = cheerTags.stream() .map(CheerTag::getName) .distinct() + .sorted(Comparator.comparing(Enum::name)) .toList(); return new TagsResponse(cheerTagNames); } }src/main/java/eatda/controller/store/StoreController.java (2)
45-49: 코드 스타일 일관성: 인라인 반환으로 통일 제안바로 위 이미지 엔드포인트처럼 인라인 반환으로 통일하면 가독성이 좋아집니다.
아래처럼 단순화할 수 있습니다:
@GetMapping("/api/shops/{storeId}/tags") public ResponseEntity<TagsResponse> getStoreTags(@PathVariable long storeId) { - TagsResponse response = storeService.getStoreTags(storeId); - return ResponseEntity.ok(response); + return ResponseEntity.ok(storeService.getStoreTags(storeId)); }
40-49: 캐싱 고려(옵션): 읽기 많은 엔드포인트는 Cache-Control 혹은 서버 캐시 적용 검토태그/이미지 조회는 변경 빈도 대비 조회가 많은 성격입니다. 짧은 TTL의 HTTP 캐시 헤더나 서버측 캐시(e.g., @Cacheable) 적용을 고려하면 비용을 줄일 수 있습니다.
src/test/java/eatda/fixture/CheerTagGenerator.java (1)
19-23: 배치 저장으로 왕복 최소화 제안(saveAll 활용)여러 태그를 개별 save로 저장하는 대신 saveAll로 한번에 처리하면 테스트 성능과 간결함이 개선됩니다.
다음과 같이 변경을 제안합니다:
public List<CheerTag> generate(Cheer cheer, List<CheerTagName> tagNames) { - return tagNames.stream() - .map(name -> cheerTagRepository.save(new CheerTag(cheer, name))) - .toList(); + return cheerTagRepository.saveAll( + tagNames.stream() + .map(name -> new CheerTag(cheer, name)) + .toList() + ); }추가로 가독성을 위해 가변 인자 오버로드도 고려해 볼 수 있습니다(선택):
public List<CheerTag> generate(Cheer cheer, CheerTagName... tagNames) { return generate(cheer, List.of(tagNames)); }src/test/java/eatda/document/store/StoreDocumentTest.java (2)
233-244: 문서 스니펫 구성 적합 — 응답 필드 최소 계약을 명확화tags 배열만 노출하는 단순 계약을 잘 반영했습니다. enum 값이라는 점을 설명에 명시하면 독자가 이해하기 더 쉽습니다.
아래처럼 설명을 보강하는 것을 제안합니다:
RestDocsResponse responseDocument = response() .responseBodyField( - fieldWithPath("tags").type(ARRAY).description("음식점 태그 목록") + fieldWithPath("tags").type(ARRAY).description("음식점 태그 목록 (CheerTagName enum 값 목록)") );
256-259: path 변수 바인딩 방식 일관화 제안다른 테스트(GetStoreImages 등)와 동일하게 pathParam을 사용하는 스타일로 통일하면 가독성이 좋아집니다(기능 차이는 없음).
다음과 같이 변경할 수 있습니다:
- given(document) - .contentType(ContentType.JSON) - .when().get("/api/shops/{storeId}/tags", storeId) - .then().statusCode(200); + given(document) + .contentType(ContentType.JSON) + .pathParam("storeId", storeId) + .when().get("/api/shops/{storeId}/tags") + .then().statusCode(200);
📜 Review details
Configuration used: .coderabbit.yaml
Review profile: CHILL
Plan: Pro
💡 Knowledge Base configuration:
- MCP integration is disabled by default for public repositories
- Jira integration is disabled by default for public repositories
- Linear integration is disabled by default for public repositories
You can enable these sources in your CodeRabbit configuration.
📒 Files selected for processing (11)
src/main/java/eatda/controller/store/StoreController.java(2 hunks)src/main/java/eatda/controller/store/TagsResponse.java(1 hunks)src/main/java/eatda/repository/cheer/CheerTagRepository.java(1 hunks)src/main/java/eatda/service/cheer/CheerService.java(0 hunks)src/main/java/eatda/service/store/StoreService.java(3 hunks)src/test/java/eatda/controller/BaseControllerTest.java(2 hunks)src/test/java/eatda/controller/store/StoreControllerTest.java(2 hunks)src/test/java/eatda/document/store/StoreDocumentTest.java(2 hunks)src/test/java/eatda/fixture/CheerTagGenerator.java(1 hunks)src/test/java/eatda/service/BaseServiceTest.java(2 hunks)src/test/java/eatda/service/store/StoreServiceTest.java(3 hunks)
💤 Files with no reviewable changes (1)
- src/main/java/eatda/service/cheer/CheerService.java
🧰 Additional context used
🧬 Code Graph Analysis (3)
src/test/java/eatda/service/store/StoreServiceTest.java (1)
src/test/java/eatda/document/store/StoreDocumentTest.java (6)
Nested(41-98)Nested(100-171)Nested(173-228)Nested(230-279)Nested(281-339)Nested(341-405)
src/test/java/eatda/controller/store/StoreControllerTest.java (1)
src/test/java/eatda/document/store/StoreDocumentTest.java (6)
Nested(41-98)Nested(100-171)Nested(173-228)Nested(230-279)Nested(281-339)Nested(341-405)
src/test/java/eatda/document/store/StoreDocumentTest.java (3)
src/test/java/eatda/document/RestDocsRequest.java (1)
RestDocsRequest(22-90)src/test/java/eatda/document/RestDocsResponse.java (1)
RestDocsResponse(14-40)src/test/java/eatda/document/cheer/CheerDocumentTest.java (3)
Nested(41-136)Nested(138-207)Nested(209-278)
🔇 Additional comments (14)
src/test/java/eatda/controller/BaseControllerTest.java (1)
17-17: CheerTagGenerator 주입 추가 좋습니다.태그 데이터 셋업이 필요한 컨트롤러 테스트에서 재사용성이 좋아졌습니다. 기존 제너레이터들과의 일관성도 유지됩니다.
Also applies to: 64-66
src/test/java/eatda/service/BaseServiceTest.java (1)
11-11: 서비스 테스트에 CheerTagGenerator 주입 추가 LGTM태그 기반 시나리오를 서비스 테스트에서도 간단히 구성할 수 있게 되어 테스트 가독성과 유지보수성이 좋아집니다.
Also applies to: 61-63
src/main/java/eatda/repository/cheer/CheerTagRepository.java (1)
4-5: import 보강 적절Store/Collection import 추가는 새 메서드 구현에 필요한 최소 변경으로 적절합니다.
src/test/java/eatda/service/store/StoreServiceTest.java (4)
164-178: 중복 제거 로직을 제대로 검증하는 좋은 테스트입니다두 개의 응원에서 중복 태그를 생성하고, 응답의 태그가 중복 없이 포함되는지 검증한 점이 핵심 요구사항과 잘 맞습니다.
180-191: 태그가 하나도 없을 때 빈 리스트 반환 확인 — 케이스 커버리지 적절응원은 존재하나 태그가 없을 때 빈 배열을 반환하는 경로를 명확히 보장합니다.
194-201: 존재하지 않는 음식점에 대한 예외 흐름 검증 OKSTORE_NOT_FOUND 예외 코드까지 검증해 API 계약을 잘 고정합니다.
169-170: 확인:generateCommon(member, store)오버로드 존재
CheerGenerator클래스에 이미지 키 없이 호출 가능한generateCommon(Member, Store)메서드가 정의되어 있으며, 내부적으로DEFAULT_IMAGE_KEY를 사용해generateCommon(Member, Store, String)를 호출합니다. 해당 테스트는 의도한 대로 동작하므로 추가 수정은 필요 없습니다.src/main/java/eatda/controller/store/TagsResponse.java (1)
7-16: DTO 형태와 변환 팩토리 메서드 설계가 간결하고 명확합니다CheerTag → CheerTagName 매핑과 distinct 처리로 요구사항을 잘 반영하고 있습니다.
src/main/java/eatda/controller/store/StoreController.java (2)
26-30: 단건 조회 엔드포인트 유지보수성 양호서비스 위임과 응답 래핑이 간단명료합니다.
40-44: 이미지 조회 엔드포인트 추가 LGTM서비스 계층에 책임을 위임하고 있고, 컨트롤러는 얇게 유지되었습니다.
src/test/java/eatda/fixture/CheerTagGenerator.java (1)
10-18: 테스트 픽스처 구성 적절리포지토리 주입으로 테스트 데이터 생성을 일관되게 처리할 수 있어 유지보수에 유리합니다.
src/test/java/eatda/document/store/StoreDocumentTest.java (3)
20-20: 문서용 DTO(TagsResponse) import 추가 적절새 응답 스키마를 문서화하기 위한 의존성 정리가 잘 되었습니다.
25-25: CheerTagName enum import 적절응답 예시/스텁 구성에 필요한 열거형 사용을 명확히 했습니다.
262-279: 예외 문서화 흐름도 일관성 있게 잘 추가되었습니다STORE_NOT_FOUND 케이스를 명확히 커버하며, 실패 문서는 성공 문서와 동일한 리소스 네임 스페이스를 사용하고 있어 탐색성도 좋습니다.
lvalentine6
left a comment
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이번 PR도 고생하셨습니다! 🍏
LGTM!
# Conflicts: # src/main/java/eatda/service/cheer/CheerService.java # src/main/java/eatda/service/store/StoreService.java # src/test/java/eatda/controller/BaseControllerTest.java # src/test/java/eatda/controller/store/StoreControllerTest.java # src/test/java/eatda/service/BaseServiceTest.java # src/test/java/eatda/service/store/StoreServiceTest.java
|
🎉 This PR is included in version 1.4.0-develop.92 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
|
|
🎉 This PR is included in version 1.8.0-develop.1 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |
|
🎉 This PR is included in version 1.8.0 🎉 The release is available on GitHub release Your semantic-release bot 📦🚀 |



✨ 개요
🧾 관련 이슈
closed #174
🔍 참고 사항 (선택)
Summary by CodeRabbit