diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 63784259..95373418 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -55,7 +55,9 @@ jobs: uses: EnricoMi/publish-unit-test-result-action@v2 if: always() with: - files: ./cakk-api/build/test-results/**/*.xml + files: | + ./cakk-api/build/test-results/**/*.xml + ./cakk-domain/mysql/build/test-results/**/*.xml - name: Upload test coverage id: jacoco diff --git a/cakk-api/src/main/java/com/cakk/api/controller/shop/ShopController.java b/cakk-api/src/main/java/com/cakk/api/controller/shop/ShopController.java index 2aa22ff0..37eb546f 100644 --- a/cakk-api/src/main/java/com/cakk/api/controller/shop/ShopController.java +++ b/cakk-api/src/main/java/com/cakk/api/controller/shop/ShopController.java @@ -123,7 +123,6 @@ public ApiResponse like( @SignInUser User user, @PathVariable Long cakeShopId ) { - likeService.validateLikeCount(user, cakeShopId); likeService.likeCakeShop(user, cakeShopId); return ApiResponse.success(); diff --git a/cakk-api/src/main/java/com/cakk/api/service/like/HeartService.java b/cakk-api/src/main/java/com/cakk/api/service/like/HeartService.java index 5167198c..fb11f081 100644 --- a/cakk-api/src/main/java/com/cakk/api/service/like/HeartService.java +++ b/cakk-api/src/main/java/com/cakk/api/service/like/HeartService.java @@ -16,21 +16,16 @@ import com.cakk.api.mapper.CakeMapper; import com.cakk.api.mapper.HeartMapper; import com.cakk.api.mapper.ShopMapper; -import com.cakk.common.enums.RedisKey; import com.cakk.domain.mysql.dto.param.like.HeartCakeImageResponseParam; import com.cakk.domain.mysql.dto.param.like.HeartCakeShopResponseParam; import com.cakk.domain.mysql.entity.cake.Cake; -import com.cakk.domain.mysql.entity.cake.CakeHeart; import com.cakk.domain.mysql.entity.shop.CakeShop; -import com.cakk.domain.mysql.entity.shop.CakeShopHeart; import com.cakk.domain.mysql.entity.user.User; +import com.cakk.domain.mysql.facade.user.UserHeartFacade; import com.cakk.domain.mysql.repository.reader.CakeHeartReader; import com.cakk.domain.mysql.repository.reader.CakeReader; import com.cakk.domain.mysql.repository.reader.CakeShopHeartReader; import com.cakk.domain.mysql.repository.reader.CakeShopReader; -import com.cakk.domain.mysql.repository.writer.CakeHeartWriter; -import com.cakk.domain.mysql.repository.writer.CakeShopHeartWriter; -import com.cakk.domain.redis.repository.LockRedisRepository; @RequiredArgsConstructor @Service @@ -39,9 +34,8 @@ public class HeartService { private final CakeReader cakeReader; private final CakeShopReader cakeShopReader; private final CakeHeartReader cakeHeartReader; - private final CakeHeartWriter cakeHeartWriter; private final CakeShopHeartReader cakeShopHeartReader; - private final CakeShopHeartWriter cakeShopHeartWriter; + private final UserHeartFacade userHeartFacade; @Transactional(readOnly = true) public HeartCakeImageListResponse searchCakeImagesByCursorAndHeart( @@ -73,33 +67,31 @@ public HeartCakeShopListResponse searchCakeShopByCursorAndHeart( @Transactional(readOnly = true) public HeartResponse isHeartCake(final User user, final Long cakeId) { - final Cake cake = cakeReader.findById(cakeId); - final boolean isHeart = cakeHeartReader.existsByUserAndCake(user, cake); + final Cake cake = cakeReader.findByIdWithHeart(cakeId); + final boolean isHeart = cake.isHeartedBy(user); return HeartMapper.supplyHeartResponseBy(isHeart); } @Transactional(readOnly = true) public HeartResponse isHeartCakeShop(final User user, final Long cakeShopId) { - final CakeShop cakeShop = cakeShopReader.findById(cakeShopId); - final boolean isHeart = cakeShopHeartReader.existsByUserAndCakeShop(user, cakeShop); + final CakeShop cakeShop = cakeShopReader.findByIdWithHeart(cakeShopId); + final boolean isHeart = cakeShop.isHeartedBy(user); return HeartMapper.supplyHeartResponseBy(isHeart); } @DistributedLock(key = "#cakeId") public void heartCake(final User user, final Long cakeId) { - final Cake cake = cakeReader.findById(cakeId); - final CakeHeart cakeHeart = cakeHeartReader.findOrNullByUserAndCake(user, cake); + final Cake cake = cakeReader.findByIdWithHeart(cakeId); - cakeHeartWriter.heartOrCancel(cakeHeart, user, cake); + userHeartFacade.heartCake(user, cake); } @DistributedLock(key = "#cakeShopId") public void heartCakeShop(final User user, final Long cakeShopId) { - final CakeShop cakeShop = cakeShopReader.findById(cakeShopId); - final CakeShopHeart cakeShopHeart = cakeShopHeartReader.findOrNullByUserAndCakeShop(user, cakeShop); + final CakeShop cakeShop = cakeShopReader.findByIdWithHeart(cakeShopId); - cakeShopHeartWriter.heartOrCancel(cakeShopHeart, user, cakeShop); + userHeartFacade.heartCakeShop(user, cakeShop); } } diff --git a/cakk-api/src/main/java/com/cakk/api/service/like/LikeService.java b/cakk-api/src/main/java/com/cakk/api/service/like/LikeService.java index 903e0772..8cbf3990 100644 --- a/cakk-api/src/main/java/com/cakk/api/service/like/LikeService.java +++ b/cakk-api/src/main/java/com/cakk/api/service/like/LikeService.java @@ -1,40 +1,26 @@ package com.cakk.api.service.like; import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; import com.cakk.api.annotation.DistributedLock; -import com.cakk.common.enums.ReturnCode; -import com.cakk.common.exception.CakkException; import com.cakk.domain.mysql.entity.shop.CakeShop; import com.cakk.domain.mysql.entity.user.User; -import com.cakk.domain.mysql.repository.reader.CakeShopLikeReader; +import com.cakk.domain.mysql.facade.user.UserLikeFacade; import com.cakk.domain.mysql.repository.reader.CakeShopReader; -import com.cakk.domain.mysql.repository.writer.CakeShopLikeWriter; @Service @RequiredArgsConstructor public class LikeService { private final CakeShopReader cakeShopReader; - private final CakeShopLikeReader cakeShopLikeReader; - private final CakeShopLikeWriter cakeShopLikeWriter; - - @Transactional(readOnly = true) - public void validateLikeCount(final User user, final Long cakeShopId) { - final int likeCount = cakeShopLikeReader.countByCakeShopIdAndUser(cakeShopId, user); - - if (likeCount >= 50) { - throw new CakkException(ReturnCode.MAX_CAKE_SHOP_LIKE); - } - } + private final UserLikeFacade userCakeFacade; @DistributedLock(key = "#cakeShopId") public void likeCakeShop(final User user, final Long cakeShopId) { - final CakeShop cakeShop = cakeShopReader.findById(cakeShopId); + final CakeShop cakeShop = cakeShopReader.findByIdWithLike(cakeShopId); - cakeShopLikeWriter.like(cakeShop, user); + userCakeFacade.likeCakeShop(user, cakeShop); } } diff --git a/cakk-api/src/test/java/com/cakk/api/service/like/HeartServiceTest.java b/cakk-api/src/test/java/com/cakk/api/service/like/HeartServiceTest.java index 5dfc35b4..5975d194 100644 --- a/cakk-api/src/test/java/com/cakk/api/service/like/HeartServiceTest.java +++ b/cakk-api/src/test/java/com/cakk/api/service/like/HeartServiceTest.java @@ -21,17 +21,15 @@ import com.cakk.domain.mysql.dto.param.like.HeartCakeImageResponseParam; import com.cakk.domain.mysql.entity.cake.Cake; import com.cakk.domain.mysql.entity.shop.CakeShop; -import com.cakk.domain.mysql.entity.shop.CakeShopHeart; import com.cakk.domain.mysql.entity.user.User; +import com.cakk.domain.mysql.facade.user.UserHeartFacade; import com.cakk.domain.mysql.repository.reader.CakeHeartReader; import com.cakk.domain.mysql.repository.reader.CakeReader; import com.cakk.domain.mysql.repository.reader.CakeShopHeartReader; import com.cakk.domain.mysql.repository.reader.CakeShopReader; -import com.cakk.domain.mysql.repository.writer.CakeHeartWriter; -import com.cakk.domain.mysql.repository.writer.CakeShopHeartWriter; @DisplayName("하트 기능 관련 비즈니스 로직 테스트") -public class HeartServiceTest extends ServiceTest { +class HeartServiceTest extends ServiceTest { @InjectMocks private HeartService heartService; @@ -45,14 +43,12 @@ public class HeartServiceTest extends ServiceTest { @Mock private CakeHeartReader cakeHeartReader; - @Mock - private CakeHeartWriter cakeHeartWriter; - @Mock private CakeShopHeartReader cakeShopHeartReader; @Mock - private CakeShopHeartWriter cakeShopHeartWriter; + private UserHeartFacade userHeartFacade; + @TestWithDisplayName("하트 한 케이크 목록을 조회한다.") void findCakeImagesByCursorAndHeart() { @@ -135,59 +131,53 @@ void heartCake1() { final Long cakeId = 1L; final Cake cake = getConstructorMonkey().giveMeOne(Cake.class); - doReturn(cake).when(cakeReader).findById(cakeId); - doReturn(null).when(cakeHeartReader).findOrNullByUserAndCake(user, cake); + doReturn(cake).when(cakeReader).findByIdWithHeart(cakeId); + doNothing().when(userHeartFacade).heartCake(user, cake); // when & then assertDoesNotThrow(() -> heartService.heartCake(user, cakeId)); - verify(cakeReader, times(1)).findById(cakeId); - verify(cakeHeartReader, times(1)).findOrNullByUserAndCake(user, cake); + verify(cakeReader, times(1)).findByIdWithHeart(cakeId); + verify(userHeartFacade, times(1)).heartCake(user, cake); } - @TestWithDisplayName("해당 케이크가 없으면 하트 동작을 실패한다.") + @TestWithDisplayName("케이크에 대하여 하트 취소를 동작한다.") void heartCake2() { // given final User user = getUser(); final Long cakeId = 1L; final Cake cake = getConstructorMonkey().giveMeOne(Cake.class); + cake.heart(user); - doThrow(new CakkException(ReturnCode.NOT_EXIST_CAKE)).when(cakeReader).findById(cakeId); + doReturn(cake).when(cakeReader).findByIdWithHeart(cakeId); + doNothing().when(userHeartFacade).heartCake(user, cake); // when & then - assertThrows( - CakkException.class, - () -> heartService.heartCake(user, cakeId), - ReturnCode.NOT_EXIST_CAKE.getMessage()); + assertDoesNotThrow(() -> heartService.heartCake(user, cakeId)); - verify(cakeReader, times(1)).findById(cakeId); - verify(cakeHeartReader, times(0)).findOrNullByUserAndCake(user, cake); + verify(cakeReader, times(1)).findByIdWithHeart(cakeId); + verify(userHeartFacade, times(1)).heartCake(user, cake); } - @TestWithDisplayName("케이크 샵에 대하여 하트를 동작한다.") - void heartCakeShop1() { + @TestWithDisplayName("해당 케이크가 없으면 하트 동작을 실패한다.") + void heartCake3() { // given final User user = getUser(); - final Long cakeShopId = 1L; - final CakeShop cakeShop = getConstructorMonkey().giveMeBuilder(CakeShop.class) - .set("shopName", Arbitraries.strings().withCharRange('a', 'z').ofMaxLength(30)) - .set("shopBio", Arbitraries.strings().withCharRange('a', 'z').ofMaxLength(40)) - .set("shopDescription", Arbitraries.strings().withCharRange('a', 'z').ofMaxLength(500)) - .set("location", supplyPointBy(Arbitraries.doubles().sample(), Arbitraries.doubles().sample())) - .sample(); + final Long cakeId = 1L; - doReturn(cakeShop).when(cakeShopReader).findById(cakeShopId); - doReturn(null).when(cakeShopHeartReader).findOrNullByUserAndCakeShop(user, cakeShop); + doThrow(new CakkException(ReturnCode.NOT_EXIST_CAKE)).when(cakeReader).findByIdWithHeart(cakeId); // when & then - assertDoesNotThrow(() -> heartService.heartCakeShop(user, cakeShopId)); + assertThrows( + CakkException.class, + () -> heartService.heartCake(user, cakeId), + ReturnCode.NOT_EXIST_CAKE.getMessage()); - verify(cakeShopReader, times(1)).findById(cakeShopId); - verify(cakeShopHeartReader, times(1)).findOrNullByUserAndCakeShop(user, cakeShop); + verify(cakeReader, times(1)).findByIdWithHeart(cakeId); } - @TestWithDisplayName("케이크 샵에 대하여 하트 취소를 동작한다.") - void heartCakeShop2() { + @TestWithDisplayName("케이크 샵에 대하여 하트를 동작한다.") + void heartCakeShop1() { // given final User user = getUser(); final Long cakeShopId = 1L; @@ -197,16 +187,15 @@ void heartCakeShop2() { .set("shopDescription", Arbitraries.strings().withCharRange('a', 'z').ofMaxLength(500)) .set("location", supplyPointBy(Arbitraries.doubles().sample(), Arbitraries.doubles().sample())) .sample(); - final CakeShopHeart cakeShopHeart = getConstructorMonkey().giveMeOne(CakeShopHeart.class); - doReturn(cakeShop).when(cakeShopReader).findById(cakeShopId); - doReturn(cakeShopHeart).when(cakeShopHeartReader).findOrNullByUserAndCakeShop(user, cakeShop); + doReturn(cakeShop).when(cakeShopReader).findByIdWithHeart(cakeShopId); + doNothing().when(userHeartFacade).heartCakeShop(user, cakeShop); // when & then assertDoesNotThrow(() -> heartService.heartCakeShop(user, cakeShopId)); - verify(cakeShopReader, times(1)).findById(cakeShopId); - verify(cakeShopHeartReader, times(1)).findOrNullByUserAndCakeShop(user, cakeShop); + verify(cakeShopReader, times(1)).findByIdWithHeart(cakeShopId); + verify(userHeartFacade, times(1)).heartCakeShop(user, cakeShop); } @TestWithDisplayName("해당 케이크 샵이 없으면 하트 동작을 실패한다.") @@ -215,7 +204,7 @@ void heartCakeShop3() { final User user = getUser(); final Long cakeShopId = 1L; - doThrow(new CakkException(ReturnCode.NOT_EXIST_CAKE_SHOP)).when(cakeShopReader).findById(cakeShopId); + doThrow(new CakkException(ReturnCode.NOT_EXIST_CAKE_SHOP)).when(cakeShopReader).findByIdWithHeart(cakeShopId); // when & then assertThrows( @@ -223,7 +212,7 @@ void heartCakeShop3() { () -> heartService.heartCakeShop(user, cakeShopId), ReturnCode.NOT_EXIST_CAKE_SHOP.getMessage()); - verify(cakeShopReader, times(1)).findById(cakeShopId); + verify(cakeShopReader, times(1)).findByIdWithHeart(cakeShopId); verify(cakeShopHeartReader, times(0)).findOrNullByUserAndCakeShop(any(User.class), any(CakeShop.class)); } } diff --git a/cakk-api/src/test/java/com/cakk/api/service/like/LikeServiceTest.java b/cakk-api/src/test/java/com/cakk/api/service/like/LikeServiceTest.java index 4a14c41f..f5f208c4 100644 --- a/cakk-api/src/test/java/com/cakk/api/service/like/LikeServiceTest.java +++ b/cakk-api/src/test/java/com/cakk/api/service/like/LikeServiceTest.java @@ -11,14 +11,10 @@ import com.cakk.api.common.annotation.TestWithDisplayName; import com.cakk.api.common.base.ServiceTest; -import com.cakk.common.enums.ReturnCode; -import com.cakk.common.exception.CakkException; import com.cakk.domain.mysql.entity.shop.CakeShop; import com.cakk.domain.mysql.entity.user.User; -import com.cakk.domain.mysql.repository.reader.CakeShopLikeReader; +import com.cakk.domain.mysql.facade.user.UserLikeFacade; import com.cakk.domain.mysql.repository.reader.CakeShopReader; -import com.cakk.domain.mysql.repository.writer.CakeShopLikeWriter; - @DisplayName("좋아요 기능 관련 비즈니스 로직 테스트") public class LikeServiceTest extends ServiceTest { @@ -29,39 +25,7 @@ public class LikeServiceTest extends ServiceTest { private CakeShopReader cakeShopReader; @Mock - private CakeShopLikeReader cakeShopLikeReader; - - @Mock - private CakeShopLikeWriter cakeShopLikeWriter; - - @TestWithDisplayName("좋아요 개수 50개가 넘지 않으면 유효성 검사에 통과한다.") - void validateLikeCountSuccess() { - // given - final User user = getUser(); - final long cakeShopId = Arbitraries.longs().greaterOrEqual(1).sample(); - final int likeCount = Arbitraries.integers().lessOrEqual(49).sample(); - - doReturn(likeCount).when(cakeShopLikeReader).countByCakeShopIdAndUser(cakeShopId, user); - - // when & then - assertDoesNotThrow(() -> likeService.validateLikeCount(user, cakeShopId)); - } - - @TestWithDisplayName("좋아요 개수 50개가 넘으면 유효성 검사에서 에러를 반환한다.") - void validateLikeCountFail() { - // given - final User user = getUser(); - final long cakeShopId = Arbitraries.longs().greaterOrEqual(1).sample(); - final int likeCount = Arbitraries.integers().greaterOrEqual(50).sample(); - - doReturn(likeCount).when(cakeShopLikeReader).countByCakeShopIdAndUser(cakeShopId, user); - - // when & then - assertThrows( - CakkException.class, - () -> likeService.validateLikeCount(user, cakeShopId), - ReturnCode.MAX_CAKE_SHOP_LIKE.getMessage()); - } + private UserLikeFacade userLikeFacade; @TestWithDisplayName("케이크 샵 좋아요를 성공한다.") void likeCakeShop() { @@ -75,8 +39,8 @@ void likeCakeShop() { .set("location", supplyPointBy(Arbitraries.doubles().sample(), Arbitraries.doubles().sample())) .sample(); - doReturn(cakeShop).when(cakeShopReader).findById(cakeShopId); - doNothing().when(cakeShopLikeWriter).like(cakeShop, user); + doReturn(cakeShop).when(cakeShopReader).findByIdWithLike(cakeShopId); + doNothing().when(userLikeFacade).likeCakeShop(user, cakeShop); // when likeService.likeCakeShop(user, cakeShopId); diff --git a/cakk-api/src/test/resources/sql/insert-cake-shop.sql b/cakk-api/src/test/resources/sql/insert-cake-shop.sql index 9830e844..030ace82 100644 --- a/cakk-api/src/test/resources/sql/insert-cake-shop.sql +++ b/cakk-api/src/test/resources/sql/insert-cake-shop.sql @@ -10,7 +10,7 @@ SET @g9 = 'Point(37.543343 127.052609)'; SET @g10 = 'Point(37.541530 127.054164)'; insert into cake_shop (shop_id, thumbnail_url, shop_name, shop_address, shop_bio, shop_description, location, like_count, heart_count, created_at, updated_at) -values (1, 'thumbnail_url1', '케이크 맛집1', '케이크 맛집입니다.', '서울시 강남구 어쩌고로1', '케이크 맛집입니다.', ST_GeomFromText(@g1, 4326), 0, 0, now(), now()), +values (1, 'thumbnail_url1', '케이크 맛집1', '케이크 맛집입니다.', '서울시 강남구 어쩌고로1', '케이크 맛집입니다.', ST_GeomFromText(@g1, 4326), 0, 1, now(), now()), (2, 'thumbnail_url2', '케이크 맛집2', '케이크 맛집입니다.', '서울시 강남구 어쩌고로2', '케이크 맛집입니다.', ST_GeomFromText(@g2, 4326), 0, 0, now(), now()), (3, 'thumbnail_url3', '케이크 맛집3', '케이크 맛집입니다.', '서울시 강남구 어쩌고로3', '케이크 맛집입니다.', ST_GeomFromText(@g3, 4326), 0, 0, now(), now()), (4, 'thumbnail_url4', '케이크 맛집4', '케이크 맛집입니다.', '서울시 강남구 어쩌고로3', '케이크 맛집입니다.', ST_GeomFromText(@g4, 4326), 0, 0, now(), now()), diff --git a/cakk-api/src/test/resources/sql/insert-cake.sql b/cakk-api/src/test/resources/sql/insert-cake.sql index f059a36b..713b74cb 100644 --- a/cakk-api/src/test/resources/sql/insert-cake.sql +++ b/cakk-api/src/test/resources/sql/insert-cake.sql @@ -42,7 +42,7 @@ values (1, 1, 0, '10:00:00', '22:00:00', now(), now()), insert into cake (cake_id, shop_id, cake_image_url, heart_count, created_at, updated_at) values (1, 1, 'cake_image_url1', 0, now(), now()), (2, 1, 'cake_image_url2', 0, now(), now()), - (3, 1, 'cake_image_url3', 0, now(), now()), + (3, 1, 'cake_image_url3', 1, now(), now()), (4, 2, 'cake_image_url4', 0, now(), now()), (5, 2, 'cake_image_url5', 0, now(), now()), (6, 2, 'cake_image_url6', 0, now(), now()), diff --git a/cakk-domain/mysql/build.gradle b/cakk-domain/mysql/build.gradle index 78de38ac..289af826 100644 --- a/cakk-domain/mysql/build.gradle +++ b/cakk-domain/mysql/build.gradle @@ -8,7 +8,7 @@ dependencies { implementation 'io.hypersistence:hypersistence-utils-hibernate-63:3.7.4' // test - testImplementation("com.navercorp.fixturemonkey:fixture-monkey-starter:1.0.16") + testImplementation("com.navercorp.fixturemonkey:fixture-monkey-starter:1.0.23") testImplementation('org.assertj:assertj-core') testImplementation('org.junit.jupiter:junit-jupiter') testRuntimeOnly('org.junit.platform:junit-platform-launcher') diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/annotation/DomainFacade.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/annotation/DomainFacade.java new file mode 100644 index 00000000..3c07ddb4 --- /dev/null +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/annotation/DomainFacade.java @@ -0,0 +1,35 @@ +package com.cakk.domain.mysql.annotation; + +import java.lang.annotation.Documented; +import java.lang.annotation.ElementType; +import java.lang.annotation.Retention; +import java.lang.annotation.RetentionPolicy; +import java.lang.annotation.Target; + +import org.springframework.core.annotation.AliasFor; +import org.springframework.stereotype.Component; +import org.springframework.stereotype.Service; + +/** + * Indicates that an annotated class is a "Service" (e.g. a domain service object). + * + *

This annotation serves as a specialization of {@link Service @Service}, + * allowing for implementation classes to be autodetected through classpath scanning. + * + * @author komment + * @see Component + * @see Service + */ + +@Target(ElementType.TYPE) +@Retention(RetentionPolicy.RUNTIME) +@Documented +@Service +public @interface DomainFacade { + + /** + * Alias for {@link Service#value}. + */ + @AliasFor(annotation = Service.class) + String value() default ""; +} diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/cake/Cake.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/cake/Cake.java index f8e1e57a..bf977848 100644 --- a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/cake/Cake.java +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/cake/Cake.java @@ -23,13 +23,19 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.ToString; +import com.cakk.common.enums.ReturnCode; +import com.cakk.common.exception.CakkException; import com.cakk.domain.mysql.entity.audit.AuditEntity; import com.cakk.domain.mysql.entity.shop.CakeShop; +import com.cakk.domain.mysql.entity.user.User; import com.cakk.domain.mysql.event.views.CakeIncreaseViewsEvent; +import com.cakk.domain.mysql.mapper.CakeHeartMapper; import com.cakk.domain.mysql.mapper.CakeTagMapper; import com.cakk.domain.mysql.mapper.EventMapper; +@ToString @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity @@ -57,6 +63,9 @@ public class Cake extends AuditEntity { @OneToMany(mappedBy = "cake", cascade = CascadeType.PERSIST, orphanRemoval = true) private Set cakeTags = new HashSet<>(); + @OneToMany(mappedBy = "cake", cascade = CascadeType.PERSIST, orphanRemoval = true) + private Set cakeHearts = new HashSet<>(); + @ColumnDefault("null") @Column(name = "deleted_at") private LocalDateTime deletedAt; @@ -68,12 +77,18 @@ public Cake(String cakeImageUrl, CakeShop cakeShop) { this.heartCount = 0; } - public void increaseHeartCount() { - this.heartCount++; + public void heart(final User user) { + cakeHearts.add(CakeHeartMapper.supplyCakeHeartBy(this, user)); + this.increaseHeartCount(); } - public void decreaseHeartCount() { - this.heartCount--; + public void unHeart(final User user) { + cakeHearts.removeIf(it -> it.getUser().equals(user)); + this.decreaseHeartCount(); + } + + public boolean isHeartedBy(final User user) { + return cakeHearts.stream().anyMatch(it -> it.getUser().equals(user)); } public void updateCakeImageUrl(final String cakeImageUrl) { @@ -121,4 +136,16 @@ public void updateCakeShop(final CakeShop cakeShop) { public CakeIncreaseViewsEvent getInCreaseViewsEvent() { return EventMapper.supplyCakeIncreaseViewsEvent(this.id); } + + private void increaseHeartCount() { + this.heartCount++; + } + + private void decreaseHeartCount() { + if (this.heartCount == 0) { + throw new CakkException(ReturnCode.INTERNAL_SERVER_ERROR); + } + + this.heartCount--; + } } diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/shop/CakeShop.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/shop/CakeShop.java index 78527da6..016a3b0f 100644 --- a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/shop/CakeShop.java +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/shop/CakeShop.java @@ -24,11 +24,16 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import com.cakk.common.enums.ReturnCode; +import com.cakk.common.exception.CakkException; import com.cakk.domain.mysql.dto.param.shop.CakeShopUpdateParam; import com.cakk.domain.mysql.dto.param.shop.UpdateShopAddressParam; import com.cakk.domain.mysql.entity.audit.AuditEntity; import com.cakk.domain.mysql.entity.cake.Cake; import com.cakk.domain.mysql.entity.user.BusinessInformation; +import com.cakk.domain.mysql.entity.user.User; +import com.cakk.domain.mysql.mapper.CakeShopHeartMapper; +import com.cakk.domain.mysql.mapper.CakeShopLikeMapper; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -83,6 +88,12 @@ public class CakeShop extends AuditEntity { @OneToMany(mappedBy = "cakeShop", cascade = CascadeType.PERSIST) private Set cakes = new HashSet<>(); + @OneToMany(mappedBy = "cakeShop", cascade = CascadeType.PERSIST, orphanRemoval = true) + private Set shopHearts = new HashSet<>(); + + @OneToMany(mappedBy = "cakeShop", cascade = CascadeType.PERSIST, orphanRemoval = true) + private Set shopLikes = new HashSet<>(); + @Builder public CakeShop( String shopName, @@ -103,15 +114,48 @@ public CakeShop( this.likeCount = 0; } - public void increaseLikeCount() { + public void like(final User user) { + if (isLikedOverMaxBy(user)) { + throw new CakkException(ReturnCode.MAX_CAKE_SHOP_LIKE); + } + + shopLikes.add(CakeShopLikeMapper.supplyCakeShopLikeBy(this, user)); + this.increaseLikeCount(); + } + + public void heart(final User user) { + shopHearts.add(CakeShopHeartMapper.supplyCakeShopHeartBy(this, user)); + this.increaseHeartCount(); + } + + public void unHeart(final User user) { + shopHearts.removeIf(it -> it.getUser().equals(user)); + this.decreaseHeartCount(); + } + + public boolean isLikedOverMaxBy(final User user) { + long count = shopLikes.stream().map(it -> it.getUser().equals(user)).count(); + + return count >= 50; + } + + public boolean isHeartedBy(final User user) { + return shopHearts.stream().anyMatch(it -> it.getUser().equals(user)); + } + + private void increaseLikeCount() { this.likeCount++; } - public void increaseHeartCount() { + private void increaseHeartCount() { this.heartCount++; } - public void decreaseHeartCount() { + private void decreaseHeartCount() { + if (this.heartCount == 0) { + throw new CakkException(ReturnCode.INTERNAL_SERVER_ERROR); + } + this.heartCount--; } diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/user/User.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/user/User.java index 793f958b..e2ae1556 100644 --- a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/user/User.java +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/entity/user/User.java @@ -2,6 +2,7 @@ import java.time.LocalDate; import java.time.LocalDateTime; +import java.util.Objects; import jakarta.persistence.Column; import jakarta.persistence.Entity; @@ -29,6 +30,8 @@ import com.cakk.common.enums.Role; import com.cakk.domain.mysql.dto.param.user.ProfileUpdateParam; import com.cakk.domain.mysql.entity.audit.AuditEntity; +import com.cakk.domain.mysql.entity.cake.Cake; +import com.cakk.domain.mysql.entity.shop.CakeShop; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -112,4 +115,40 @@ public void updateProfile(final ProfileUpdateParam param) { this.gender = param.gender(); this.birthday = param.birthday(); } + + public void heartCake(final Cake cake) { + cake.heart(this); + } + + public void unHeartCake(final Cake cake) { + cake.unHeart(this); + } + + public void likeCakeShop(final CakeShop cakeShop) { + cakeShop.like(this); + } + + public void heartCakeShop(final CakeShop cakeShop) { + cakeShop.heart(this); + } + + public void unHeartCakeShop(final CakeShop cakeShop) { + cakeShop.unHeart(this); + } + + @Override + public boolean equals(Object object) { + if (this == object) { + return true; + } + if (!(object instanceof User that)) { + return false; + } + return this.getId() != null && Objects.equals(this.getId(), that.getId()); + } + + @Override + public int hashCode() { + return Objects.hash(getId()); + } } diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/facade/user/UserHeartFacade.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/facade/user/UserHeartFacade.java new file mode 100644 index 00000000..3c558ff0 --- /dev/null +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/facade/user/UserHeartFacade.java @@ -0,0 +1,29 @@ +package com.cakk.domain.mysql.facade.user; + +import lombok.RequiredArgsConstructor; + +import com.cakk.domain.mysql.annotation.DomainFacade; +import com.cakk.domain.mysql.entity.cake.Cake; +import com.cakk.domain.mysql.entity.shop.CakeShop; +import com.cakk.domain.mysql.entity.user.User; + +@RequiredArgsConstructor +@DomainFacade +public class UserHeartFacade { + + public void heartCake(final User user, final Cake cake) { + if (!cake.isHeartedBy(user)) { + user.heartCake(cake); + } else { + user.unHeartCake(cake); + } + } + + public void heartCakeShop(final User user, final CakeShop cakeShop) { + if (!cakeShop.isHeartedBy(user)) { + user.heartCakeShop(cakeShop); + } else { + user.unHeartCakeShop(cakeShop); + } + } +} diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/facade/user/UserLikeFacade.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/facade/user/UserLikeFacade.java new file mode 100644 index 00000000..10b88bf9 --- /dev/null +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/facade/user/UserLikeFacade.java @@ -0,0 +1,13 @@ +package com.cakk.domain.mysql.facade.user; + +import com.cakk.domain.mysql.annotation.DomainFacade; +import com.cakk.domain.mysql.entity.shop.CakeShop; +import com.cakk.domain.mysql.entity.user.User; + +@DomainFacade +public class UserLikeFacade { + + public void likeCakeShop(final User user, final CakeShop cakeShop) { + user.likeCakeShop(cakeShop); + } +} diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/mapper/CakeHeartMapper.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/mapper/CakeHeartMapper.java new file mode 100644 index 00000000..658ae8ac --- /dev/null +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/mapper/CakeHeartMapper.java @@ -0,0 +1,16 @@ +package com.cakk.domain.mysql.mapper; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import com.cakk.domain.mysql.entity.cake.Cake; +import com.cakk.domain.mysql.entity.cake.CakeHeart; +import com.cakk.domain.mysql.entity.user.User; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CakeHeartMapper { + + public static CakeHeart supplyCakeHeartBy(final Cake cake, User user) { + return new CakeHeart(cake, user); + } +} diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/mapper/CakeShopHeartMapper.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/mapper/CakeShopHeartMapper.java new file mode 100644 index 00000000..7c0c5b4b --- /dev/null +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/mapper/CakeShopHeartMapper.java @@ -0,0 +1,16 @@ +package com.cakk.domain.mysql.mapper; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import com.cakk.domain.mysql.entity.shop.CakeShop; +import com.cakk.domain.mysql.entity.shop.CakeShopHeart; +import com.cakk.domain.mysql.entity.user.User; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CakeShopHeartMapper { + + public static CakeShopHeart supplyCakeShopHeartBy(final CakeShop cakeShop, final User user) { + return new CakeShopHeart(cakeShop, user); + } +} diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/mapper/CakeShopLikeMapper.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/mapper/CakeShopLikeMapper.java new file mode 100644 index 00000000..d0de5ff9 --- /dev/null +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/mapper/CakeShopLikeMapper.java @@ -0,0 +1,16 @@ +package com.cakk.domain.mysql.mapper; + +import lombok.AccessLevel; +import lombok.NoArgsConstructor; + +import com.cakk.domain.mysql.entity.shop.CakeShop; +import com.cakk.domain.mysql.entity.shop.CakeShopLike; +import com.cakk.domain.mysql.entity.user.User; + +@NoArgsConstructor(access = AccessLevel.PRIVATE) +public class CakeShopLikeMapper { + + public static CakeShopLike supplyCakeShopLikeBy(final CakeShop cakeShop, final User user) { + return new CakeShopLike(cakeShop, user); + } +} diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/query/CakeQueryRepository.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/query/CakeQueryRepository.java index c0e8ec36..ad5ed584 100644 --- a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/query/CakeQueryRepository.java +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/query/CakeQueryRepository.java @@ -2,6 +2,7 @@ import static com.cakk.domain.mysql.entity.cake.QCake.*; import static com.cakk.domain.mysql.entity.cake.QCakeCategory.*; +import static com.cakk.domain.mysql.entity.cake.QCakeHeart.*; import static com.cakk.domain.mysql.entity.cake.QCakeTag.*; import static com.cakk.domain.mysql.entity.cake.QTag.*; import static com.cakk.domain.mysql.entity.shop.QCakeShop.*; @@ -168,7 +169,15 @@ public CakeDetailParam searchCakeDetailById(final Long cakeId) { tag.tagName) ) ))); - return results.isEmpty() ? null : results.get(0); + return results.isEmpty() ? null : results.getFirst(); + } + + public Cake searchByIdWithHeart(final Long cakeId) { + return queryFactory + .selectFrom(cake) + .leftJoin(cake.cakeHearts, cakeHeart).fetchJoin() + .where(cake.id.eq(cakeId)) + .fetchOne(); } private BooleanExpression ltCakeId(final Long cakeId) { diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/query/CakeShopQueryRepository.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/query/CakeShopQueryRepository.java index 47d78047..3df25714 100644 --- a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/query/CakeShopQueryRepository.java +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/query/CakeShopQueryRepository.java @@ -202,6 +202,22 @@ public List searchByShopIds(List shopIds) { .fetch(); } + public CakeShop searchByIdWithHeart(Long cakeShopId) { + return queryFactory + .selectFrom(cakeShop) + .leftJoin(cakeShop.shopHearts).fetchJoin() + .where(cakeShop.id.eq(cakeShopId)) + .fetchOne(); + } + + public CakeShop searchByIdWithLike(Long cakeShopId) { + return queryFactory + .selectFrom(cakeShop) + .leftJoin(cakeShop.shopLikes).fetchJoin() + .where(cakeShop.id.eq(cakeShopId)) + .fetchOne(); + } + private BooleanExpression eqCakeShopId(Long cakeShopId) { return cakeShop.id.eq(cakeShopId); } diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/reader/CakeHeartReader.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/reader/CakeHeartReader.java index 5d196f52..8fe90668 100644 --- a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/reader/CakeHeartReader.java +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/reader/CakeHeartReader.java @@ -6,9 +6,6 @@ import com.cakk.domain.mysql.annotation.Reader; import com.cakk.domain.mysql.dto.param.like.HeartCakeImageResponseParam; -import com.cakk.domain.mysql.entity.cake.Cake; -import com.cakk.domain.mysql.entity.cake.CakeHeart; -import com.cakk.domain.mysql.entity.user.User; import com.cakk.domain.mysql.repository.jpa.CakeHeartJpaRepository; import com.cakk.domain.mysql.repository.query.CakeHeartQueryRepository; @@ -26,12 +23,4 @@ public List searchCakeImagesByCursorAndHeart( ) { return cakeHeartQueryRepository.searchCakeImagesByCursorAndHeart(cakeHeartId, userId, pageSize); } - - public CakeHeart findOrNullByUserAndCake(final User user, final Cake cake) { - return cakeHeartJpaRepository.findByUserAndCake(user, cake).orElse(null); - } - - public boolean existsByUserAndCake(final User user, final Cake cake) { - return cakeHeartJpaRepository.existsByUserAndCake(user, cake); - } } diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/reader/CakeReader.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/reader/CakeReader.java index 99f2981e..36251eb0 100644 --- a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/reader/CakeReader.java +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/reader/CakeReader.java @@ -29,6 +29,16 @@ public Cake findById(final Long cakeId) { return cakeJpaRepository.findById(cakeId).orElseThrow(() -> new CakkException(ReturnCode.NOT_EXIST_CAKE)); } + public Cake findByIdWithHeart(final Long cakeId) { + final Cake cake = cakeQueryRepository.searchByIdWithHeart(cakeId); + + if (isNull(cake)) { + throw new CakkException(ReturnCode.NOT_EXIST_CAKE); + } + + return cake; + } + public List searchCakeImagesByCursorAndCategory( final Long cakeId, final CakeDesignCategory category, diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/reader/CakeShopReader.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/reader/CakeShopReader.java index 2fd16a2a..dd89849a 100644 --- a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/reader/CakeShopReader.java +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/reader/CakeShopReader.java @@ -35,6 +35,26 @@ public CakeShop findById(final Long cakeShopId) { return cakeShopJpaRepository.findById(cakeShopId).orElseThrow(() -> new CakkException(ReturnCode.NOT_EXIST_CAKE_SHOP)); } + public CakeShop findByIdWithHeart(final Long cakeShopId) { + final CakeShop cakeShop = cakeShopQueryRepository.searchByIdWithHeart(cakeShopId); + + if (isNull(cakeShop)) { + throw new CakkException(ReturnCode.NOT_EXIST_CAKE_SHOP); + } + + return cakeShop; + } + + public CakeShop findByIdWithLike(final Long cakeShopId) { + final CakeShop cakeShop = cakeShopQueryRepository.searchByIdWithLike(cakeShopId); + + if (isNull(cakeShop)) { + throw new CakkException(ReturnCode.NOT_EXIST_CAKE_SHOP); + } + + return cakeShop; + } + public CakeShopSimpleParam searchSimpleById(final Long cakeShopId) { final CakeShopSimpleParam response = cakeShopQueryRepository.searchSimpleById(cakeShopId); diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/writer/CakeHeartWriter.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/writer/CakeHeartWriter.java index 0586fd56..41e0a9e9 100644 --- a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/writer/CakeHeartWriter.java +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/writer/CakeHeartWriter.java @@ -7,7 +7,6 @@ import lombok.RequiredArgsConstructor; import com.cakk.domain.mysql.annotation.Writer; -import com.cakk.domain.mysql.entity.cake.Cake; import com.cakk.domain.mysql.entity.cake.CakeHeart; import com.cakk.domain.mysql.entity.user.User; import com.cakk.domain.mysql.repository.jpa.CakeHeartJpaRepository; @@ -18,14 +17,6 @@ public class CakeHeartWriter { private final CakeHeartJpaRepository cakeHeartJpaRepository; - public void heartOrCancel(final CakeHeart cakeHeart, final User user, final Cake cake) { - if (isNull(cakeHeart)) { - this.heart(cake, user); - } else { - this.cancelHeart(cakeHeart); - } - } - public void deleteAllByUser(final User user) { final List cakeHearts = cakeHeartJpaRepository.findAllByUser(user); @@ -35,18 +26,4 @@ public void deleteAllByUser(final User user) { cakeHeartJpaRepository.deleteAllInBatch(cakeHearts); } - - private void heart(final Cake cake, final User user) { - final CakeHeart cakeHeart = new CakeHeart(cake, user); - - cakeHeartJpaRepository.save(cakeHeart); - cake.increaseHeartCount(); - } - - private void cancelHeart(final CakeHeart cakeHeart) { - final Cake cake = cakeHeart.getCake(); - - cakeHeartJpaRepository.delete(cakeHeart); - cake.decreaseHeartCount(); - } } diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/writer/CakeShopHeartWriter.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/writer/CakeShopHeartWriter.java index 8e182a82..7c35e04f 100644 --- a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/writer/CakeShopHeartWriter.java +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/writer/CakeShopHeartWriter.java @@ -1,13 +1,10 @@ package com.cakk.domain.mysql.repository.writer; -import static java.util.Objects.*; - import java.util.List; import lombok.RequiredArgsConstructor; import com.cakk.domain.mysql.annotation.Writer; -import com.cakk.domain.mysql.entity.shop.CakeShop; import com.cakk.domain.mysql.entity.shop.CakeShopHeart; import com.cakk.domain.mysql.entity.user.User; import com.cakk.domain.mysql.repository.jpa.CakeShopHeartJpaRepository; @@ -18,14 +15,6 @@ public class CakeShopHeartWriter { private final CakeShopHeartJpaRepository cakeShopHeartJpaRepository; - public void heartOrCancel(final CakeShopHeart cakeShopHeart, final User user, final CakeShop cakeShop) { - if (isNull(cakeShopHeart)) { - this.heart(cakeShop, user); - } else { - this.cancelHeart(cakeShopHeart); - } - } - public void deleteAllByUser(final User user) { final List cakeShopHearts = cakeShopHeartJpaRepository.findAllByUser(user); @@ -35,18 +24,4 @@ public void deleteAllByUser(final User user) { cakeShopHeartJpaRepository.deleteAllInBatch(cakeShopHearts); } - - private void heart(final CakeShop cakeShop, final User user) { - final CakeShopHeart cakeShopHeart = new CakeShopHeart(cakeShop, user); - - cakeShopHeartJpaRepository.save(cakeShopHeart); - cakeShop.increaseHeartCount(); - } - - private void cancelHeart(final CakeShopHeart cakeShopHeart) { - final CakeShop cakeShop = cakeShopHeart.getCakeShop(); - - cakeShopHeartJpaRepository.delete(cakeShopHeart); - cakeShop.decreaseHeartCount(); - } } diff --git a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/writer/CakeShopLikeWriter.java b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/writer/CakeShopLikeWriter.java index 85bbed88..486e1a5f 100644 --- a/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/writer/CakeShopLikeWriter.java +++ b/cakk-domain/mysql/src/main/java/com/cakk/domain/mysql/repository/writer/CakeShopLikeWriter.java @@ -4,20 +4,13 @@ import com.cakk.domain.mysql.annotation.Writer; import com.cakk.domain.mysql.entity.shop.CakeShop; -import com.cakk.domain.mysql.entity.shop.CakeShopLike; import com.cakk.domain.mysql.entity.user.User; -import com.cakk.domain.mysql.repository.jpa.CakeShopLikeJpaRepository; @Writer @RequiredArgsConstructor public class CakeShopLikeWriter { - private final CakeShopLikeJpaRepository cakeShopLikeJpaRepository; - public void like(final CakeShop cakeShop, final User user) { - final CakeShopLike cakeShopLike = new CakeShopLike(cakeShop, user); - - cakeShopLikeJpaRepository.save(cakeShopLike); - cakeShop.increaseLikeCount(); + user.likeCakeShop(cakeShop); } } diff --git a/cakk-domain/mysql/src/test/java/com/cakk/domain/base/DomainTest.java b/cakk-domain/mysql/src/test/java/com/cakk/domain/base/DomainTest.java index 25fef877..d02275ba 100644 --- a/cakk-domain/mysql/src/test/java/com/cakk/domain/base/DomainTest.java +++ b/cakk-domain/mysql/src/test/java/com/cakk/domain/base/DomainTest.java @@ -19,6 +19,7 @@ import com.cakk.domain.mysql.bo.user.DefaultVerificationPolicy; import com.cakk.domain.mysql.bo.user.VerificationPolicy; import com.cakk.domain.mysql.dto.param.user.CertificationParam; +import com.cakk.domain.mysql.entity.cake.Cake; import com.cakk.domain.mysql.entity.shop.CakeShop; import com.cakk.domain.mysql.entity.user.BusinessInformation; import com.cakk.domain.mysql.entity.user.User; @@ -74,6 +75,8 @@ protected CakeShop getCakeShopFixture() { .set("shopName", Arbitraries.strings().withCharRange('a', 'z').ofMaxLength(30)) .set("shopBio", Arbitraries.strings().withCharRange('a', 'z').ofMaxLength(40)) .set("shopDescription", Arbitraries.strings().withCharRange('a', 'z').ofMaxLength(500)) + .set("likeCount", 0) + .set("heartCount", 0) .set("location", supplyPointBy( Arbitraries.doubles().between(-90, 90).sample(), Arbitraries.doubles().between(-180, 180).sample()) @@ -100,4 +103,11 @@ protected CertificationParam getCertificationParamFixtureWithUser(User user) { .set("user", user) .sample(); } + + protected Cake getCakeFixture() { + return getConstructorMonkey().giveMeBuilder(Cake.class) + .set("cakeImageUrl", Arbitraries.strings().withCharRange('a', 'z').ofMaxLength(50)) + .set("cakeShop", Values.just(getCakeShopFixture())) + .sample(); + } } diff --git a/cakk-domain/mysql/src/test/java/com/cakk/domain/facade/user/UserHeartFacadeTest.java b/cakk-domain/mysql/src/test/java/com/cakk/domain/facade/user/UserHeartFacadeTest.java new file mode 100644 index 00000000..4bd57d61 --- /dev/null +++ b/cakk-domain/mysql/src/test/java/com/cakk/domain/facade/user/UserHeartFacadeTest.java @@ -0,0 +1,76 @@ +package com.cakk.domain.facade.user; + +import static org.assertj.core.api.Assertions.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.cakk.common.enums.Role; +import com.cakk.domain.base.DomainTest; +import com.cakk.domain.mysql.entity.cake.Cake; +import com.cakk.domain.mysql.entity.shop.CakeShop; +import com.cakk.domain.mysql.entity.user.User; +import com.cakk.domain.mysql.facade.user.UserHeartFacade; + +class UserHeartFacadeTest extends DomainTest { + + private final UserHeartFacade userHeartFacade = new UserHeartFacade(); + + @Test + @DisplayName("케이크 하트를 성공한다") + void heartCake() { + //given + final User user = getUserFixture(Role.USER); + final Cake cake = getCakeFixture(); + + //when + userHeartFacade.heartCake(user, cake); + + //then + assertThat(cake.isHeartedBy(user)).isTrue(); + } + + @Test + @DisplayName("케이크 하트 취소를 성공한다") + void heartCake2() { + //given + final User user = getUserFixture(Role.USER); + final Cake cake = getCakeFixture(); + userHeartFacade.heartCake(user, cake); + + //when + userHeartFacade.heartCake(user, cake); + + //then + assertThat(cake.isHeartedBy(user)).isFalse(); + } + + @Test + @DisplayName("케이크 샵 하트를 성공한다") + void heartCakeShop() { + //given + final User user = getUserFixture(Role.USER); + final CakeShop cakeShop = getCakeShopFixture(); + + //when + userHeartFacade.heartCakeShop(user, cakeShop); + + //then + assertThat(cakeShop.isHeartedBy(user)).isTrue(); + } + + @Test + @DisplayName("케이크 샵 하트 취소를 성공한다") + void heartCakeShop2() { + //given + final User user = getUserFixture(Role.USER); + final CakeShop cakeShop = getCakeShopFixture(); + userHeartFacade.heartCakeShop(user, cakeShop); + + //when + userHeartFacade.heartCakeShop(user, cakeShop); + + //then + assertThat(cakeShop.isHeartedBy(user)).isFalse(); + } +} diff --git a/cakk-domain/mysql/src/test/java/com/cakk/domain/facade/user/UserLikeFacadeTest.java b/cakk-domain/mysql/src/test/java/com/cakk/domain/facade/user/UserLikeFacadeTest.java new file mode 100644 index 00000000..27dfc58d --- /dev/null +++ b/cakk-domain/mysql/src/test/java/com/cakk/domain/facade/user/UserLikeFacadeTest.java @@ -0,0 +1,55 @@ +package com.cakk.domain.facade.user; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.util.stream.IntStream; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +import com.cakk.common.enums.ReturnCode; +import com.cakk.common.enums.Role; +import com.cakk.common.exception.CakkException; +import com.cakk.domain.base.DomainTest; +import com.cakk.domain.mysql.entity.shop.CakeShop; +import com.cakk.domain.mysql.entity.user.User; +import com.cakk.domain.mysql.facade.user.UserLikeFacade; + +class UserLikeFacadeTest extends DomainTest { + + private final UserLikeFacade userLikeFacade = new UserLikeFacade(); + + @Test + @DisplayName("케이크 샵 기대돼요 동작을 성공한다") + void likeCakeShop() { + //given + final User user = getUserFixture(Role.USER); + final CakeShop cakeShop = getCakeShopFixture(); + + //when + userLikeFacade.likeCakeShop(user, cakeShop); + + //then + assertThat(cakeShop.getLikeCount()).isEqualTo(1); + final boolean liked = cakeShop.getShopLikes().stream().anyMatch(it -> it.getUser().equals(user)); + assertTrue(liked); + } + + @Test + @DisplayName("케이크 샵 기대돼요 동작이 50회 초과로 실패한다") + void heartCakeShop2() { + // given + final User user = getUserFixture(Role.USER); + final CakeShop cakeShop = getCakeShopFixture(); + + IntStream.range(0, 50).forEach(i -> userLikeFacade.likeCakeShop(user, cakeShop)); + + // when & then + assertThrows( + CakkException.class, + () -> userLikeFacade.likeCakeShop(user, cakeShop), + ReturnCode.MAX_CAKE_SHOP_LIKE.getMessage() + ); + } +} diff --git a/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/CakeShopViewsRedisRepository.java b/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/CakeShopViewsRedisRepository.java index cfc78f47..ae0e99cc 100644 --- a/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/CakeShopViewsRedisRepository.java +++ b/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/CakeShopViewsRedisRepository.java @@ -6,34 +6,34 @@ import com.cakk.common.enums.RedisKey; import com.cakk.domain.redis.annotation.RedisRepository; -import com.cakk.domain.redis.template.impl.RedisLongZSetTemplate; +import com.cakk.domain.redis.template.RedisZSetTemplate; @RedisRepository @RequiredArgsConstructor public class CakeShopViewsRedisRepository { - private final RedisLongZSetTemplate redisLongZSetTemplate; + private final RedisZSetTemplate redisZSetTemplate; private final String key = RedisKey.VIEWS_CAKE_SHOP.getValue(); public void saveOrIncreaseSearchCount(final Long value) { - redisLongZSetTemplate.save(key, value); - redisLongZSetTemplate.increaseScore(key, value, 1); + redisZSetTemplate.save(key, value); + redisZSetTemplate.increaseScore(key, value, 1); } public List findTopShopIdsByOffsetAndCount(final long offset, final long count) { - return redisLongZSetTemplate.findAllReverseScore(key, offset, count); + return redisZSetTemplate.findAllReverseScore(key, offset, count); } public List findAll() { - return redisLongZSetTemplate.findAll(key); + return redisZSetTemplate.findAll(key); } public void deleteByValue(final Long value) { - redisLongZSetTemplate.remove(key, value); + redisZSetTemplate.remove(key, value); } public void clear() { - redisLongZSetTemplate.removeAll(key); + redisZSetTemplate.removeAll(key); } } diff --git a/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/CakeViewsRedisRepository.java b/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/CakeViewsRedisRepository.java index dfe358b4..a8dbbf98 100644 --- a/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/CakeViewsRedisRepository.java +++ b/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/CakeViewsRedisRepository.java @@ -6,34 +6,34 @@ import com.cakk.common.enums.RedisKey; import com.cakk.domain.redis.annotation.RedisRepository; -import com.cakk.domain.redis.template.impl.RedisLongZSetTemplate; +import com.cakk.domain.redis.template.RedisZSetTemplate; @RedisRepository @RequiredArgsConstructor public class CakeViewsRedisRepository { - private final RedisLongZSetTemplate redisLongZSetTemplate; + private final RedisZSetTemplate redisZSetTemplate; private final String key = RedisKey.VIEWS_CAKE.getValue(); public void saveOrIncreaseSearchCount(final Long value) { - redisLongZSetTemplate.save(key, value); - redisLongZSetTemplate.increaseScore(key, value, 1); + redisZSetTemplate.save(key, value); + redisZSetTemplate.increaseScore(key, value, 1); } public List findTopCakeIdsByOffsetAndCount(final long offset, final long count) { - return redisLongZSetTemplate.findAllReverseScore(key, offset, count); + return redisZSetTemplate.findAllReverseScore(key, offset, count); } public List findAll() { - return redisLongZSetTemplate.findAll(key); + return redisZSetTemplate.findAll(key); } public void deleteByValue(final Long value) { - redisLongZSetTemplate.remove(key, value); + redisZSetTemplate.remove(key, value); } public void clear() { - redisLongZSetTemplate.removeAll(key); + redisZSetTemplate.removeAll(key); } } diff --git a/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/EmailVerificationRedisRepository.java b/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/EmailVerificationRedisRepository.java index 1da324f1..16469bf9 100644 --- a/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/EmailVerificationRedisRepository.java +++ b/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/EmailVerificationRedisRepository.java @@ -6,30 +6,30 @@ import com.cakk.common.enums.RedisKey; import com.cakk.domain.redis.annotation.RedisRepository; -import com.cakk.domain.redis.template.impl.RedisStringValueTemplate; +import com.cakk.domain.redis.template.RedisValueTemplate; @RedisRepository @RequiredArgsConstructor public class EmailVerificationRedisRepository { - private final RedisStringValueTemplate redisStringValueTemplate; + private final RedisValueTemplate redisValueTemplate; private final String key = RedisKey.EMAIL_VERIFICATION.getValue(); public void save(final String email, final String verificationCode) { deleteByEmail(email); - redisStringValueTemplate.save(key + email, verificationCode, 180, TimeUnit.SECONDS); + redisValueTemplate.save(key + email, verificationCode, 180, TimeUnit.SECONDS); } public String findCodeByEmail(final String email) { - return redisStringValueTemplate.findByKey(key + email); + return redisValueTemplate.findByKey(key + email); } public Boolean existByEmail(final String email) { - return redisStringValueTemplate.existByKey(key + email); + return redisValueTemplate.existByKey(key + email); } public Boolean deleteByEmail(final String email) { - return redisStringValueTemplate.delete(key + email); + return redisValueTemplate.delete(key + email); } } diff --git a/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/KeywordRedisRepository.java b/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/KeywordRedisRepository.java index 73bd14eb..b3808366 100644 --- a/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/KeywordRedisRepository.java +++ b/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/KeywordRedisRepository.java @@ -6,34 +6,34 @@ import com.cakk.common.enums.RedisKey; import com.cakk.domain.redis.annotation.RedisRepository; -import com.cakk.domain.redis.template.impl.RedisStringZSetTemplate; +import com.cakk.domain.redis.template.RedisZSetTemplate; @RedisRepository @RequiredArgsConstructor public class KeywordRedisRepository { - private final RedisStringZSetTemplate redisStringZSetTemplate; + private final RedisZSetTemplate redisZSetTemplate; private final String key = RedisKey.SEARCH_KEYWORD.getValue(); public void saveOrIncreaseSearchCount(final String value) { - redisStringZSetTemplate.save(key, value); - redisStringZSetTemplate.increaseScore(key, value, 1); + redisZSetTemplate.save(key, value); + redisZSetTemplate.increaseScore(key, value, 1); } public List findTopSearchedLimitCount(final long count) { - return redisStringZSetTemplate.findAllReverseScore(key, count); + return redisZSetTemplate.findAllReverseScore(key, count); } public List findAll() { - return redisStringZSetTemplate.findAll(key); + return redisZSetTemplate.findAll(key); } public void deleteByValue(final String value) { - redisStringZSetTemplate.remove(key, value); + redisZSetTemplate.remove(key, value); } public void clear() { - redisStringZSetTemplate.removeAll(key); + redisZSetTemplate.removeAll(key); } } diff --git a/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/TokenRedisRepository.java b/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/TokenRedisRepository.java index a66b0a98..99621fe5 100644 --- a/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/TokenRedisRepository.java +++ b/cakk-domain/redis/src/main/java/com/cakk/domain/redis/repository/TokenRedisRepository.java @@ -6,25 +6,25 @@ import com.cakk.common.enums.RedisKey; import com.cakk.domain.redis.annotation.RedisRepository; -import com.cakk.domain.redis.template.impl.RedisStringValueTemplate; +import com.cakk.domain.redis.template.RedisValueTemplate; @RedisRepository @RequiredArgsConstructor public class TokenRedisRepository { - private final RedisStringValueTemplate redisStringValueTemplate; + private final RedisValueTemplate redisValueTemplate; private final String key = RedisKey.REFRESH_TOKEN.getValue(); public void registerBlackList(final String token, final long timeout) { - redisStringValueTemplate.save(key + token, "token", timeout, TimeUnit.MILLISECONDS); + redisValueTemplate.save(key + token, "token", timeout, TimeUnit.MILLISECONDS); } public Boolean isBlackListToken(final String token) { - return redisStringValueTemplate.existByKey(key + token); + return redisValueTemplate.existByKey(key + token); } public void deleteByToken(final String token) { - redisStringValueTemplate.delete(key + token); + redisValueTemplate.delete(key + token); } }