diff --git a/src/main/java/eatda/controller/cheer/CheerResponse.java b/src/main/java/eatda/controller/cheer/CheerResponse.java index 85dca2b2..45f6c6ee 100644 --- a/src/main/java/eatda/controller/cheer/CheerResponse.java +++ b/src/main/java/eatda/controller/cheer/CheerResponse.java @@ -1,7 +1,6 @@ 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; @@ -14,19 +13,13 @@ public record CheerResponse( List tags ) { - public CheerResponse(Cheer cheer, List cheerTags, Store store, String imageUrl) { + public CheerResponse(Cheer cheer, Store store, String imageUrl) { this( store.getId(), cheer.getId(), imageUrl, cheer.getDescription(), - toTagNames(cheerTags) + cheer.getCheerTagNames() ); } - - private static List toTagNames(List cheerTags) { - return cheerTags.stream() - .map(CheerTag::getName) - .toList(); - } } diff --git a/src/main/java/eatda/domain/cheer/Cheer.java b/src/main/java/eatda/domain/cheer/Cheer.java index 4257be36..ecc8acfd 100644 --- a/src/main/java/eatda/domain/cheer/Cheer.java +++ b/src/main/java/eatda/domain/cheer/Cheer.java @@ -16,6 +16,8 @@ import jakarta.persistence.JoinColumn; import jakarta.persistence.ManyToOne; import jakarta.persistence.Table; +import java.util.Collections; +import java.util.List; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -44,6 +46,9 @@ public class Cheer extends AuditingEntity { @Embedded private ImageKey imageKey; + @Embedded + private CheerTags cheerTags; + @Column(name = "is_admin", nullable = false) private boolean isAdmin; @@ -53,6 +58,7 @@ public Cheer(Member member, Store store, String description, ImageKey imageKey) this.store = store; this.description = description; this.imageKey = imageKey; + this.cheerTags = new CheerTags(); this.isAdmin = false; } @@ -67,4 +73,15 @@ private void validateDescription(String description) { throw new BusinessException(BusinessErrorCode.INVALID_CHEER_DESCRIPTION); } } + + public void setCheerTags(List cheerTagNames) { + this.cheerTags.setTags(this, cheerTagNames); + } + + public List getCheerTagNames() { + if (cheerTags == null) { + return Collections.emptyList(); + } + return cheerTags.getNames(); + } } diff --git a/src/main/java/eatda/domain/cheer/CheerTagNames.java b/src/main/java/eatda/domain/cheer/CheerTags.java similarity index 62% rename from src/main/java/eatda/domain/cheer/CheerTagNames.java rename to src/main/java/eatda/domain/cheer/CheerTags.java index 89d34246..6b0ed6e0 100644 --- a/src/main/java/eatda/domain/cheer/CheerTagNames.java +++ b/src/main/java/eatda/domain/cheer/CheerTags.java @@ -2,18 +2,29 @@ import eatda.exception.BusinessErrorCode; import eatda.exception.BusinessException; +import jakarta.persistence.CascadeType; +import jakarta.persistence.Embeddable; +import jakarta.persistence.OneToMany; +import java.util.ArrayList; import java.util.List; import java.util.stream.Collectors; -public class CheerTagNames { +@Embeddable +public class CheerTags { private static final int MAX_CHEER_TAGS_PER_TYPE = 2; - private final List cheerTagNames; + @OneToMany(mappedBy = "cheer", cascade = CascadeType.ALL, orphanRemoval = true) + private List values = new ArrayList<>(); - public CheerTagNames(List cheerTagNames) { + public void setTags(Cheer cheer, List cheerTagNames) { validate(cheerTagNames); - this.cheerTagNames = List.copyOf(cheerTagNames); + List cheerTags = cheerTagNames.stream() + .map(name -> new CheerTag(cheer, name)) // cheer is set later + .toList(); + + this.values.clear(); + this.values.addAll(cheerTags); } private void validate(List cheerTagNames) { @@ -41,9 +52,9 @@ private long maxCountByType(List cheerTagNames) { .orElse(0L); } - public List toCheerTags(Cheer cheer) { - return cheerTagNames.stream() - .map(name -> new CheerTag(cheer, name)) + public List getNames() { + return values.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 89c06e3e..3741ee05 100644 --- a/src/main/java/eatda/service/cheer/CheerService.java +++ b/src/main/java/eatda/service/cheer/CheerService.java @@ -8,8 +8,6 @@ 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; @@ -43,15 +41,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)); - List cheerTags = cheerTagRepository.saveAll(cheerTagNames.toCheerTags(cheer)); - return new CheerResponse(cheer, cheerTags, store, imageStorage.getPreSignedUrl(imageKey)); + Cheer cheer = new Cheer(member, store, request.description(), imageKey); + cheer.setCheerTags(request.tags()); + Cheer savedCheer = cheerRepository.save(cheer); + return new CheerResponse(savedCheer, store, imageStorage.getPreSignedUrl(imageKey)); } private void validateRegisterCheer(Member member, String storeKakaoId) { diff --git a/src/test/java/eatda/domain/cheer/CheerTagNamesTest.java b/src/test/java/eatda/domain/cheer/CheerTagsTest.java similarity index 52% rename from src/test/java/eatda/domain/cheer/CheerTagNamesTest.java rename to src/test/java/eatda/domain/cheer/CheerTagsTest.java index 22100eee..0319f9dc 100644 --- a/src/test/java/eatda/domain/cheer/CheerTagNamesTest.java +++ b/src/test/java/eatda/domain/cheer/CheerTagsTest.java @@ -4,6 +4,11 @@ import static org.assertj.core.api.Assertions.assertThatCode; import static org.junit.jupiter.api.Assertions.assertThrows; +import eatda.domain.ImageKey; +import eatda.domain.member.Member; +import eatda.domain.store.District; +import eatda.domain.store.Store; +import eatda.domain.store.StoreCategory; import eatda.exception.BusinessErrorCode; import eatda.exception.BusinessException; import java.util.Collections; @@ -11,33 +16,53 @@ import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; -class CheerTagNamesTest { +class CheerTagsTest { + + private static final Member DEFAULT_MEMBER = new Member("socialId", "email@kakao.com", "nickname"); + private static final Store DEFAULT_STORE = Store.builder() + .kakaoId("123456789") + .category(StoreCategory.OTHER) + .phoneNumber("010-1234-5678") + .name("가게 이름") + .placeUrl("https://place.kakao.com/123456789") + .roadAddress("") + .lotNumberAddress("서울특별시 강남구 역삼동 123-45") + .district(District.GANGNAM) + .latitude(37.5665) + .longitude(126.978) + .build(); + private static final Cheer DEFAULT_CHEER = new Cheer(DEFAULT_MEMBER, DEFAULT_STORE, "Great store!", + new ImageKey("imageKey")); @Nested - class Validate { + class SetTags { @Test void 각_카테고리별_태그는_최대_개수가_정해져있다() { List tagNames = List.of( CheerTagName.OLD_STORE_MOOD, CheerTagName.ENERGETIC, CheerTagName.GROUP_RESERVATION, CheerTagName.LARGE_PARKING); + CheerTags cheerTags = new CheerTags(); - assertThatCode(() -> new CheerTagNames(tagNames)).doesNotThrowAnyException(); + assertThatCode(() -> cheerTags.setTags(DEFAULT_CHEER, tagNames)).doesNotThrowAnyException(); } @Test void 태그_이름은_비어있을_수_있다() { List tagNames = Collections.emptyList(); + CheerTags cheerTags = new CheerTags(); - assertThatCode(() -> new CheerTagNames(tagNames)).doesNotThrowAnyException(); + assertThatCode(() -> cheerTags.setTags(DEFAULT_CHEER, tagNames)).doesNotThrowAnyException(); } @Test void 카테고리별_태그는_최대_개수를_초과할_수_없다() { List tagNames = List.of( CheerTagName.OLD_STORE_MOOD, CheerTagName.ENERGETIC, CheerTagName.GOOD_FOR_DATING); + CheerTags cheerTags = new CheerTags(); - BusinessException exception = assertThrows(BusinessException.class, () -> new CheerTagNames(tagNames)); + BusinessException exception = assertThrows(BusinessException.class, + () -> cheerTags.setTags(DEFAULT_CHEER, tagNames)); assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.EXCEED_CHEER_TAGS_PER_TYPE); } @@ -45,10 +70,13 @@ class Validate { @Test void 태그_이름은_중복될_수_없다() { List tagNames = List.of(CheerTagName.OLD_STORE_MOOD, CheerTagName.OLD_STORE_MOOD); + CheerTags cheerTags = new CheerTags(); - BusinessException exception = assertThrows(BusinessException.class, () -> new CheerTagNames(tagNames)); + BusinessException exception = assertThrows(BusinessException.class, + () -> cheerTags.setTags(DEFAULT_CHEER, tagNames)); assertThat(exception.getErrorCode()).isEqualTo(BusinessErrorCode.CHEER_TAGS_DUPLICATED); } } + } diff --git a/src/test/java/eatda/service/cheer/CheerServiceTest.java b/src/test/java/eatda/service/cheer/CheerServiceTest.java index 7f3aa001..1f424334 100644 --- a/src/test/java/eatda/service/cheer/CheerServiceTest.java +++ b/src/test/java/eatda/service/cheer/CheerServiceTest.java @@ -145,6 +145,27 @@ class RegisterCheer { CheerTagName.GOOD_FOR_DATING, CheerTagName.CLEAN_RESTROOM) ); } + + @Test + void 해당_응원의_응원_태그가_비어있어도_응원을_저장할_수_있다() { + Member member = memberGenerator.generate("123"); + + CheerRegisterRequest request = new CheerRegisterRequest("123", "농민백암순대 본점", "맛있어요!", List.of()); + 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); + ImageKey imageKey = new ImageKey("image-key"); + + CheerResponse response = cheerService.registerCheer(request, result, imageKey, member.getId()); + + Store foundStore = storeRepository.findByKakaoId("123").orElseThrow(); + assertAll( + () -> assertThat(response.storeId()).isEqualTo(foundStore.getId()), + () -> assertThat(response.cheerDescription()).isEqualTo("맛있어요!"), + () -> assertThat(response.imageUrl()).isNotBlank(), + () -> assertThat(response.tags()).isEmpty() + ); + } } @Nested