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..85dca2b2 100644 --- a/src/main/java/eatda/controller/cheer/CheerResponse.java +++ b/src/main/java/eatda/controller/cheer/CheerResponse.java @@ -1,21 +1,32 @@ 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; public record CheerResponse( long storeId, long cheerId, String imageUrl, - String cheerDescription + String cheerDescription, + 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() + cheer.getDescription(), + toTagNames(cheerTags) ); } + + private static List toTagNames(List cheerTags) { + return cheerTags.stream() + .map(CheerTag::getName) + .toList(); + } } 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/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/controller/BaseControllerTest.java b/src/test/java/eatda/controller/BaseControllerTest.java index f729fe7c..bb58a198 100644 --- a/src/test/java/eatda/controller/BaseControllerTest.java +++ b/src/test/java/eatda/controller/BaseControllerTest.java @@ -18,6 +18,7 @@ import eatda.fixture.StoreGenerator; import eatda.fixture.StoryGenerator; 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 class BaseControllerTest { @Autowired protected CheerRepository cheerRepository; + @Autowired + protected CheerTagRepository cheerTagRepository; + @Autowired protected StoryRepository storyRepository; diff --git a/src/test/java/eatda/controller/cheer/CheerControllerTest.java b/src/test/java/eatda/controller/cheer/CheerControllerTest.java index 5ac4b927..ebc8dc3a 100644 --- a/src/test/java/eatda/controller/cheer/CheerControllerTest.java +++ b/src/test/java/eatda/controller/cheer/CheerControllerTest.java @@ -5,12 +5,14 @@ import eatda.controller.BaseControllerTest; import eatda.domain.cheer.Cheer; +import eatda.domain.cheer.CheerTagName; import eatda.domain.member.Member; import eatda.domain.store.District; 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,8 +24,9 @@ class RegisterCheer { @Test void 응원을_등록한다() { - Store store = storeGenerator.generate("123", "서울시 노원구 월계3동 123-45", District.NOWON); - CheerRegisterRequest request = new CheerRegisterRequest(store.getKakaoId(), store.getName(), "맛있어요!"); + Store store = storeGenerator.generate("123", "서울시 노원구 월계3동 123-45"); + CheerRegisterRequest request = new CheerRegisterRequest(store.getKakaoId(), store.getName(), "맛있어요!", + List.of(CheerTagName.INSTAGRAMMABLE, CheerTagName.CLEAN_RESTROOM)); CheerResponse response = given() .header(HttpHeaders.AUTHORIZATION, accessToken()) @@ -36,13 +39,18 @@ class RegisterCheer { .statusCode(201) .extract().as(CheerResponse.class); - assertThat(response.storeId()).isEqualTo(store.getId()); + assertAll( + () -> assertThat(response.storeId()).isEqualTo(store.getId()), + () -> assertThat(response.cheerDescription()).isEqualTo(request.description()), + () -> assertThat(response.tags()).containsExactlyInAnyOrderElementsOf(request.tags()) + ); } @Test void 이미지가_비어있을_경우에도_응원을_등록한다() { Store store = storeGenerator.generate("123", "서울시 노원구 월계3동 123-45", District.NOWON); - CheerRegisterRequest request = new CheerRegisterRequest(store.getKakaoId(), store.getName(), "맛있어요!"); + CheerRegisterRequest request = new CheerRegisterRequest(store.getKakaoId(), store.getName(), "맛있어요!", + List.of(CheerTagName.INSTAGRAMMABLE, CheerTagName.CLEAN_RESTROOM)); CheerResponse response = given() .header(HttpHeaders.AUTHORIZATION, accessToken()) @@ -54,7 +62,11 @@ class RegisterCheer { .statusCode(201) .extract().as(CheerResponse.class); - assertThat(response.storeId()).isEqualTo(store.getId()); + assertAll( + () -> assertThat(response.storeId()).isEqualTo(store.getId()), + () -> assertThat(response.cheerDescription()).isEqualTo(request.description()), + () -> assertThat(response.tags()).containsExactlyInAnyOrderElementsOf(request.tags()) + ); } } 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 19c5be0d..35230225 100644 --- a/src/test/java/eatda/service/BaseServiceTest.java +++ b/src/test/java/eatda/service/BaseServiceTest.java @@ -11,6 +11,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; @@ -67,6 +68,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 ca49ce17..7f3aa001 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.District; import eatda.domain.store.Store; @@ -19,6 +20,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; @@ -41,7 +43,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", District.GANGNAM, 37.5665, 126.9780); @@ -59,7 +62,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", District.GANGNAM, 37.5665, 126.9780); @@ -75,7 +79,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", District.GANGNAM, 37.5665, 126.9780); @@ -87,7 +92,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) ); } @@ -96,7 +103,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", District.GANGNAM, 37.5665, 126.9780); @@ -109,7 +117,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) ); } @@ -117,7 +127,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", District.GANGNAM, 37.5665, 126.9780); @@ -129,7 +140,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) ); } }