From 48086b822c4768cd183776acb0364cf1022c864a Mon Sep 17 00:00:00 2001 From: leegwichan Date: Tue, 12 Aug 2025 11:46:08 +0900 Subject: [PATCH 1/3] =?UTF-8?q?feat:=20API=20=EC=9A=94=EC=B2=AD/=EC=9D=91?= =?UTF-8?q?=EB=8B=B5=EC=97=90=20=ED=83=9C=EA=B7=B8=20=EC=A0=95=EB=B3=B4=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/cheer/CheerRegisterRequest.java | 15 ++++++++++++++- .../eatda/controller/cheer/CheerResponse.java | 8 ++++++-- .../repository/cheer/CheerTagRepository.java | 8 ++++++++ .../eatda/controller/BaseControllerTest.java | 4 ++++ .../controller/cheer/CheerControllerTest.java | 8 ++++++-- .../eatda/document/store/CheerDocumentTest.java | 16 +++++++++++----- .../java/eatda/service/BaseServiceTest.java | 4 ++++ .../eatda/service/cheer/CheerServiceTest.java | 17 ++++++++++++----- 8 files changed, 65 insertions(+), 15 deletions(-) create mode 100644 src/main/java/eatda/repository/cheer/CheerTagRepository.java diff --git a/src/main/java/eatda/controller/cheer/CheerRegisterRequest.java b/src/main/java/eatda/controller/cheer/CheerRegisterRequest.java index c7f67a80..ce8fc410 100644 --- a/src/main/java/eatda/controller/cheer/CheerRegisterRequest.java +++ b/src/main/java/eatda/controller/cheer/CheerRegisterRequest.java @@ -1,8 +1,21 @@ package eatda.controller.cheer; +import eatda.domain.cheer.CheerTagName; +import java.util.Collections; +import java.util.List; + public record CheerRegisterRequest( String storeKakaoId, String storeName, - String description + String description, + List tags ) { + + @Override + public List tags() { // TODO : 클라이언트 태그 구현 완료 시 삭제 + if (tags == null) { + return Collections.emptyList(); + } + return tags; + } } diff --git a/src/main/java/eatda/controller/cheer/CheerResponse.java b/src/main/java/eatda/controller/cheer/CheerResponse.java index ea02d843..0611e697 100644 --- a/src/main/java/eatda/controller/cheer/CheerResponse.java +++ b/src/main/java/eatda/controller/cheer/CheerResponse.java @@ -1,13 +1,16 @@ package eatda.controller.cheer; import eatda.domain.cheer.Cheer; +import eatda.domain.cheer.CheerTagName; import eatda.domain.store.Store; +import java.util.List; public record CheerResponse( long storeId, long cheerId, String imageUrl, - String cheerDescription + String cheerDescription, + List tags ) { public CheerResponse(Cheer cheer, String imageUrl, Store store) { @@ -15,7 +18,8 @@ public CheerResponse(Cheer cheer, String imageUrl, Store store) { store.getId(), cheer.getId(), imageUrl, - cheer.getDescription() + cheer.getDescription(), + List.of() // TODO tags 불러오기 ); } } diff --git a/src/main/java/eatda/repository/cheer/CheerTagRepository.java b/src/main/java/eatda/repository/cheer/CheerTagRepository.java new file mode 100644 index 00000000..3f52b3de --- /dev/null +++ b/src/main/java/eatda/repository/cheer/CheerTagRepository.java @@ -0,0 +1,8 @@ +package eatda.repository.cheer; + +import eatda.domain.cheer.CheerTag; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CheerTagRepository extends JpaRepository { + +} diff --git a/src/test/java/eatda/controller/BaseControllerTest.java b/src/test/java/eatda/controller/BaseControllerTest.java index 38cf7d97..1b60bc2a 100644 --- a/src/test/java/eatda/controller/BaseControllerTest.java +++ b/src/test/java/eatda/controller/BaseControllerTest.java @@ -20,6 +20,7 @@ import eatda.fixture.StoryGenerator; import eatda.repository.article.ArticleRepository; import eatda.repository.cheer.CheerRepository; +import eatda.repository.cheer.CheerTagRepository; import eatda.repository.member.MemberRepository; import eatda.repository.store.StoreRepository; import eatda.repository.story.StoryRepository; @@ -77,6 +78,9 @@ public class BaseControllerTest { @Autowired protected CheerRepository cheerRepository; + @Autowired + protected CheerTagRepository cheerTagRepository; + @Autowired protected ArticleRepository articleRepository; diff --git a/src/test/java/eatda/controller/cheer/CheerControllerTest.java b/src/test/java/eatda/controller/cheer/CheerControllerTest.java index 2d2c5300..15f29f04 100644 --- a/src/test/java/eatda/controller/cheer/CheerControllerTest.java +++ b/src/test/java/eatda/controller/cheer/CheerControllerTest.java @@ -5,11 +5,13 @@ import eatda.controller.BaseControllerTest; import eatda.domain.cheer.Cheer; +import eatda.domain.cheer.CheerTagName; import eatda.domain.member.Member; import eatda.domain.store.Store; import eatda.util.ImageUtils; import eatda.util.MappingUtils; import java.time.LocalDateTime; +import java.util.List; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.http.HttpHeaders; @@ -22,7 +24,8 @@ class RegisterCheer { @Test void 응원을_등록한다() { Store store = storeGenerator.generate("123", "서울시 노원구 월계3동 123-45"); - CheerRegisterRequest request = new CheerRegisterRequest(store.getKakaoId(), store.getName(), "맛있어요!"); + CheerRegisterRequest request = new CheerRegisterRequest(store.getKakaoId(), store.getName(), "맛있어요!", + List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM)); CheerResponse response = given() .header(HttpHeaders.AUTHORIZATION, accessToken()) @@ -41,7 +44,8 @@ class RegisterCheer { @Test void 이미지가_비어있을_경우에도_응원을_등록한다() { Store store = storeGenerator.generate("123", "서울시 노원구 월계3동 123-45"); - CheerRegisterRequest request = new CheerRegisterRequest(store.getKakaoId(), store.getName(), "맛있어요!"); + CheerRegisterRequest request = new CheerRegisterRequest(store.getKakaoId(), store.getName(), "맛있어요!", + List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM)); CheerResponse response = given() .header(HttpHeaders.AUTHORIZATION, accessToken()) diff --git a/src/test/java/eatda/document/store/CheerDocumentTest.java b/src/test/java/eatda/document/store/CheerDocumentTest.java index 5e3e09f4..56ec5e99 100644 --- a/src/test/java/eatda/document/store/CheerDocumentTest.java +++ b/src/test/java/eatda/document/store/CheerDocumentTest.java @@ -23,6 +23,7 @@ import eatda.document.RestDocsRequest; import eatda.document.RestDocsResponse; import eatda.document.Tag; +import eatda.domain.cheer.CheerTagName; import eatda.exception.BusinessErrorCode; import eatda.exception.BusinessException; import eatda.util.ImageUtils; @@ -67,7 +68,8 @@ class RegisterCheer { ).requestBodyField("request", fieldWithPath("storeKakaoId").type(STRING).description("가게 카카오 ID"), fieldWithPath("storeName").type(STRING).description("가게 이름"), - fieldWithPath("description").type(STRING).description("응원 내용") + fieldWithPath("description").type(STRING).description("응원 내용"), + fieldWithPath("tags").type(ARRAY).description("응원 태그 목록") ); RestDocsResponse responseDocument = response() @@ -75,13 +77,16 @@ class RegisterCheer { fieldWithPath("storeId").type(NUMBER).description("가게 ID"), fieldWithPath("cheerId").type(NUMBER).description("응원 ID"), fieldWithPath("imageUrl").type(STRING).description("이미지 URL").optional(), - fieldWithPath("cheerDescription").type(STRING).description("응원 내용") + fieldWithPath("cheerDescription").type(STRING).description("응원 내용"), + fieldWithPath("tags").type(ARRAY).description("응원 태그 목록") ); @Test void 응원_등록_성공() { - CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "너무 맛있어요!"); - CheerResponse response = new CheerResponse(1L, 1L, "https://example.img", "너무 맛있어요!"); + CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "너무 맛있어요!", + List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM)); + CheerResponse response = new CheerResponse(1L, 1L, "https://example.img", "너무 맛있어요!", + List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM)); doReturn(response).when(cheerService).registerCheer(eq(request), any(), any(), anyLong()); var document = document("cheer/register", 201) @@ -110,7 +115,8 @@ class RegisterCheer { "INVALID_CHEER_DESCRIPTION"}) @ParameterizedTest void 응원_등록_실패(BusinessErrorCode errorCode) { - CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "너무 맛있어요!"); + CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "너무 맛있어요!", + List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM)); doThrow(new BusinessException(errorCode)) .when(cheerService).registerCheer(eq(request), any(), any(), anyLong()); diff --git a/src/test/java/eatda/service/BaseServiceTest.java b/src/test/java/eatda/service/BaseServiceTest.java index 9f9832bb..f624785e 100644 --- a/src/test/java/eatda/service/BaseServiceTest.java +++ b/src/test/java/eatda/service/BaseServiceTest.java @@ -12,6 +12,7 @@ import eatda.fixture.MemberGenerator; import eatda.fixture.StoreGenerator; import eatda.repository.cheer.CheerRepository; +import eatda.repository.cheer.CheerTagRepository; import eatda.repository.member.MemberRepository; import eatda.repository.store.StoreRepository; import eatda.repository.story.StoryRepository; @@ -71,6 +72,9 @@ public abstract class BaseServiceTest { @Autowired protected CheerRepository cheerRepository; + @Autowired + protected CheerTagRepository cheerTagRepository; + @Autowired protected StoryRepository storyRepository; diff --git a/src/test/java/eatda/service/cheer/CheerServiceTest.java b/src/test/java/eatda/service/cheer/CheerServiceTest.java index 057f9e02..d0dac9f9 100644 --- a/src/test/java/eatda/service/cheer/CheerServiceTest.java +++ b/src/test/java/eatda/service/cheer/CheerServiceTest.java @@ -10,6 +10,7 @@ import eatda.controller.cheer.CheersResponse; import eatda.domain.ImageKey; import eatda.domain.cheer.Cheer; +import eatda.domain.cheer.CheerTagName; import eatda.domain.member.Member; import eatda.domain.store.Store; import eatda.domain.store.StoreCategory; @@ -18,6 +19,7 @@ import eatda.exception.BusinessException; import eatda.service.BaseServiceTest; import java.time.LocalDateTime; +import java.util.List; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; @@ -40,7 +42,8 @@ class RegisterCheer { cheerGenerator.generateCommon(member, store2); cheerGenerator.generateCommon(member, store3); - CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "추가 응원"); + CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "추가 응원", + List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM)); StoreSearchResult result = new StoreSearchResult( "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", 37.5665, 126.9780); @@ -58,7 +61,8 @@ class RegisterCheer { Store store = storeGenerator.generate("123", "서울시 강남구 역삼동 123-45"); cheerGenerator.generateCommon(member, store); - CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "추가 응원"); + CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "추가 응원", + List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM)); StoreSearchResult result = new StoreSearchResult( "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", 37.5665, 126.9780); @@ -74,7 +78,8 @@ class RegisterCheer { void 해당_응원의_가게가_저장되어_있지_않다면_가게와_응원을_저장한다() { Member member = memberGenerator.generate("123"); - CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "맛있어요!"); + CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "맛있어요!", + List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM)); StoreSearchResult result = new StoreSearchResult( "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", 37.5665, 126.9780); @@ -95,7 +100,8 @@ class RegisterCheer { Member member = memberGenerator.generate("123"); Store store = storeGenerator.generate("123", "서울시 강남구 역삼동 123-45"); - CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "맛있어요!"); + CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "맛있어요!", + List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM)); StoreSearchResult result = new StoreSearchResult( "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", 37.5665, 126.9780); @@ -116,7 +122,8 @@ class RegisterCheer { void 해당_응원의_이미지가_비어있어도_응원을_저장할_수_있다() { Member member = memberGenerator.generate("123"); - CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "맛있어요!"); + CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "맛있어요!", + List.of(CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM)); StoreSearchResult result = new StoreSearchResult( "123", StoreCategory.KOREAN, "02-755-5232", "농민백암순대 본점", "http://place.map.kakao.com/123", "서울시 강남구 역삼동 123-45", "서울시 강남구 역삼동 123-45", 37.5665, 126.9780); From c6df6e48081bb4bdffe00700ce921f6fdcc51bf0 Mon Sep 17 00:00:00 2001 From: leegwichan Date: Tue, 12 Aug 2025 11:59:01 +0900 Subject: [PATCH 2/3] =?UTF-8?q?feat:=20=ED=83=9C=EA=B7=B8=20=EC=A0=80?= =?UTF-8?q?=EC=9E=A5=20=EC=84=9C=EB=B9=84=EC=8A=A4=20=EB=A1=9C=EC=A7=81=20?= =?UTF-8?q?=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/eatda/controller/cheer/CheerResponse.java | 11 +++++++++-- src/main/java/eatda/service/cheer/CheerService.java | 8 +++++++- .../java/eatda/service/cheer/CheerServiceTest.java | 12 +++++++++--- 3 files changed, 25 insertions(+), 6 deletions(-) diff --git a/src/main/java/eatda/controller/cheer/CheerResponse.java b/src/main/java/eatda/controller/cheer/CheerResponse.java index 0611e697..85dca2b2 100644 --- a/src/main/java/eatda/controller/cheer/CheerResponse.java +++ b/src/main/java/eatda/controller/cheer/CheerResponse.java @@ -1,6 +1,7 @@ package eatda.controller.cheer; import eatda.domain.cheer.Cheer; +import eatda.domain.cheer.CheerTag; import eatda.domain.cheer.CheerTagName; import eatda.domain.store.Store; import java.util.List; @@ -13,13 +14,19 @@ public record CheerResponse( List tags ) { - public CheerResponse(Cheer cheer, String imageUrl, Store store) { + public CheerResponse(Cheer cheer, List cheerTags, Store store, String imageUrl) { this( store.getId(), cheer.getId(), imageUrl, cheer.getDescription(), - List.of() // TODO tags 불러오기 + toTagNames(cheerTags) ); } + + private static List toTagNames(List cheerTags) { + return cheerTags.stream() + .map(CheerTag::getName) + .toList(); + } } diff --git a/src/main/java/eatda/service/cheer/CheerService.java b/src/main/java/eatda/service/cheer/CheerService.java index d22998af..89c06e3e 100644 --- a/src/main/java/eatda/service/cheer/CheerService.java +++ b/src/main/java/eatda/service/cheer/CheerService.java @@ -8,12 +8,15 @@ import eatda.controller.cheer.CheersResponse; import eatda.domain.ImageKey; import eatda.domain.cheer.Cheer; +import eatda.domain.cheer.CheerTag; +import eatda.domain.cheer.CheerTagNames; import eatda.domain.member.Member; import eatda.domain.store.Store; import eatda.domain.store.StoreSearchResult; import eatda.exception.BusinessErrorCode; import eatda.exception.BusinessException; import eatda.repository.cheer.CheerRepository; +import eatda.repository.cheer.CheerTagRepository; import eatda.repository.member.MemberRepository; import eatda.repository.store.StoreRepository; import eatda.storage.image.ImageStorage; @@ -32,6 +35,7 @@ public class CheerService { private final MemberRepository memberRepository; private final StoreRepository storeRepository; private final CheerRepository cheerRepository; + private final CheerTagRepository cheerTagRepository; private final ImageStorage imageStorage; @Transactional @@ -39,13 +43,15 @@ public CheerResponse registerCheer(CheerRegisterRequest request, StoreSearchResult result, ImageKey imageKey, long memberId) { + CheerTagNames cheerTagNames = new CheerTagNames(request.tags()); Member member = memberRepository.getById(memberId); validateRegisterCheer(member, request.storeKakaoId()); Store store = storeRepository.findByKakaoId(result.kakaoId()) .orElseGet(() -> storeRepository.save(result.toStore())); // TODO 상점 조회/저장 동시성 이슈 해결 Cheer cheer = cheerRepository.save(new Cheer(member, store, request.description(), imageKey)); - return new CheerResponse(cheer, imageStorage.getPreSignedUrl(imageKey), store); + List cheerTags = cheerTagRepository.saveAll(cheerTagNames.toCheerTags(cheer)); + return new CheerResponse(cheer, cheerTags, store, imageStorage.getPreSignedUrl(imageKey)); } private void validateRegisterCheer(Member member, String storeKakaoId) { diff --git a/src/test/java/eatda/service/cheer/CheerServiceTest.java b/src/test/java/eatda/service/cheer/CheerServiceTest.java index d0dac9f9..9e676929 100644 --- a/src/test/java/eatda/service/cheer/CheerServiceTest.java +++ b/src/test/java/eatda/service/cheer/CheerServiceTest.java @@ -91,7 +91,9 @@ class RegisterCheer { assertAll( () -> assertThat(response.storeId()).isEqualTo(foundStore.getId()), () -> assertThat(response.cheerDescription()).isEqualTo("맛있어요!"), - () -> assertThat(response.imageUrl()).isNotNull() + () -> assertThat(response.imageUrl()).isNotNull(), + () -> assertThat(response.tags()).containsExactlyInAnyOrder( + CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM) ); } @@ -114,7 +116,9 @@ class RegisterCheer { () -> assertThat(foundStore.getId()).isEqualTo(store.getId()), () -> assertThat(response.storeId()).isEqualTo(foundStore.getId()), () -> assertThat(response.cheerDescription()).isEqualTo("맛있어요!"), - () -> assertThat(response.imageUrl()).isNotNull() + () -> assertThat(response.imageUrl()).isNotNull(), + () -> assertThat(response.tags()).containsExactlyInAnyOrder( + CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM) ); } @@ -135,7 +139,9 @@ class RegisterCheer { assertAll( () -> assertThat(response.storeId()).isEqualTo(foundStore.getId()), () -> assertThat(response.cheerDescription()).isEqualTo("맛있어요!"), - () -> assertThat(response.imageUrl()).isNull() + () -> assertThat(response.imageUrl()).isNull(), + () -> assertThat(response.tags()).containsExactlyInAnyOrder( + CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM) ); } } From fb898f6eefc46cf3d34fac9c1f4e953e36440abe Mon Sep 17 00:00:00 2001 From: leegwichan Date: Fri, 15 Aug 2025 17:54:03 +0900 Subject: [PATCH 3/3] =?UTF-8?q?test(CheerControllerTest):=20=ED=83=9C?= =?UTF-8?q?=EA=B7=B8=20=EA=B2=80=EC=A6=9D=EB=AC=B8=20=EA=B5=AC=EC=B2=B4?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/test/java/eatda/controller/cheer/CheerControllerTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/eatda/controller/cheer/CheerControllerTest.java b/src/test/java/eatda/controller/cheer/CheerControllerTest.java index a3b7d9aa..ebc8dc3a 100644 --- a/src/test/java/eatda/controller/cheer/CheerControllerTest.java +++ b/src/test/java/eatda/controller/cheer/CheerControllerTest.java @@ -42,7 +42,7 @@ class RegisterCheer { assertAll( () -> assertThat(response.storeId()).isEqualTo(store.getId()), () -> assertThat(response.cheerDescription()).isEqualTo(request.description()), - () -> assertThat(response.tags()).containsAll(request.tags()) + () -> assertThat(response.tags()).containsExactlyInAnyOrderElementsOf(request.tags()) ); } @@ -65,7 +65,7 @@ class RegisterCheer { assertAll( () -> assertThat(response.storeId()).isEqualTo(store.getId()), () -> assertThat(response.cheerDescription()).isEqualTo(request.description()), - () -> assertThat(response.tags()).containsAll(request.tags()) + () -> assertThat(response.tags()).containsExactlyInAnyOrderElementsOf(request.tags()) ); } }