diff --git a/src/main/java/com/odiga/fiesta/auth/service/AuthService.java b/src/main/java/com/odiga/fiesta/auth/service/AuthService.java index 1dd039bc..f204a5fc 100644 --- a/src/main/java/com/odiga/fiesta/auth/service/AuthService.java +++ b/src/main/java/com/odiga/fiesta/auth/service/AuthService.java @@ -13,8 +13,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import com.odiga.fiesta.auth.domain.Authority; import com.odiga.fiesta.auth.dto.KakaoProfile; +import com.odiga.fiesta.badge.service.BadgeService; import com.odiga.fiesta.common.error.exception.CustomException; import com.odiga.fiesta.common.jwt.TokenProvider; import com.odiga.fiesta.common.util.HttpClientUtil; @@ -40,6 +40,7 @@ public class AuthService { private final UserRepository userRepository; private final RoleRepository roleRepository; + private final BadgeService badgeService; private final TokenProvider tokenProvider; private final NicknameUtils nicknameUtils; @@ -72,6 +73,9 @@ public LoginResponse kakaoLogin(String accessTokenByClient) { String refreshToken = issueRefreshToken(user); boolean isProfileRegistered = !isNull(user.getUserTypeId()); + // 뱃지 수여 + badgeService.giveUserBadge(user.getId()); + return LoginResponse.builder() .accessToken(accessToken) .refreshToken(refreshToken) diff --git a/src/main/java/com/odiga/fiesta/badge/domain/Badge.java b/src/main/java/com/odiga/fiesta/badge/domain/Badge.java new file mode 100644 index 00000000..60cab0b3 --- /dev/null +++ b/src/main/java/com/odiga/fiesta/badge/domain/Badge.java @@ -0,0 +1,48 @@ +package com.odiga.fiesta.badge.domain; + +import static jakarta.persistence.GenerationType.*; +import static lombok.AccessLevel.*; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.SuperBuilder; + +@Entity +@AllArgsConstructor +@Getter +@SuperBuilder +@NoArgsConstructor(access = PROTECTED) +@ToString +@Table(name = "badge", indexes = { + @Index(name = "idx_badge_type", columnList = "type") +}) +public class Badge { + + @Id + @GeneratedValue(strategy = IDENTITY) + @Column(name = "badge_id") + private Long id; + + @Column(nullable = false) + private String name; + + @Column(nullable = false) + private String description; + + @Column(name = "image_url", nullable = false) + private String imageUrl; + + @Enumerated(EnumType.STRING) + @Column(name = "type", nullable = false) + private BadgeType type; +} diff --git a/src/main/java/com/odiga/fiesta/badge/domain/BadgeConstants.java b/src/main/java/com/odiga/fiesta/badge/domain/BadgeConstants.java new file mode 100644 index 00000000..cbaa9ac8 --- /dev/null +++ b/src/main/java/com/odiga/fiesta/badge/domain/BadgeConstants.java @@ -0,0 +1,29 @@ +package com.odiga.fiesta.badge.domain; + +import static lombok.AccessLevel.*; + +import lombok.NoArgsConstructor; + +@NoArgsConstructor(access = PRIVATE) +public class BadgeConstants { + + public static final Long USER_JOIN_BADGE_ID = 1L; + + // 리뷰 작성 관련 뱃지 + public static final Long FIRST_REVIEW_BADGE_ID = 2L; + public static final Long PASSIONATE_REVIEWER_BADGE_ID = 4L; + public static final Long HISTORY_LOVER_BADGE_ID = 5L; + public static final Long MUSIC_LOVER_BADGE_ID = 6L; + public static final Long ACTIVITY_BADGE_ID = 7L; + public static final Long FOODIE_LOVER_BADGE_ID = 8L; + public static final Long MOVIE_LOVER_BADGE_ID = 9L; + public static final Long FIREWORKS_LOVER_BADGE_ID = 10L; + public static final Long NATURE_LOVER_BADGE_ID = 11L; + public static final Long NIGHT_LOVER_BADGE_ID = 12L; + public static final Long ART_LOVER_BADGE_ID = 13L; + public static final Long CULTURE_LOVER_BADGE_ID = 14L; + public static final Long UNIQUE_LOVER_BADGE_ID = 15L; + + // 페스티벌 등록 관련 뱃지 + public static final Long FIRST_FESTIVAL_BADGE_ID = 3L; +} diff --git a/src/main/java/com/odiga/fiesta/badge/domain/BadgeType.java b/src/main/java/com/odiga/fiesta/badge/domain/BadgeType.java new file mode 100644 index 00000000..a6213164 --- /dev/null +++ b/src/main/java/com/odiga/fiesta/badge/domain/BadgeType.java @@ -0,0 +1,6 @@ +package com.odiga.fiesta.badge.domain; + +public enum BadgeType { + + USER, REVIEW, FESTIVAL +} diff --git a/src/main/java/com/odiga/fiesta/badge/domain/UserBadge.java b/src/main/java/com/odiga/fiesta/badge/domain/UserBadge.java new file mode 100644 index 00000000..d519443f --- /dev/null +++ b/src/main/java/com/odiga/fiesta/badge/domain/UserBadge.java @@ -0,0 +1,40 @@ +package com.odiga.fiesta.badge.domain; + +import static jakarta.persistence.GenerationType.*; +import static lombok.AccessLevel.*; + +import com.odiga.fiesta.common.domain.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +@Entity +@Getter +@AllArgsConstructor +@SuperBuilder +@NoArgsConstructor(access = PROTECTED) +@Table(name = "user_badge", indexes = { + @Index(name = "idx_user_badge_user_id", columnList = "user_id"), + @Index(name = "idx_user_badge_badge_id", columnList = "badge_id") +}) +public class UserBadge extends BaseEntity { + + @Id + @GeneratedValue(strategy = IDENTITY) + @Column(name = "user_badge_id") + private Long id; + + @Column(name = "badge_id") + private Long badgeId; + + @Column(name = "user_id") + private Long userId; +} diff --git a/src/main/java/com/odiga/fiesta/badge/repository/BadgeCustomRepository.java b/src/main/java/com/odiga/fiesta/badge/repository/BadgeCustomRepository.java new file mode 100644 index 00000000..bd8293f4 --- /dev/null +++ b/src/main/java/com/odiga/fiesta/badge/repository/BadgeCustomRepository.java @@ -0,0 +1,10 @@ +package com.odiga.fiesta.badge.repository; + +import java.util.List; + +import com.odiga.fiesta.user.dto.response.UserBadgeResponse; + +public interface BadgeCustomRepository { + + List findUserBadges(Long userId); +} diff --git a/src/main/java/com/odiga/fiesta/badge/repository/BadgeCustomRepositoryImpl.java b/src/main/java/com/odiga/fiesta/badge/repository/BadgeCustomRepositoryImpl.java new file mode 100644 index 00000000..aee6eb64 --- /dev/null +++ b/src/main/java/com/odiga/fiesta/badge/repository/BadgeCustomRepositoryImpl.java @@ -0,0 +1,45 @@ +package com.odiga.fiesta.badge.repository; + +import static com.odiga.fiesta.badge.domain.QBadge.*; +import static com.odiga.fiesta.badge.domain.QUserBadge.*; + +import java.util.List; + +import com.odiga.fiesta.user.dto.response.UserBadgeResponse; +import com.querydsl.core.types.Projections; +import com.querydsl.core.types.dsl.CaseBuilder; +import com.querydsl.jpa.impl.JPAQueryFactory; + +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +public class BadgeCustomRepositoryImpl implements BadgeCustomRepository { + + private final JPAQueryFactory queryFactory; + + @Override + public List findUserBadges(Long userId) { + + return queryFactory.select( + Projections.fields( + UserBadgeResponse.class, + badge.id.as("badgeId"), + badge.name.as("badgeName"), + badge.description, + badge.imageUrl, + new CaseBuilder() + .when(userBadge.isNull()) // userBadge가 null이면 false + .then(false) + .when(userBadge.userId.isNull()) + .then(false) + .when(userBadge.userId.eq(userId)) + .then(true) + .otherwise(false).as("isAcquired") + ) + ) + .from(badge) + .leftJoin(userBadge) + .on(badge.id.eq(userBadge.badgeId)) // 단순 조인 + .fetch(); + } +} diff --git a/src/main/java/com/odiga/fiesta/badge/repository/BadgeRepository.java b/src/main/java/com/odiga/fiesta/badge/repository/BadgeRepository.java new file mode 100644 index 00000000..6d29eb18 --- /dev/null +++ b/src/main/java/com/odiga/fiesta/badge/repository/BadgeRepository.java @@ -0,0 +1,16 @@ +package com.odiga.fiesta.badge.repository; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import com.odiga.fiesta.badge.domain.Badge; +import com.odiga.fiesta.badge.domain.BadgeType; + +public interface BadgeRepository extends JpaRepository, BadgeCustomRepository { + + @Query("SELECT b.id FROM Badge b WHERE b.type = :type") + List findIdsByType(@Param("type") BadgeType type); +} diff --git a/src/main/java/com/odiga/fiesta/badge/repository/UserBadgeRepository.java b/src/main/java/com/odiga/fiesta/badge/repository/UserBadgeRepository.java new file mode 100644 index 00000000..75db65a6 --- /dev/null +++ b/src/main/java/com/odiga/fiesta/badge/repository/UserBadgeRepository.java @@ -0,0 +1,15 @@ +package com.odiga.fiesta.badge.repository; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; + +import com.odiga.fiesta.badge.domain.UserBadge; + +public interface UserBadgeRepository extends JpaRepository { + + @Query("SELECT ub.badgeId FROM UserBadge ub WHERE ub.userId= :userId AND ub.badgeId IN :badgeIds") + List findBadgeIdByUserIdAndBadgeIdIn(@Param("userId") Long userId, @Param("badgeIds") List badgeIds); +} diff --git a/src/main/java/com/odiga/fiesta/badge/service/BadgeService.java b/src/main/java/com/odiga/fiesta/badge/service/BadgeService.java new file mode 100644 index 00000000..50dc0d92 --- /dev/null +++ b/src/main/java/com/odiga/fiesta/badge/service/BadgeService.java @@ -0,0 +1,185 @@ +package com.odiga.fiesta.badge.service; + +import static com.odiga.fiesta.badge.domain.BadgeConstants.*; +import static com.odiga.fiesta.category.domain.CategoryConstants.*; +import static com.odiga.fiesta.common.error.ErrorCode.*; +import static java.util.Objects.*; + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CompletableFuture; + +import org.springframework.scheduling.annotation.Async; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.odiga.fiesta.badge.domain.BadgeType; +import com.odiga.fiesta.badge.domain.UserBadge; +import com.odiga.fiesta.badge.repository.BadgeRepository; +import com.odiga.fiesta.badge.repository.UserBadgeRepository; +import com.odiga.fiesta.common.error.exception.CustomException; +import com.odiga.fiesta.festival.repository.FestivalRepository; +import com.odiga.fiesta.review.repository.ReviewRepository; +import com.odiga.fiesta.user.domain.User; +import com.odiga.fiesta.user.dto.response.UserBadgeResponse; +import com.odiga.fiesta.user.repository.UserRepository; + +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; + +@Service +@RequiredArgsConstructor +@Slf4j +@Transactional(readOnly = true) +public class BadgeService { + + public static final int PASSIONATE_REVIEWER_THRESHOLD = 5; + public static final int REVIEW_BADGE_COUNT_THRESHOLD = 2; + + private final BadgeRepository badgeRepository; + private final UserBadgeRepository userBadgeRepository; + private final UserRepository userRepository; + private final ReviewRepository reviewRepository; + private final FestivalRepository festivalRepository; + + // 현재 유저의 뱃지 조회 + public List getUserBadges(User user) { + validateUser(user); + return badgeRepository.findUserBadges(user.getId()); + } + + @Async + @Transactional + public CompletableFuture> giveFestivalBadge(Long userId) { + return CompletableFuture.completedFuture(giveBadge(userId, BadgeType.FESTIVAL)); + } + + @Async + @Transactional + public CompletableFuture> giveReviewBadge(Long userId) { + return CompletableFuture.completedFuture(giveBadge(userId, BadgeType.REVIEW)); + } + + @Async + @Transactional + public CompletableFuture> giveUserBadge(Long userId) { + return CompletableFuture.completedFuture(giveBadge(userId, BadgeType.USER)); + } + + private List giveBadge(Long userId, BadgeType badgeType) { + List givenBadgeIds = new ArrayList<>(); + Map userBadgeMap = new HashMap<>(); + + // 특정 뱃지 타입에 대한 id를 조회 + List badgeIds = badgeRepository.findIdsByType(badgeType); + for (Long badgeId : badgeIds) { + userBadgeMap.put(badgeId, false); + } + + // 현재 유저가 해당 뱃지를 가지고 있는지 확인 + List currentUserBadgeIds = userBadgeRepository.findBadgeIdByUserIdAndBadgeIdIn(userId, badgeIds); + for (Long badgeId : currentUserBadgeIds) { + userBadgeMap.put(badgeId, true); + } + + // 조건을 확인하고 뱃지를 수여 + for (Long badgeId : badgeIds) { + if (isUserNotOwnedBadge(badgeId, userBadgeMap) && isBadgeCondition(userId, badgeId)) { + userBadgeRepository.save(UserBadge.builder().badgeId(badgeId).userId(userId).build()); + givenBadgeIds.add(badgeId); + } + } + + return givenBadgeIds; + } + + private boolean isBadgeCondition(long userId, long badgeId) { + + if (badgeId == USER_JOIN_BADGE_ID) { + // TODO: soft delete 전환 시, 탈퇴한 회원인지 검증 필요 + userRepository.findById(userId).orElseThrow(() -> new IllegalArgumentException("해당 유저가 존재하지 않습니다.")); + return true; + } + + if (badgeId == FIRST_REVIEW_BADGE_ID) { + return reviewRepository.existsByUserId(userId); + } + + if (badgeId == PASSIONATE_REVIEWER_BADGE_ID) { + return reviewRepository.countByUserId(userId) >= PASSIONATE_REVIEWER_THRESHOLD; + } + + if (badgeId == HISTORY_LOVER_BADGE_ID) { + return reviewRepository.countByUserIdAndCategoryId(userId, CATEGORY_HISTORY) + >= REVIEW_BADGE_COUNT_THRESHOLD; + } + + if (badgeId == MUSIC_LOVER_BADGE_ID) { + return reviewRepository.countByUserIdAndCategoryId(userId, CATEGORY_MUSIC_AND_DANCE) + >= REVIEW_BADGE_COUNT_THRESHOLD; + } + + if (badgeId == ACTIVITY_BADGE_ID) { + return reviewRepository.countByUserIdAndCategoryId(userId, CATEGORY_ACTIVITY) + >= REVIEW_BADGE_COUNT_THRESHOLD; + } + + if (badgeId == FOODIE_LOVER_BADGE_ID) { + return reviewRepository.countByUserIdAndCategoryId(userId, CATEGORY_FOOD_AND_DRINK) + >= REVIEW_BADGE_COUNT_THRESHOLD; + } + + if (badgeId == MOVIE_LOVER_BADGE_ID) { + return reviewRepository.countByUserIdAndCategoryId(userId, CATEGORY_MOVIE) >= REVIEW_BADGE_COUNT_THRESHOLD; + } + + if (badgeId == FIREWORKS_LOVER_BADGE_ID) { + return reviewRepository.countByUserIdAndCategoryId(userId, CATEGORY_FIREWORKS) + >= REVIEW_BADGE_COUNT_THRESHOLD; + } + + if (badgeId == NATURE_LOVER_BADGE_ID) { + return reviewRepository.countByUserIdAndCategoryId(userId, CATEGORY_NATURE) >= REVIEW_BADGE_COUNT_THRESHOLD; + } + + if (badgeId == NIGHT_LOVER_BADGE_ID) { + return reviewRepository.countByUserIdAndCategoryId(userId, CATEGORY_NIGHT) >= REVIEW_BADGE_COUNT_THRESHOLD; + } + + if (badgeId == ART_LOVER_BADGE_ID) { + return reviewRepository.countByUserIdAndCategoryId(userId, CATEGORY_ART) >= REVIEW_BADGE_COUNT_THRESHOLD; + } + + if (badgeId == CULTURE_LOVER_BADGE_ID) { + return reviewRepository.countByUserIdAndCategoryId(userId, CATEGORY_CULTURE) + >= REVIEW_BADGE_COUNT_THRESHOLD; + } + + if (badgeId == UNIQUE_LOVER_BADGE_ID) { + return reviewRepository.countByUserIdAndCategoryId(userId, CATEGORY_UNIQUE_FESTIVAL) + >= REVIEW_BADGE_COUNT_THRESHOLD; + } + + if (badgeId == FIRST_FESTIVAL_BADGE_ID) { + return festivalRepository.existsByUserId(userId); + } + + return false; + } + + private static boolean isUserNotOwnedBadge(Long badgeId, Map userBadgeMap) { + return !userBadgeMap.get(badgeId); + } + + private void validateUser(User user) { + if (isNull(user) || isNull(user.getId())) { + throw new CustomException(USER_NOT_FOUND); + } + + if (!userRepository.existsById(user.getId())) { + throw new CustomException(USER_NOT_FOUND); + } + } +} diff --git a/src/main/java/com/odiga/fiesta/category/domain/CategoryConstants.java b/src/main/java/com/odiga/fiesta/category/domain/CategoryConstants.java new file mode 100644 index 00000000..d86c93c6 --- /dev/null +++ b/src/main/java/com/odiga/fiesta/category/domain/CategoryConstants.java @@ -0,0 +1,21 @@ +package com.odiga.fiesta.category.domain; + +public class CategoryConstants { + + public static final Long CATEGORY_CULTURE = 1L; + public static final Long CATEGORY_MUSIC_AND_DANCE = 2L; + public static final Long CATEGORY_MOVIE = 3L; + public static final Long CATEGORY_FOOD_AND_DRINK = 4L; + public static final Long CATEGORY_ACTIVITY = 5L; + public static final Long CATEGORY_ART = 6L; + public static final Long CATEGORY_HISTORY = 7L; + public static final Long CATEGORY_NATURE = 8L; + public static final Long CATEGORY_PETS = 9L; + public static final Long CATEGORY_NIGHT = 10L; + public static final Long CATEGORY_FIREWORKS = 11L; + public static final Long CATEGORY_UNIQUE_FESTIVAL = 12L; + + // 생성자를 private으로 하여 상수 클래스가 인스턴스화되지 않도록 방지 + private CategoryConstants() { + } +} diff --git a/src/main/java/com/odiga/fiesta/festival/domain/Festival.java b/src/main/java/com/odiga/fiesta/festival/domain/Festival.java index 715135f7..3372a81b 100644 --- a/src/main/java/com/odiga/fiesta/festival/domain/Festival.java +++ b/src/main/java/com/odiga/fiesta/festival/domain/Festival.java @@ -16,6 +16,8 @@ import jakarta.persistence.Entity; import jakarta.persistence.GeneratedValue; import jakarta.persistence.Id; +import jakarta.persistence.Index; +import jakarta.persistence.Table; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; @@ -27,7 +29,9 @@ @Getter @SuperBuilder @NoArgsConstructor(access = PROTECTED) -@ToString +@Table(name = "festival", indexes = { + @Index(name = "idx_festival_user_id", columnList = "user_id") +}) public class Festival extends BaseEntity { @Id diff --git a/src/main/java/com/odiga/fiesta/festival/repository/FestivalRepository.java b/src/main/java/com/odiga/fiesta/festival/repository/FestivalRepository.java index ee11b097..6c631d70 100644 --- a/src/main/java/com/odiga/fiesta/festival/repository/FestivalRepository.java +++ b/src/main/java/com/odiga/fiesta/festival/repository/FestivalRepository.java @@ -6,4 +6,5 @@ public interface FestivalRepository extends JpaRepository , FestivalCustomRepository { + boolean existsByUserId(long userId); } diff --git a/src/main/java/com/odiga/fiesta/festival/service/FestivalService.java b/src/main/java/com/odiga/fiesta/festival/service/FestivalService.java index 8f556dfe..03d3d164 100644 --- a/src/main/java/com/odiga/fiesta/festival/service/FestivalService.java +++ b/src/main/java/com/odiga/fiesta/festival/service/FestivalService.java @@ -26,6 +26,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import com.odiga.fiesta.badge.service.BadgeService; import com.odiga.fiesta.category.repository.CategoryRepository; import com.odiga.fiesta.common.PageResponse; import com.odiga.fiesta.common.error.exception.CustomException; @@ -97,6 +98,7 @@ public class FestivalService { private final FestivalModificationRequestRepository festivalModificationRequestRepository; private final UserTypeService userTypeService; + private final BadgeService badgeService; private final RedisUtils redisUtils; private final FileUtils fileUtils; @@ -121,6 +123,7 @@ public FestivalBasic createFestival(final Long userId, final FestivalCreateReque saveFestivalCategory(request.getCategoryIds(), festival); saveFestivalMood(request.getMoodIds(), festival); + badgeService.giveFestivalBadge(userId); return FestivalBasic.of(festival); } diff --git a/src/main/java/com/odiga/fiesta/review/domain/Review.java b/src/main/java/com/odiga/fiesta/review/domain/Review.java index e1afcfd1..44135caa 100644 --- a/src/main/java/com/odiga/fiesta/review/domain/Review.java +++ b/src/main/java/com/odiga/fiesta/review/domain/Review.java @@ -15,6 +15,9 @@ @Getter @SuperBuilder @NoArgsConstructor(access = PROTECTED) +@Table(name = "review", indexes = { + @Index(name = "idx_review_user_id", columnList = "user_id"), +}) public class Review extends BaseEntity { @Id diff --git a/src/main/java/com/odiga/fiesta/review/repository/ReviewCustomRepository.java b/src/main/java/com/odiga/fiesta/review/repository/ReviewCustomRepository.java index 75c031a2..6b3a5005 100644 --- a/src/main/java/com/odiga/fiesta/review/repository/ReviewCustomRepository.java +++ b/src/main/java/com/odiga/fiesta/review/repository/ReviewCustomRepository.java @@ -24,4 +24,6 @@ public interface ReviewCustomRepository { List findMostLikeReviews(Long size); TopReviewKeywordsResponse findTopReviewKeywords(Long festivalId, Long size); + + Long countByUserIdAndCategoryId(Long userId, Long festivalCategoryId); } diff --git a/src/main/java/com/odiga/fiesta/review/repository/ReviewCustomRepositoryImpl.java b/src/main/java/com/odiga/fiesta/review/repository/ReviewCustomRepositoryImpl.java index 528e036b..83441ab7 100644 --- a/src/main/java/com/odiga/fiesta/review/repository/ReviewCustomRepositoryImpl.java +++ b/src/main/java/com/odiga/fiesta/review/repository/ReviewCustomRepositoryImpl.java @@ -1,6 +1,7 @@ package com.odiga.fiesta.review.repository; import static com.odiga.fiesta.festival.domain.QFestival.*; +import static com.odiga.fiesta.festival.domain.QFestivalCategory.*; import static com.odiga.fiesta.keyword.domain.QKeyword.*; import static com.odiga.fiesta.review.domain.QReview.*; import static com.odiga.fiesta.review.domain.QReviewImage.*; @@ -21,6 +22,7 @@ import org.springframework.data.domain.Pageable; import org.springframework.data.support.PageableExecutionUtils; +import com.odiga.fiesta.festival.domain.QFestivalCategory; import com.odiga.fiesta.review.domain.QReviewLike; import com.odiga.fiesta.review.dto.projection.ReviewDataWithLike; import com.odiga.fiesta.review.dto.projection.ReviewSimpleData; @@ -150,6 +152,20 @@ public TopReviewKeywordsResponse findTopReviewKeywords(Long festivalId, Long siz .build(); } + @Override + public Long countByUserIdAndCategoryId(Long userId, Long festivalCategoryId) { + return queryFactory + .select(review.id.countDistinct()) + .from(review) + .leftJoin(festival) + .on(review.festivalId.eq(festival.id)) + .leftJoin(festivalCategory) + .on(festivalCategory.festivalId.eq(festival.id)) + .where(review.userId.eq(userId) + .and(festivalCategory.categoryId.eq(festivalCategoryId))) + .fetchOne(); + } + @Override public Map> findReviewKeywordsMap(List reviewIds) { Map> resultMap = queryFactory diff --git a/src/main/java/com/odiga/fiesta/review/repository/ReviewRepository.java b/src/main/java/com/odiga/fiesta/review/repository/ReviewRepository.java index 4c82d976..5cd8d328 100644 --- a/src/main/java/com/odiga/fiesta/review/repository/ReviewRepository.java +++ b/src/main/java/com/odiga/fiesta/review/repository/ReviewRepository.java @@ -9,4 +9,8 @@ public interface ReviewRepository extends JpaRepository, ReviewCustomRepository { List findAllByFestivalId(Long festivalId); + + Boolean existsByUserId(Long userId); + + Long countByUserId(Long userId); } diff --git a/src/main/java/com/odiga/fiesta/review/service/ReviewService.java b/src/main/java/com/odiga/fiesta/review/service/ReviewService.java index 263f4bc7..cf5b7603 100644 --- a/src/main/java/com/odiga/fiesta/review/service/ReviewService.java +++ b/src/main/java/com/odiga/fiesta/review/service/ReviewService.java @@ -16,6 +16,7 @@ import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; +import com.odiga.fiesta.badge.service.BadgeService; import com.odiga.fiesta.common.error.exception.CustomException; import com.odiga.fiesta.common.util.FileUtils; import com.odiga.fiesta.festival.repository.FestivalRepository; @@ -65,6 +66,7 @@ public class ReviewService { private final UserRepository userRepository; private final ReviewReportRepository reviewReportRepository; private final ReviewLikeRepository reviewLikeRepository; + private final BadgeService badgeService; private final FileUtils fileUtils; @@ -126,6 +128,8 @@ public ReviewIdResponse createReview(final Long userId, final ReviewCreateReques createReviewImages(images, review); saveReviewKeywords(request.getKeywordIds(), review); + badgeService.giveReviewBadge(userId); + return ReviewIdResponse.builder() .reviewId(review.getId()) .build(); diff --git a/src/main/java/com/odiga/fiesta/user/controller/UserController.java b/src/main/java/com/odiga/fiesta/user/controller/UserController.java index f338322b..2ef85959 100644 --- a/src/main/java/com/odiga/fiesta/user/controller/UserController.java +++ b/src/main/java/com/odiga/fiesta/user/controller/UserController.java @@ -1,5 +1,7 @@ package com.odiga.fiesta.user.controller; +import java.util.List; + import org.springdoc.core.annotations.ParameterObject; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -17,6 +19,7 @@ import com.odiga.fiesta.auth.domain.AuthUser; import com.odiga.fiesta.auth.service.AuthService; +import com.odiga.fiesta.badge.service.BadgeService; import com.odiga.fiesta.common.BasicResponse; import com.odiga.fiesta.common.PageResponse; import com.odiga.fiesta.common.error.ErrorCode; @@ -28,6 +31,7 @@ import com.odiga.fiesta.user.dto.response.LoginResponse; import com.odiga.fiesta.user.dto.response.ProfileCreateResponse; import com.odiga.fiesta.user.dto.response.TokenReissueResponse; +import com.odiga.fiesta.user.dto.response.UserBadgeResponse; import com.odiga.fiesta.user.dto.response.UserInfoResponse; import com.odiga.fiesta.user.service.UserService; @@ -48,6 +52,7 @@ public class UserController { private final UserService userService; private final AuthService authService; + private final BadgeService badgeService; @PostMapping("/oauth/login") @Operation(summary = "소셜 로그인", description = "소셜 로그인을 진행합니다.") @@ -116,4 +121,13 @@ public ResponseEntity>> get Page festivals = userService.getBookmarkedFestivals(user, pageable); return ResponseEntity.ok(BasicResponse.ok("스크랩한 페스티벌 조회 성공", PageResponse.of(festivals))); } + + @GetMapping("/badges") + @Operation(summary = "유저의 활동 뱃지 조회", description = "현재 유저가 획득한 뱃지를 조회합니다.") + public ResponseEntity>> getUserBadges(@AuthUser User user) { + + List response = badgeService.getUserBadges(user); + return ResponseEntity.ok(BasicResponse.ok("유저의 활동 뱃지 조회 성공", response)); + } + } diff --git a/src/main/java/com/odiga/fiesta/user/dto/response/UserBadgeResponse.java b/src/main/java/com/odiga/fiesta/user/dto/response/UserBadgeResponse.java new file mode 100644 index 00000000..c04de4a1 --- /dev/null +++ b/src/main/java/com/odiga/fiesta/user/dto/response/UserBadgeResponse.java @@ -0,0 +1,19 @@ +package com.odiga.fiesta.user.dto.response; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class UserBadgeResponse { + + private Long badgeId; + private String badgeName; + private String description; + private String imageUrl; + private Boolean isAcquired; +} diff --git a/src/main/java/com/odiga/fiesta/user/service/UserService.java b/src/main/java/com/odiga/fiesta/user/service/UserService.java index 36d92144..9c808704 100644 --- a/src/main/java/com/odiga/fiesta/user/service/UserService.java +++ b/src/main/java/com/odiga/fiesta/user/service/UserService.java @@ -10,6 +10,8 @@ import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import com.odiga.fiesta.badge.repository.BadgeRepository; +import com.odiga.fiesta.badge.repository.UserBadgeRepository; import com.odiga.fiesta.category.domain.Category; import com.odiga.fiesta.category.repository.CategoryRepository; import com.odiga.fiesta.common.error.exception.CustomException; @@ -62,7 +64,7 @@ public class UserService { @Transactional public ProfileCreateResponse createProfile(User user, ProfileCreateRequest request) { - checkLogin(user); + validateUser(user); List priorityIds = request.getPriorityIds(); List moodIds = request.getMoodIds(); @@ -93,7 +95,7 @@ public ProfileCreateResponse createProfile(User user, ProfileCreateRequest reque } public Page getBookmarkedFestivals(User user, Pageable pageable) { - checkLogin(user); + validateUser(user); Page festivals = festivalRepository.findBookmarkedFestivals(user.getId(), pageable); @@ -101,6 +103,8 @@ public Page getBookmarkedFestivals(User user, Pageable return festivals; } + + private void saveUserCompanions(final Long userId, List companionIds) { userCompanionRepository.saveAll( companionIds.stream() @@ -173,7 +177,7 @@ private void validatePriorities(List priorityIds) { } } - private void checkLogin(User user) { + private void validateUser(User user) { if (isNull(user) || isNull(user.getId())) { throw new CustomException(USER_NOT_FOUND); } @@ -182,4 +186,5 @@ private void checkLogin(User user) { throw new CustomException(USER_NOT_FOUND); } } + } diff --git a/src/main/resources/db/migration/V25__add_index_for_badge.sql b/src/main/resources/db/migration/V25__add_index_for_badge.sql new file mode 100644 index 00000000..ba104fda --- /dev/null +++ b/src/main/resources/db/migration/V25__add_index_for_badge.sql @@ -0,0 +1,11 @@ +ALTER TABLE `festival` + ADD INDEX idx_festival_user_id (`user_id`); + +ALTER TABLE `review` + ADD INDEX idx_review_user_id (`user_id`); + +ALTER TABLE `user_badge` + ADD INDEX idx_user_badge_user_id (`user_id`); + +ALTER TABLE `user_badge` + ADD INDEX idx_user_badge_badge_id (`badge_id`); diff --git a/src/main/resources/db/migration/V26__add_temp_badge_data.sql b/src/main/resources/db/migration/V26__add_temp_badge_data.sql new file mode 100644 index 00000000..ecf1bdc9 --- /dev/null +++ b/src/main/resources/db/migration/V26__add_temp_badge_data.sql @@ -0,0 +1,24 @@ +ALTER TABLE `badge` + ADD COLUMN `description` varchar(255) NOT NULL; +ALTER TABLE `badge` + ADD COLUMN `image_url` varchar(2048) NOT NULL; +ALTER TABLE `badge` + ADD COLUMN `type` varchar(255) NOT NULL; +ALTER TABLE `badge` + ADD INDEX `idx_badge_type` (`type`); + +INSERT INTO `badge` (`name`, `description`, `image_url`, `type`) +VALUES ('회원 가입', '피에스타 회원가입!', 'https://fiesta-image.s3.ap-northeast-2.amazonaws.com/badge/badge_temp.png', 'USER'), + ('첫 리뷰 작성', '피에스타 첫 리뷰', 'https://fiesta-image.s3.ap-northeast-2.amazonaws.com/badge/badge_temp.png', 'REVIEW'), + ('첫 등록', '피에스타 첫 등록', 'https://fiesta-image.s3.ap-northeast-2.amazonaws.com/badge/badge_temp.png', 'FESTIVAL'), + ('열혈 참여러', '✨리뷰 5개 작성✨', 'https://fiesta-image.s3.ap-northeast-2.amazonaws.com/badge/badge_temp.png', 'REVIEW'), + ('역사 탐방러', '리뷰 2개째!', 'https://fiesta-image.s3.ap-northeast-2.amazonaws.com/badge/badge_temp.png', 'REVIEW'), + ('음악 탐방러', '리뷰 2개째!', 'https://fiesta-image.s3.ap-northeast-2.amazonaws.com/badge/badge_temp.png', 'REVIEW'), + ('액티비티 탐방러', '리뷰 2개째!', 'https://fiesta-image.s3.ap-northeast-2.amazonaws.com/badge/badge_temp.png', 'REVIEW'), + ('음식&술 탐방러', '리뷰 2개째!', 'https://fiesta-image.s3.ap-northeast-2.amazonaws.com/badge/badge_temp.png', 'REVIEW'), + ('불꽃축제 탐방러', '리뷰 2개째!', 'https://fiesta-image.s3.ap-northeast-2.amazonaws.com/badge/badge_temp.png', 'REVIEW'), + ('자연 탐방러', '리뷰 2개째!', 'https://fiesta-image.s3.ap-northeast-2.amazonaws.com/badge/badge_temp.png', 'REVIEW'), + ('야간 탐방러', '리뷰 2개째!', 'https://fiesta-image.s3.ap-northeast-2.amazonaws.com/badge/badge_temp.png', 'REVIEW'), + ('미술 탐방러', '리뷰 2개째!', 'https://fiesta-image.s3.ap-northeast-2.amazonaws.com/badge/badge_temp.png', 'REVIEW'), + ('문화 탐방러', '리뷰 2개째!', 'https://fiesta-image.s3.ap-northeast-2.amazonaws.com/badge/badge_temp.png', 'REVIEW'), + ('이색축제 탐방러', '리뷰 2개째!', 'https://fiesta-image.s3.ap-northeast-2.amazonaws.com/badge/badge_temp.png', 'REVIEW'); diff --git a/src/test/java/com/odiga/fiesta/IntegrationTestSupport.java b/src/test/java/com/odiga/fiesta/IntegrationTestSupport.java index 164d86a4..170921fc 100644 --- a/src/test/java/com/odiga/fiesta/IntegrationTestSupport.java +++ b/src/test/java/com/odiga/fiesta/IntegrationTestSupport.java @@ -8,7 +8,6 @@ import com.odiga.fiesta.config.S3MockConfig; @ActiveProfiles("test") -@Transactional @SpringBootTest @Import(S3MockConfig.class) public abstract class IntegrationTestSupport { diff --git a/src/test/java/com/odiga/fiesta/badge/service/BadgeServiceTest.java b/src/test/java/com/odiga/fiesta/badge/service/BadgeServiceTest.java new file mode 100644 index 00000000..8c13d8f5 --- /dev/null +++ b/src/test/java/com/odiga/fiesta/badge/service/BadgeServiceTest.java @@ -0,0 +1,352 @@ +package com.odiga.fiesta.badge.service; + +import static com.odiga.fiesta.badge.domain.BadgeConstants.*; +import static org.junit.jupiter.api.Assertions.*; + +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Set; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.CountDownLatch; +import java.util.concurrent.ExecutionException; +import java.util.concurrent.TimeUnit; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.test.annotation.DirtiesContext; +import org.springframework.transaction.annotation.Transactional; + +import com.odiga.fiesta.IntegrationTestSupport; +import com.odiga.fiesta.badge.domain.Badge; +import com.odiga.fiesta.badge.domain.BadgeType; +import com.odiga.fiesta.badge.domain.UserBadge; +import com.odiga.fiesta.badge.repository.BadgeRepository; +import com.odiga.fiesta.badge.repository.UserBadgeRepository; +import com.odiga.fiesta.category.domain.Category; +import com.odiga.fiesta.category.domain.CategoryConstants; +import com.odiga.fiesta.category.repository.CategoryRepository; +import com.odiga.fiesta.festival.domain.Festival; +import com.odiga.fiesta.festival.domain.FestivalCategory; +import com.odiga.fiesta.festival.repository.FestivalCategoryRepository; +import com.odiga.fiesta.festival.repository.FestivalRepository; +import com.odiga.fiesta.review.domain.Review; +import com.odiga.fiesta.review.repository.ReviewRepository; +import com.odiga.fiesta.user.domain.User; +import com.odiga.fiesta.user.dto.response.UserBadgeResponse; +import com.odiga.fiesta.user.repository.UserRepository; + +import jakarta.persistence.Entity; +import jakarta.persistence.EntityManager; +import jakarta.persistence.Id; +import jakarta.persistence.PersistenceContext; + + +class BadgeServiceTest extends IntegrationTestSupport { + + @Autowired + private BadgeService badgeService; + + @Autowired + private UserRepository userRepository; + + @Autowired + private BadgeRepository badgeRepository; + + @Autowired + private UserBadgeRepository userBadgeRepository; + + @Autowired + private ReviewRepository reviewRepository; + + @Autowired + private FestivalRepository festivalRepository; + + @Autowired + private CategoryRepository categoryRepository; + + @Autowired + private FestivalCategoryRepository festivalCategoryRepository; + + @PersistenceContext + private EntityManager entityManager; + + @BeforeEach + void setUp() { + // entityManager.createNativeQuery("ALTER TABLE badge AUTO_INCREMENT = 1").executeUpdate(); + // 기본 뱃지 데이터 셋업 + badgeRepository.save(createBadge(1L, BadgeType.USER)); + badgeRepository.save(createBadge(2L, BadgeType.REVIEW)); + badgeRepository.save(createBadge(3L, BadgeType.FESTIVAL)); + + for (long badgeId = 4L; badgeId <= 15L; badgeId++) { + badgeRepository.save(createBadge(badgeId, BadgeType.REVIEW)); + } + + System.out.println("badges: " + badgeRepository.findAll()); + + // 카테고리 셋업 + for (long categoryId = 1L; categoryId <= 12L; categoryId++) { + categoryRepository.save(createCategory(categoryId)); + } + } + + @AfterEach + void tearDown() { + System.out.println("tearDown"); + userBadgeRepository.deleteAll(); + badgeRepository.deleteAll(); + userRepository.deleteAll(); + reviewRepository.deleteAll(); + festivalRepository.deleteAll(); + categoryRepository.deleteAll(); + festivalCategoryRepository.deleteAll(); + } + + @DisplayName("뱃지 수여 - 이미 뱃지를 가지고 있는 경우, 뱃지 수여하지 않음") + @Test + void giveUserBadge_ExistingUser() throws ExecutionException, InterruptedException { + System.out.println("giveUserBadge_ExistingUser"); + // given + User user = createUser(); + userRepository.save(user); + UserBadge userBadge = createUserBadge(user, USER_JOIN_BADGE_ID); + userBadgeRepository.save(userBadge); + + // when + CompletableFuture> badgeIdsFuture = badgeService.giveUserBadge(user.getId()); + List badgeIds = badgeIdsFuture.get(); + + // then + assertEquals(0, badgeIds.size()); + } + + // 유저 뱃지 수여 테스트 + @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD) + @DisplayName("유저 뱃지 수여 - 회원가입 시 뱃지 수여") + @Test + void giveUserBadge_FirstJoin() throws ExecutionException, InterruptedException { + + // given + User newUser = createUser(); + userRepository.save(newUser); + + // when + CompletableFuture> badgeIdsFuture = badgeService.giveUserBadge(newUser.getId()); + List badgeIds = badgeIdsFuture.get(); + + // then + assertEquals(1, badgeIds.size()); + assertTrue(badgeIds.contains(USER_JOIN_BADGE_ID)); + } + + @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD) + @DisplayName("리뷰 뱃지 수여 - 첫 리뷰 작성") + @Test + void giveReviewBadge_FirstReview() throws ExecutionException, InterruptedException { + + // given + User user = createUser(); + userRepository.save(user); + Festival festival = createFestival(user); + festivalRepository.save(festival); + Review review = createReview(user, festival); + reviewRepository.save(review); + + // when + CompletableFuture> badgeIdsFuture = badgeService.giveReviewBadge(user.getId()); + List badgeIds = badgeIdsFuture.get(); + + // then + assertEquals(1, badgeIds.size()); + assertTrue(badgeIds.contains(FIRST_REVIEW_BADGE_ID)); + } + + @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD) + @DisplayName("리뷰 뱃지 수여 - 열정적인 리뷰어") + @Test + void giveReviewBadge_PassionateReviewer() throws ExecutionException, InterruptedException { + // given + User user = createUser(); + userRepository.save(user); + Festival festival = createFestival(user); + festivalRepository.save(festival); + + for (int reviewCount = 0; reviewCount < 5; reviewCount++) { + Review review = createReview(user, festival); + reviewRepository.save(review); + } + + UserBadge userBadge = createUserBadge(user, FIRST_REVIEW_BADGE_ID); + userBadgeRepository.save(userBadge); + + // when + CompletableFuture> badgeIdsFuture = badgeService.giveReviewBadge(user.getId()); + List badgeIds = badgeIdsFuture.get(); + + // then + assertEquals(1, badgeIds.size()); + assertTrue(badgeIds.contains(PASSIONATE_REVIEWER_BADGE_ID)); + } + + @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD) + @DisplayName("리뷰 뱃지 수여 - 역사 애호가") + @Test + void giveReviewBadge_HistoryLover() throws ExecutionException, InterruptedException { + // given + User user = createUser(); + userRepository.save(user); + + Festival festival = createFestival(user); + festivalRepository.save(festival); + + FestivalCategory festivalCategory = createFestivalCategory(festival, CategoryConstants.CATEGORY_HISTORY); + festivalCategoryRepository.save(festivalCategory); + + for (int reviewCount = 0; reviewCount < 2; reviewCount++) { + Review review = createReview(user, festival); + reviewRepository.save(review); + } + + UserBadge userBadge = createUserBadge(user, FIRST_REVIEW_BADGE_ID); + userBadgeRepository.save(userBadge); + + // when + CompletableFuture> badgeIdsFuture = badgeService.giveReviewBadge(user.getId()); + List badgeIds = badgeIdsFuture.get(); + + // then + assertEquals(1, badgeIds.size()); + assertTrue(badgeIds.contains(HISTORY_LOVER_BADGE_ID)); + } + + @DirtiesContext(methodMode = DirtiesContext.MethodMode.BEFORE_METHOD) + @DisplayName("뱃지 수여 - 페스티벌 첫 등록") + @Test + void giveFestivalBadge_FirstFestival() throws ExecutionException, InterruptedException { + // given + User user = createUser(); + userRepository.save(user); + + Festival festival = createFestival(user); + festivalRepository.save(festival); + + // when + CompletableFuture> badgeIdsFuture = badgeService.giveFestivalBadge(user.getId()); + List badgeIds = badgeIdsFuture.get(); + + // then + assertEquals(1, badgeIds.size()); + assertTrue(badgeIds.contains(FIRST_FESTIVAL_BADGE_ID)); + } + + @DisplayName("현재 유저의 뱃지 조회") + @Test + void getUserBadges() { + // given + User user = createUser(); + userRepository.save(user); + + userBadgeRepository.save(createUserBadge(user, USER_JOIN_BADGE_ID)); + userBadgeRepository.save(createUserBadge(user, FIRST_REVIEW_BADGE_ID)); + userBadgeRepository.save(createUserBadge(user, PASSIONATE_REVIEWER_BADGE_ID)); + userBadgeRepository.save(createUserBadge(user, MOVIE_LOVER_BADGE_ID)); + + // when + List userBadges = badgeService.getUserBadges(user); + + // then + + Set acquiredBadgeIds = Set.of(USER_JOIN_BADGE_ID, FIRST_REVIEW_BADGE_ID, PASSIONATE_REVIEWER_BADGE_ID, + MOVIE_LOVER_BADGE_ID); + + assertEquals(15, userBadges.size()); + + for (UserBadgeResponse response : userBadges) { + if (acquiredBadgeIds.contains(response.getBadgeId())) { + assertTrue(response.getIsAcquired(), "Badge " + response.getBadgeId() + " should be acquired"); + } else { + assertFalse(response.getIsAcquired(), "Badge " + response.getBadgeId() + " should not be acquired"); + } + } + + } + + private UserBadge createUserBadge(User user, Long badgeId) { + return UserBadge.builder() + .userId(user.getId()) + .badgeId(badgeId) + .build(); + } + + private Review createReview(User user, Festival festival) { + return Review.builder() + .userId(user.getId()) + .festivalId(festival.getId()) + .rating(35) + .content("리뷰 내용") + .build(); + } + + // 카테고리 별 페스티벌 + private Festival createFestival(User user) { + return Festival.builder() + .userId(user.getId()) + .name("페스티벌 이름") + .startDate(LocalDate.of(2021, 8, 1)) + .endDate(LocalDate.of(2021, 8, 1)) + .address("주소") + .sidoId(1L) + .sigungu("시군구") + .latitude(1.0) + .longitude(1.0) + .tip("팁") + .homepageUrl("홈페이지") + .instagramUrl("인스타그램") + .fee("요금") + .description("페스티벌 설명") + .ticketLink("티켓 링크") + .playtime("행사 시간") + .isPending(false) + .contentId("콘텐츠 ID") + .build(); + } + + private Category createCategory(Long categoryId) { + return Category.builder() + .id(categoryId) + .name("카테고리 이름") + .emoji("\uD83D\uDC30") + .build(); + } + + private FestivalCategory createFestivalCategory(Festival festival, Long categoryId) { + return FestivalCategory.builder() + .festivalId(festival.getId()) + .categoryId(categoryId) + .build(); + } + + private Badge createBadge(Long id, BadgeType type) { + return Badge.builder() + .id(id) + .name("뱃지 이름") + .description("뱃지 설명") + .imageUrl("이미지 URL") + .type(type) + .build(); + } + + private static User createUser() { + return User.builder() + .email("fiesta@odiga.com") + .nickname("테스트 유저") + .userTypeId(1L) + .statusMessage("상태 메시지") + .profileImage("프로필 이미지") + .build(); + } +} diff --git a/src/test/java/com/odiga/fiesta/festival/service/FestivalServiceMockTest.java b/src/test/java/com/odiga/fiesta/festival/service/FestivalServiceMockTest.java index ecc79e0a..12b3a88a 100644 --- a/src/test/java/com/odiga/fiesta/festival/service/FestivalServiceMockTest.java +++ b/src/test/java/com/odiga/fiesta/festival/service/FestivalServiceMockTest.java @@ -22,6 +22,7 @@ import org.springframework.web.multipart.MultipartFile; import com.odiga.fiesta.MockTestSupport; +import com.odiga.fiesta.badge.service.BadgeService; import com.odiga.fiesta.common.PageResponse; import com.odiga.fiesta.common.error.exception.CustomException; import com.odiga.fiesta.common.util.FileUtils; @@ -74,6 +75,9 @@ class FestivalServiceMockTest extends MockTestSupport { @Mock private FileUtils fileUtils; + @Mock + private BadgeService badgeService; + @InjectMocks private FestivalService festivalService; diff --git a/src/test/java/com/odiga/fiesta/festival/service/FestivalServiceTest.java b/src/test/java/com/odiga/fiesta/festival/service/FestivalServiceTest.java index 2adcd41e..d745baf8 100644 --- a/src/test/java/com/odiga/fiesta/festival/service/FestivalServiceTest.java +++ b/src/test/java/com/odiga/fiesta/festival/service/FestivalServiceTest.java @@ -17,6 +17,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Nested; @@ -130,6 +131,22 @@ void beforeEach() { .instant(); } + @AfterEach + void tearDown() { + festivalRepository.deleteAll(); + festivalImageRepository.deleteAll(); + festivalCategoryRepository.deleteAll(); + festivalMoodRepository.deleteAll(); + sidoRepository.deleteAll(); + festivalBookmarkRepository.deleteAll(); + categoryRepository.deleteAll(); + userTypeRepository.deleteAll(); + moodRepository.deleteAll(); + userRepository.deleteAll(); + festivalUserTypeRepository.deleteAll(); + festivalModificationRequestRepository.deleteAll(); + } + @DisplayName("페스티벌 월간 조회 - startDate 와 endDate 사이에 해당 월이 끼어있어도 페스티벌이 포함되어야 한다.") @Test void getMonthlyFestivals() { diff --git a/src/test/java/com/odiga/fiesta/review/service/ReviewServiceMockTest.java b/src/test/java/com/odiga/fiesta/review/service/ReviewServiceMockTest.java index 25b4631b..1902545b 100644 --- a/src/test/java/com/odiga/fiesta/review/service/ReviewServiceMockTest.java +++ b/src/test/java/com/odiga/fiesta/review/service/ReviewServiceMockTest.java @@ -30,6 +30,7 @@ import org.springframework.web.multipart.MultipartFile; import com.odiga.fiesta.MockTestSupport; +import com.odiga.fiesta.badge.service.BadgeService; import com.odiga.fiesta.common.error.exception.CustomException; import com.odiga.fiesta.common.util.FileUtils; import com.odiga.fiesta.festival.domain.Festival; @@ -78,6 +79,9 @@ class ReviewServiceMockTest extends MockTestSupport { @Mock private ReviewLikeRepository reviewLikeRepository; + @Mock + private BadgeService badgeService; + @InjectMocks private ReviewService reviewService; diff --git a/src/test/java/com/odiga/fiesta/review/service/ReviewServiceTest.java b/src/test/java/com/odiga/fiesta/review/service/ReviewServiceTest.java index 463da5b9..cae28b62 100644 --- a/src/test/java/com/odiga/fiesta/review/service/ReviewServiceTest.java +++ b/src/test/java/com/odiga/fiesta/review/service/ReviewServiceTest.java @@ -9,6 +9,7 @@ import java.util.Arrays; import java.util.List; +import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; @@ -104,6 +105,14 @@ void setUp() { .build()); } + @AfterEach + void tearDown() { + reviewRepository.deleteAll(); + festivalRepository.deleteAll(); + keywordRepository.deleteAll(); + userRepository.deleteAll(); + } + @DisplayName("실시간 가장 핫한 후기 조회 - 좋아요 순 정렬") @Test void getMostLikeReviews() {