diff --git a/CONVENTIONS.md b/CONVENTIONS.md index 2cb63ed6..1e4d93e9 100644 --- a/CONVENTIONS.md +++ b/CONVENTIONS.md @@ -16,13 +16,13 @@ | Facade Service 구현체 | `Facade{Role}{Domain}ServiceImpl` | `FacadeClubFeedServiceImpl` | | Domain Service 인터페이스 | `{Domain}Service` | `FeedService`, `ClubService` | | Domain Service 구현체 | `General{Domain}Service` | `GeneralFeedService`, `GeneralClubService` | -| Repository | `{Entity}Repository` | `FeedRepository`, `FeedLikeRepository` | +| Repository | `{Entity}Repository` | `FeedRepository`, `FeedCommentRepository` | | Request DTO | `{Action}{Entity}Request` | `CreateFeedRequest`, `UpdateFixZoneRequest` | | Response DTO | `{Context}{Entity}Response` | `MyFeedPageResponse`, `CentralMyFixZoneListResponse` | | Command DTO | `{Action}{Entity}Command` | `CreateFeedCommand`, `UpdateFormCommand` | | Query DTO | `{Entity}{Context}Query` | `FeedPageQuery`, `ClubFeedPageQuery` | | Exception (부모) | `{Domain}Exception` | `FeedException`, `FormException` | -| Exception (구체) | `{Description}Exception` (static inner) | `DuplicatedFeedLikeException`, `FormPeriodException` | +| Exception (구체) | `{Description}Exception` (static inner) | `FeedNotFoundException`, `FormPeriodException` | | Repository Projection | `{QueryPurpose}Dto` (interface) | `MonthlyFeedRankingDto`, `ClubFeedRankingDto` | --- @@ -243,16 +243,16 @@ public class FeedException extends CustomException { } // 구체적 예외 — static final inner class - public static final class DuplicatedFeedLikeException extends FeedException { - private static final String MESSAGE = "이미 좋아요한 피드입니다."; - public DuplicatedFeedLikeException() { - super(MESSAGE, BAD_REQUEST.value()); + public static final class FeedNotFoundException extends FeedException { + private static final String MESSAGE = "존재하지 않는 피드입니다."; + public FeedNotFoundException() { + super(MESSAGE, NOT_FOUND.value()); } } } // 사용 -throw new FeedException.DuplicatedFeedLikeException(); +throw new FeedException.FeedNotFoundException(); ``` | 예외 유형 | 패턴 | HTTP 코드 | @@ -302,4 +302,4 @@ public void createFeed(CreateFeedRequest request, PrincipalDetails principalDeta | Service Impl 네이밍 | Impl 유무 혼재 | **항상 Impl suffix 붙이기** | | Exception 메시지 | 상수 vs 인라인 혼재 | **`private static final String MESSAGE`로 상수화** | | Boolean 필드 네이밍 | `is` / `has` 혼재 | **엔티티 상태 → `is`, 보유 여부 → `has`** | -| FeedLike soft delete | unique constraint 충돌 | **FeedLike만 예외적으로 hard delete** | +| FeedComment hard delete | 댓글 삭제 시 물리 삭제 | **FeedComment만 예외적으로 hard delete** | diff --git a/src/main/java/ddingdong/ddingdongBE/common/config/SecurityConfig.java b/src/main/java/ddingdong/ddingdongBE/common/config/SecurityConfig.java index 36417514..88376f03 100644 --- a/src/main/java/ddingdong/ddingdongBE/common/config/SecurityConfig.java +++ b/src/main/java/ddingdong/ddingdongBE/common/config/SecurityConfig.java @@ -2,6 +2,7 @@ import static org.springframework.http.HttpMethod.DELETE; import static org.springframework.http.HttpMethod.GET; +import static org.springframework.http.HttpMethod.PATCH; import static org.springframework.http.HttpMethod.POST; import ddingdong.ddingdongBE.auth.service.JwtAuthService; @@ -59,12 +60,14 @@ public SecurityFilterChain filterChain(HttpSecurity http, JwtAuthService authSer .requestMatchers(POST, API_PREFIX + "/forms/{formId}/applications", API_PREFIX + "/pair-game/appliers", - API_PREFIX + "/feeds/*/likes", API_PREFIX + "/feeds/*/comments" ) .permitAll() + .requestMatchers(PATCH, + API_PREFIX + "/feeds/*/likes" + ) + .permitAll() .requestMatchers(DELETE, - API_PREFIX + "/feeds/*/likes", API_PREFIX + "/feeds/*/comments/*" ) .permitAll() diff --git a/src/main/java/ddingdong/ddingdongBE/common/exception/FeedException.java b/src/main/java/ddingdong/ddingdongBE/common/exception/FeedException.java index 1f3fbfc7..4f3520ad 100644 --- a/src/main/java/ddingdong/ddingdongBE/common/exception/FeedException.java +++ b/src/main/java/ddingdong/ddingdongBE/common/exception/FeedException.java @@ -1,6 +1,5 @@ package ddingdong.ddingdongBE.common.exception; -import static org.springframework.http.HttpStatus.CONFLICT; import static org.springframework.http.HttpStatus.FORBIDDEN; import static org.springframework.http.HttpStatus.NOT_FOUND; @@ -10,24 +9,6 @@ public FeedException(String message, int errorCode) { super(message, errorCode); } - public static final class DuplicatedFeedLikeException extends FeedException { - - private static final String MESSAGE = "이미 좋아요한 피드입니다."; - - public DuplicatedFeedLikeException() { - super(MESSAGE, CONFLICT.value()); - } - } - - public static final class FeedLikeNotFoundException extends FeedException { - - private static final String MESSAGE = "좋아요 기록이 존재하지 않습니다."; - - public FeedLikeNotFoundException() { - super(MESSAGE, NOT_FOUND.value()); - } - } - public static final class CommentNotFoundException extends FeedException { private static final String MESSAGE = "존재하지 않는 댓글입니다."; diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java index b113b4bd..9ca5b02b 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/api/FeedLikeApi.java @@ -1,17 +1,15 @@ package ddingdong.ddingdongBE.domain.feed.api; -import static ddingdong.ddingdongBE.common.constant.ValidationConstants.UUID_V4_REGEXP; - +import ddingdong.ddingdongBE.domain.feed.controller.dto.request.CreateFeedLikeRequest; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import io.swagger.v3.oas.annotations.tags.Tag; -import jakarta.validation.constraints.Pattern; +import jakarta.validation.Valid; import org.springframework.http.HttpStatus; -import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestHeader; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.ResponseStatus; @@ -21,30 +19,13 @@ public interface FeedLikeApi { @Operation(summary = "피드 좋아요 API") @ApiResponses({ - @ApiResponse(responseCode = "201", description = "피드 좋아요 성공"), - @ApiResponse(responseCode = "400", description = "유효하지 않은 UUID 형식"), - @ApiResponse(responseCode = "404", description = "피드 없음"), - @ApiResponse(responseCode = "409", description = "이미 좋아요한 피드") - }) - @ResponseStatus(HttpStatus.CREATED) - @PostMapping("/{feedId}/likes") - void createLike( - @PathVariable("feedId") Long feedId, - @Pattern(regexp = UUID_V4_REGEXP, message = "유효하지 않은 UUID v4 형식입니다.") - @RequestHeader("X-Anonymous-UUID") String uuid - ); - - @Operation(summary = "피드 좋아요 취소 API") - @ApiResponses({ - @ApiResponse(responseCode = "204", description = "피드 좋아요 취소 성공"), - @ApiResponse(responseCode = "400", description = "유효하지 않은 UUID 형식"), - @ApiResponse(responseCode = "404", description = "좋아요 기록 없음") + @ApiResponse(responseCode = "204", description = "피드 좋아요 성공"), + @ApiResponse(responseCode = "400", description = "좋아요 횟수 초과 (최대 100)") }) @ResponseStatus(HttpStatus.NO_CONTENT) - @DeleteMapping("/{feedId}/likes") - void deleteLike( + @PatchMapping("/{feedId}/likes") + void createLike( @PathVariable("feedId") Long feedId, - @Pattern(regexp = UUID_V4_REGEXP, message = "유효하지 않은 UUID v4 형식입니다.") - @RequestHeader("X-Anonymous-UUID") String uuid + @RequestBody @Valid CreateFeedLikeRequest request ); } diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeController.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeController.java index 0134c398..73472036 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeController.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeController.java @@ -1,25 +1,20 @@ package ddingdong.ddingdongBE.domain.feed.controller; import ddingdong.ddingdongBE.domain.feed.api.FeedLikeApi; -import ddingdong.ddingdongBE.domain.feed.service.FeedLikeService; +import ddingdong.ddingdongBE.domain.feed.controller.dto.request.CreateFeedLikeRequest; +import ddingdong.ddingdongBE.domain.feed.service.FeedService; import lombok.RequiredArgsConstructor; -import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.RestController; -@Validated @RestController @RequiredArgsConstructor public class FeedLikeController implements FeedLikeApi { - private final FeedLikeService feedLikeService; + private final FeedService feedService; @Override - public void createLike(Long feedId, String uuid) { - feedLikeService.create(feedId, uuid); - } - - @Override - public void deleteLike(Long feedId, String uuid) { - feedLikeService.delete(feedId, uuid); + public void createLike(Long feedId, CreateFeedLikeRequest request) { + feedService.getById(feedId); + feedService.addLikeCount(feedId, request.count()); } } diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/request/CreateFeedLikeRequest.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/request/CreateFeedLikeRequest.java new file mode 100644 index 00000000..cdd02d64 --- /dev/null +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/controller/dto/request/CreateFeedLikeRequest.java @@ -0,0 +1,14 @@ +package ddingdong.ddingdongBE.domain.feed.controller.dto.request; + +import jakarta.validation.constraints.Max; +import jakarta.validation.constraints.Min; +import jakarta.validation.constraints.NotNull; + +public record CreateFeedLikeRequest( + @NotNull(message = "count는 null이 될 수 없습니다.") + @Min(value = 1, message = "count는 1 이상이어야 합니다.") + @Max(value = 100, message = "count는 100 이하여야 합니다.") + Integer count +) { + +} diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/entity/Feed.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/entity/Feed.java index 27c82411..063e55a5 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/entity/Feed.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/entity/Feed.java @@ -43,16 +43,20 @@ public class Feed extends BaseEntity { @Column(nullable = false) private long viewCount; + @Column(nullable = false) + private long likeCount; + @Column(name = "deleted_at") private LocalDateTime deletedAt; @Builder - private Feed(Long id, String activityContent, Club club, FeedType feedType, long viewCount) { + private Feed(Long id, String activityContent, Club club, FeedType feedType, long viewCount, long likeCount) { this.id = id; this.activityContent = activityContent; this.club = club; this.feedType = feedType; this.viewCount = viewCount; + this.likeCount = likeCount; } public boolean isImage() { diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/entity/FeedLike.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/entity/FeedLike.java deleted file mode 100644 index 99dd374f..00000000 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/entity/FeedLike.java +++ /dev/null @@ -1,39 +0,0 @@ -package ddingdong.ddingdongBE.domain.feed.entity; - -import ddingdong.ddingdongBE.common.BaseEntity; -import jakarta.persistence.Column; -import jakarta.persistence.Entity; -import jakarta.persistence.FetchType; -import jakarta.persistence.GeneratedValue; -import jakarta.persistence.GenerationType; -import jakarta.persistence.Id; -import jakarta.persistence.JoinColumn; -import jakarta.persistence.ManyToOne; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; - -@Entity -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -public class FeedLike extends BaseEntity { - - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name = "feed_id", nullable = false) - private Feed feed; - - @Column(nullable = false, length = 36) - private String uuid; - - @Builder - private FeedLike(Long id, Feed feed, String uuid) { - this.id = id; - this.feed = feed; - this.uuid = uuid; - } -} diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedLikeRepository.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedLikeRepository.java deleted file mode 100644 index cc0fa808..00000000 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedLikeRepository.java +++ /dev/null @@ -1,25 +0,0 @@ -package ddingdong.ddingdongBE.domain.feed.repository; - -import ddingdong.ddingdongBE.domain.feed.entity.FeedLike; -import ddingdong.ddingdongBE.domain.feed.repository.dto.FeedCountDto; -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; - -public interface FeedLikeRepository extends JpaRepository { - - boolean existsByFeedIdAndUuid(Long feedId, String uuid); - - void deleteByFeedIdAndUuid(Long feedId, String uuid); - - long countByFeedId(Long feedId); - - @Query(value = """ - SELECT fl.feed_id AS feedId, COUNT(*) AS cnt - FROM feed_like fl - WHERE fl.feed_id IN (:feedIds) - GROUP BY fl.feed_id - """, nativeQuery = true) - List countsByFeedIds(@Param("feedIds") List feedIds); -} diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepository.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepository.java index a0966f7d..7dfcc69a 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepository.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepository.java @@ -69,23 +69,22 @@ Slice getAllFeedPage( @Query(value = "UPDATE feed SET view_count = view_count + 1 WHERE id = :feedId", nativeQuery = true) void incrementViewCount(@Param("feedId") Long feedId); + @Modifying(clearAutomatically = true) + @Query(value = "UPDATE feed SET like_count = like_count + :count WHERE id = :feedId", nativeQuery = true) + void addLikeCount(@Param("feedId") Long feedId, @Param("count") int count); + @Query(value = """ SELECT c.id AS clubId, c.name AS clubName, COUNT(f.id) AS feedCount, COALESCE(SUM(f.view_count), 0) AS viewCount, - COALESCE(SUM(sub_like.like_cnt), 0) AS likeCount, + COALESCE(SUM(f.like_count), 0) AS likeCount, COALESCE(SUM(sub_comment.comment_cnt), 0) AS commentCount FROM club c LEFT JOIN feed f ON f.club_id = c.id AND f.deleted_at IS NULL AND YEAR(f.created_at) = :year AND MONTH(f.created_at) = :month - LEFT JOIN ( - SELECT fl.feed_id, COUNT(*) AS like_cnt - FROM feed_like fl - GROUP BY fl.feed_id - ) sub_like ON sub_like.feed_id = f.id LEFT JOIN ( SELECT fc.feed_id, COUNT(*) AS comment_cnt FROM feed_comment fc diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedService.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedService.java index 91e3042f..70297c4b 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedService.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedService.java @@ -2,7 +2,6 @@ import ddingdong.ddingdongBE.domain.feed.entity.Feed; import ddingdong.ddingdongBE.domain.feed.repository.FeedCommentRepository; -import ddingdong.ddingdongBE.domain.feed.repository.FeedLikeRepository; import ddingdong.ddingdongBE.domain.feed.repository.dto.FeedCountDto; import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubFeedPageQuery; import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubProfileQuery; @@ -12,7 +11,6 @@ import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedQuery; import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedPageQuery; import ddingdong.ddingdongBE.domain.feed.service.dto.query.PagingQuery; -import java.util.Collections; import java.util.List; import java.util.Map; import java.util.stream.Collectors; @@ -28,9 +26,7 @@ public class FacadeFeedService { private final FeedService feedService; private final FeedFileService feedFileService; - private final FeedLikeService feedLikeService; private final FeedCommentService feedCommentService; - private final FeedLikeRepository feedLikeRepository; private final FeedCommentRepository feedCommentRepository; public ClubFeedPageQuery getFeedPageByClub(Long clubId, int size, Long currentCursorId) { @@ -63,7 +59,7 @@ public FeedQuery getById(Long feedId) { Feed feed = feedService.getById(feedId); ClubProfileQuery clubProfileQuery = feedFileService.extractClubInfo(feed.getClub()); FeedFileInfoQuery feedFileInfoQuery = feedFileService.extractFeedFileInfo(feed); - long likeCount = feedLikeService.countByFeedId(feedId); + long likeCount = feed.getLikeCount(); long commentCount = feedCommentService.countByFeedId(feedId); List comments = feedCommentService.getAllByFeedId(feedId); return FeedQuery.of(feed, clubProfileQuery, feedFileInfoQuery, likeCount, commentCount, comments); @@ -79,8 +75,8 @@ private List buildFeedListQueriesWithCounts(List feeds) { return feedListQueries; } - Map likeCountMap = feedLikeRepository.countsByFeedIds(feedIds).stream() - .collect(Collectors.toMap(FeedCountDto::getFeedId, FeedCountDto::getCnt)); + Map likeCountMap = feeds.stream() + .collect(Collectors.toMap(Feed::getId, Feed::getLikeCount)); Map commentCountMap = feedCommentRepository.countsByFeedIds(feedIds).stream() .collect(Collectors.toMap(FeedCountDto::getFeedId, FeedCountDto::getCnt)); diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedLikeCacheService.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedLikeCacheService.java deleted file mode 100644 index 4840ee20..00000000 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedLikeCacheService.java +++ /dev/null @@ -1,41 +0,0 @@ -package ddingdong.ddingdongBE.domain.feed.service; - -import com.github.benmanes.caffeine.cache.Cache; -import com.github.benmanes.caffeine.cache.Caffeine; -import java.util.Set; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import org.springframework.stereotype.Service; - -@Service -public class FeedLikeCacheService { - - private final Cache> likeCache = Caffeine.newBuilder() - .expireAfterWrite(14, TimeUnit.DAYS) - .maximumSize(100_000) - .build(); - - public boolean isLiked(String uuid, Long feedId) { - Set likedFeeds = likeCache.getIfPresent(uuid); - return likedFeeds != null && likedFeeds.contains(feedId); - } - - public void addLike(String uuid, Long feedId) { - likeCache.asMap().compute(uuid, (k, existing) -> { - Set set = existing != null ? existing : ConcurrentHashMap.newKeySet(); - set.add(feedId); - return set; - }); - } - - public void removeLike(String uuid, Long feedId) { - likeCache.asMap().computeIfPresent(uuid, (k, existing) -> { - existing.remove(feedId); - return existing.isEmpty() ? null : existing; - }); - } - - public void clearAll() { - likeCache.invalidateAll(); - } -} diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedLikeService.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedLikeService.java deleted file mode 100644 index cfd8b0be..00000000 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedLikeService.java +++ /dev/null @@ -1,12 +0,0 @@ -package ddingdong.ddingdongBE.domain.feed.service; - -public interface FeedLikeService { - - void create(Long feedId, String uuid); - - void delete(Long feedId, String uuid); - - long countByFeedId(Long feedId); - - boolean existsByFeedIdAndUuid(Long feedId, String uuid); -} diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedService.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedService.java index 539d7d6b..10791b95 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedService.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/FeedService.java @@ -19,4 +19,6 @@ public interface FeedService { void delete(Feed feed); void incrementViewCount(Long feedId); + + void addLikeCount(Long feedId, int count); } diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedLikeService.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedLikeService.java deleted file mode 100644 index 9dc34609..00000000 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedLikeService.java +++ /dev/null @@ -1,65 +0,0 @@ -package ddingdong.ddingdongBE.domain.feed.service; - -import ddingdong.ddingdongBE.common.exception.FeedException.DuplicatedFeedLikeException; -import ddingdong.ddingdongBE.common.exception.FeedException.FeedLikeNotFoundException; -import ddingdong.ddingdongBE.domain.feed.entity.Feed; -import ddingdong.ddingdongBE.domain.feed.entity.FeedLike; -import ddingdong.ddingdongBE.domain.feed.repository.FeedLikeRepository; -import lombok.RequiredArgsConstructor; -import org.springframework.dao.DataIntegrityViolationException; -import org.springframework.stereotype.Service; -import org.springframework.transaction.annotation.Transactional; - -@Service -@RequiredArgsConstructor -@Transactional(readOnly = true) -public class GeneralFeedLikeService implements FeedLikeService { - - private final FeedLikeRepository feedLikeRepository; - private final FeedService feedService; - private final FeedLikeCacheService feedLikeCacheService; - - @Override - @Transactional - public void create(Long feedId, String uuid) { - if (feedLikeCacheService.isLiked(uuid, feedId)) { - throw new DuplicatedFeedLikeException(); - } - if (feedLikeRepository.existsByFeedIdAndUuid(feedId, uuid)) { - throw new DuplicatedFeedLikeException(); - } - Feed feed = feedService.getById(feedId); - FeedLike feedLike = FeedLike.builder() - .feed(feed) - .uuid(uuid) - .build(); - try { - feedLikeRepository.save(feedLike); - } catch (DataIntegrityViolationException e) { - throw new DuplicatedFeedLikeException(); - } - feedLikeCacheService.addLike(uuid, feedId); - } - - @Override - @Transactional - public void delete(Long feedId, String uuid) { - if (!feedLikeCacheService.isLiked(uuid, feedId) - && !feedLikeRepository.existsByFeedIdAndUuid(feedId, uuid)) { - throw new FeedLikeNotFoundException(); - } - feedLikeRepository.deleteByFeedIdAndUuid(feedId, uuid); - feedLikeCacheService.removeLike(uuid, feedId); - } - - @Override - public long countByFeedId(Long feedId) { - return feedLikeRepository.countByFeedId(feedId); - } - - @Override - public boolean existsByFeedIdAndUuid(Long feedId, String uuid) { - return feedLikeCacheService.isLiked(uuid, feedId) - || feedLikeRepository.existsByFeedIdAndUuid(feedId, uuid); - } -} diff --git a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedService.java b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedService.java index 58349f13..8b4a3b91 100644 --- a/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedService.java +++ b/src/main/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedService.java @@ -62,6 +62,12 @@ public void incrementViewCount(Long feedId) { feedRepository.incrementViewCount(feedId); } + @Override + @Transactional + public void addLikeCount(Long feedId, int count) { + feedRepository.addLikeCount(feedId, count); + } + private Slice buildSlice(Slice originalSlice, int size) { List content = new ArrayList<>(originalSlice.getContent()); if (content.isEmpty()) { diff --git a/src/main/resources/db/migration/V60__simplify_feed_like_to_counter.sql b/src/main/resources/db/migration/V60__simplify_feed_like_to_counter.sql new file mode 100644 index 00000000..e23afdf6 --- /dev/null +++ b/src/main/resources/db/migration/V60__simplify_feed_like_to_counter.sql @@ -0,0 +1,7 @@ +ALTER TABLE feed ADD COLUMN like_count BIGINT NOT NULL DEFAULT 0; + +UPDATE feed f SET f.like_count = ( + SELECT COUNT(*) FROM feed_like fl WHERE fl.feed_id = f.id +); + +DROP TABLE feed_like; diff --git a/src/test/java/ddingdong/ddingdongBE/common/fixture/FeedFixture.java b/src/test/java/ddingdong/ddingdongBE/common/fixture/FeedFixture.java index 044d6d1d..b6a029ec 100644 --- a/src/test/java/ddingdong/ddingdongBE/common/fixture/FeedFixture.java +++ b/src/test/java/ddingdong/ddingdongBE/common/fixture/FeedFixture.java @@ -3,7 +3,6 @@ import ddingdong.ddingdongBE.domain.club.entity.Club; import ddingdong.ddingdongBE.domain.feed.entity.Feed; import ddingdong.ddingdongBE.domain.feed.entity.FeedComment; -import ddingdong.ddingdongBE.domain.feed.entity.FeedLike; import ddingdong.ddingdongBE.domain.feed.entity.FeedType; public class FeedFixture { @@ -32,13 +31,6 @@ public static Feed createFeed(Club club, FeedType feedType, String content) { .build(); } - public static FeedLike createFeedLike(Feed feed, String uuid) { - return FeedLike.builder() - .feed(feed) - .uuid(uuid) - .build(); - } - public static FeedComment createFeedComment(Feed feed, String uuid, int anonymousNumber, String content) { return FeedComment.builder() .feed(feed) diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/AdminFeedControllerE2ETest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/AdminFeedControllerE2ETest.java index ebd2a4e7..de4cd93d 100644 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/AdminFeedControllerE2ETest.java +++ b/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/AdminFeedControllerE2ETest.java @@ -13,7 +13,6 @@ import ddingdong.ddingdongBE.domain.club.repository.ClubRepository; import ddingdong.ddingdongBE.domain.feed.controller.dto.response.AdminClubFeedRankingResponse; import ddingdong.ddingdongBE.domain.feed.entity.Feed; -import ddingdong.ddingdongBE.domain.feed.repository.FeedLikeRepository; import ddingdong.ddingdongBE.domain.feed.repository.FeedRepository; import ddingdong.ddingdongBE.domain.user.entity.User; import ddingdong.ddingdongBE.domain.user.repository.UserRepository; @@ -45,9 +44,6 @@ class AdminFeedControllerE2ETest extends NonTxTestContainerSupport { @Autowired private FeedRepository feedRepository; - @Autowired - private FeedLikeRepository feedLikeRepository; - @Autowired private PasswordEncoder passwordEncoder; @@ -77,7 +73,13 @@ void getClubFeedRanking_success() { // 동아리B: 피드 2개 + 좋아요 1개 → score = 2*10 + 1*3 = 23 Feed feedB = feedRepository.save(FeedFixture.createImageFeed(clubB, "피드B1")); feedRepository.save(FeedFixture.createImageFeed(clubB, "피드B2")); - feedLikeRepository.save(FeedFixture.createFeedLike(feedB, "uuid-1")); + given() + .contentType(ContentType.JSON) + .body(java.util.Map.of("count", 1)) + .when() + .patch("/server/feeds/{feedId}/likes", feedB.getId()) + .then() + .statusCode(204); int year = java.time.LocalDate.now().getYear(); int month = java.time.LocalDate.now().getMonthValue(); diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java index 53d98bda..8568a1ca 100644 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java +++ b/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/ClubFeedStatusE2ETest.java @@ -13,7 +13,6 @@ import ddingdong.ddingdongBE.domain.club.repository.ClubRepository; import ddingdong.ddingdongBE.domain.feed.entity.Feed; import ddingdong.ddingdongBE.domain.feed.repository.FeedCommentRepository; -import ddingdong.ddingdongBE.domain.feed.repository.FeedLikeRepository; import ddingdong.ddingdongBE.domain.feed.repository.FeedRepository; import ddingdong.ddingdongBE.domain.user.entity.User; import ddingdong.ddingdongBE.domain.user.repository.UserRepository; @@ -44,9 +43,6 @@ class ClubFeedStatusE2ETest extends NonTxTestContainerSupport { @Autowired private FeedRepository feedRepository; - @Autowired - private FeedLikeRepository feedLikeRepository; - @Autowired private FeedCommentRepository feedCommentRepository; @@ -72,7 +68,13 @@ void getFeedStatus_success() { Feed feed1 = feedRepository.save(FeedFixture.createImageFeed(club, "활동 내용 1")); Feed feed2 = feedRepository.save(FeedFixture.createImageFeed(club, "활동 내용 2")); - feedLikeRepository.save(FeedFixture.createFeedLike(feed1, "uuid-1")); + given() + .contentType(ContentType.JSON) + .body(java.util.Map.of("count", 1)) + .when() + .patch("/server/feeds/{feedId}/likes", feed1.getId()) + .then() + .statusCode(204); feedCommentRepository.save(FeedFixture.createFeedComment(feed2, "uuid-2", 1, "댓글")); String token = signIn("club123", "1234"); diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java index 7f9b6dcc..6dff4eb2 100644 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java +++ b/src/test/java/ddingdong/ddingdongBE/domain/feed/controller/FeedLikeControllerE2ETest.java @@ -9,26 +9,20 @@ import ddingdong.ddingdongBE.domain.club.entity.Club; import ddingdong.ddingdongBE.domain.club.repository.ClubRepository; import ddingdong.ddingdongBE.domain.feed.entity.Feed; -import ddingdong.ddingdongBE.domain.feed.repository.FeedLikeRepository; import ddingdong.ddingdongBE.domain.feed.repository.FeedRepository; -import ddingdong.ddingdongBE.domain.feed.service.FeedLikeCacheService; import io.restassured.RestAssured; import io.restassured.http.ContentType; +import java.util.Map; 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.boot.test.context.SpringBootTest; import org.springframework.boot.test.web.server.LocalServerPort; -import org.springframework.transaction.annotation.Propagation; -import org.springframework.transaction.annotation.Transactional; @SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT) class FeedLikeControllerE2ETest extends NonTxTestContainerSupport { - private static final String VALID_UUID = "550e8400-e29b-41d4-a716-446655440000"; - private static final String INVALID_UUID = "not-a-valid-uuid"; - @LocalServerPort private int port; @@ -38,74 +32,84 @@ class FeedLikeControllerE2ETest extends NonTxTestContainerSupport { @Autowired private FeedRepository feedRepository; - @Autowired - private FeedLikeRepository feedLikeRepository; - - @Autowired - private FeedLikeCacheService feedLikeCacheService; - private Feed feed; @BeforeEach - @Transactional(propagation = Propagation.REQUIRES_NEW) void setUp() { RestAssured.port = port; - feedLikeCacheService.clearAll(); Club club = clubRepository.save(ClubFixture.createClub()); feed = feedRepository.save(FeedFixture.createImageFeed(club, "활동 내용")); } - @DisplayName("비회원이 X-Anonymous-UUID 헤더로 피드 좋아요를 생성한다.") + @DisplayName("좋아요 5회를 한 번에 보내면 카운터가 5 증가한다") @Test void createLike_success() { // when & then given() .contentType(ContentType.JSON) - .header("X-Anonymous-UUID", VALID_UUID) + .body(Map.of("count", 5)) .when() - .post("/server/feeds/{feedId}/likes", feed.getId()) + .patch("/server/feeds/{feedId}/likes", feed.getId()) .then() - .statusCode(201); + .statusCode(204); - long count = feedLikeRepository.countByFeedId(feed.getId()); - assertThat(count).isEqualTo(1L); + Feed found = feedRepository.findById(feed.getId()).orElseThrow(); + assertThat(found.getLikeCount()).isEqualTo(5L); } - @DisplayName("유효하지 않은 UUID로 좋아요 요청하면 400을 반환한다.") + @DisplayName("같은 피드에 여러 번 요청하면 좋아요가 누적된다") @Test - void createLike_fail_invalidUuid() { + void createLike_accumulates() { + // when + for (int i = 0; i < 3; i++) { + given() + .contentType(ContentType.JSON) + .body(Map.of("count", 10)) + .when() + .patch("/server/feeds/{feedId}/likes", feed.getId()) + .then() + .statusCode(204); + } + + // then + Feed found = feedRepository.findById(feed.getId()).orElseThrow(); + assertThat(found.getLikeCount()).isEqualTo(30L); + } + + @DisplayName("좋아요 횟수가 100을 초과하면 400을 반환한다") + @Test + void createLike_exceedsMaxCount() { given() .contentType(ContentType.JSON) - .header("X-Anonymous-UUID", INVALID_UUID) + .body(Map.of("count", 101)) .when() - .post("/server/feeds/{feedId}/likes", feed.getId()) + .patch("/server/feeds/{feedId}/likes", feed.getId()) .then() .statusCode(400); } - @DisplayName("비회원이 X-Anonymous-UUID 헤더로 피드 좋아요를 취소한다.") + @DisplayName("좋아요 횟수가 0 이하이면 400을 반환한다") @Test - void deleteLike_success() { - // given: 좋아요 생성 + void createLike_zeroCount() { given() .contentType(ContentType.JSON) - .header("X-Anonymous-UUID", VALID_UUID) + .body(Map.of("count", 0)) .when() - .post("/server/feeds/{feedId}/likes", feed.getId()) + .patch("/server/feeds/{feedId}/likes", feed.getId()) .then() - .statusCode(201); + .statusCode(400); + } - // when & then + @DisplayName("존재하지 않는 피드에 좋아요하면 404를 반환한다") + @Test + void createLike_nonExistentFeed() { given() .contentType(ContentType.JSON) - .header("X-Anonymous-UUID", VALID_UUID) + .body(Map.of("count", 1)) .when() - .delete("/server/feeds/{feedId}/likes", feed.getId()) + .patch("/server/feeds/{feedId}/likes", 999999L) .then() - .statusCode(204); - - long count = feedLikeRepository.countByFeedId(feed.getId()); - assertThat(count).isEqualTo(0L); + .statusCode(404); } } diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/entity/FeedLikeTest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/entity/FeedLikeTest.java deleted file mode 100644 index 4a5b3880..00000000 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/entity/FeedLikeTest.java +++ /dev/null @@ -1,28 +0,0 @@ -package ddingdong.ddingdongBE.domain.feed.entity; - -import static org.assertj.core.api.Assertions.assertThat; - -import ddingdong.ddingdongBE.common.fixture.FeedFixture; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; - -class FeedLikeTest { - - @DisplayName("FeedLike를 builder로 생성하면 feed와 uuid가 정상적으로 설정된다.") - @Test - void create() { - // given - Feed feed = Feed.builder() - .activityContent("활동 내용") - .feedType(FeedType.IMAGE) - .build(); - String uuid = "550e8400-e29b-41d4-a716-446655440000"; - - // when - FeedLike feedLike = FeedFixture.createFeedLike(feed, uuid); - - // then - assertThat(feedLike.getFeed()).isEqualTo(feed); - assertThat(feedLike.getUuid()).isEqualTo(uuid); - } -} diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/repository/FeedLikeRepositoryTest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/repository/FeedLikeRepositoryTest.java deleted file mode 100644 index a8688cdc..00000000 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/repository/FeedLikeRepositoryTest.java +++ /dev/null @@ -1,73 +0,0 @@ -package ddingdong.ddingdongBE.domain.feed.repository; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.SoftAssertions.assertSoftly; - -import ddingdong.ddingdongBE.common.fixture.ClubFixture; -import ddingdong.ddingdongBE.common.fixture.FeedFixture; -import ddingdong.ddingdongBE.common.support.DataJpaTestSupport; -import ddingdong.ddingdongBE.domain.club.entity.Club; -import ddingdong.ddingdongBE.domain.club.repository.ClubRepository; -import ddingdong.ddingdongBE.domain.feed.entity.Feed; -import ddingdong.ddingdongBE.domain.feed.repository.dto.FeedCountDto; -import java.util.List; -import java.util.Map; -import java.util.stream.Collectors; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -class FeedLikeRepositoryTest extends DataJpaTestSupport { - - @Autowired - private FeedLikeRepository feedLikeRepository; - - @Autowired - private FeedRepository feedRepository; - - @Autowired - private ClubRepository clubRepository; - - @DisplayName("여러 피드의 좋아요 수를 한 번에 조회한다") - @Test - void countsByFeedIds_ReturnsCorrectCounts() { - // given - Club club = clubRepository.save(ClubFixture.createClub()); - Feed feed1 = feedRepository.save(FeedFixture.createImageFeed(club, "피드 1")); - Feed feed2 = feedRepository.save(FeedFixture.createImageFeed(club, "피드 2")); - Feed feed3 = feedRepository.save(FeedFixture.createImageFeed(club, "피드 3")); - - feedLikeRepository.save(FeedFixture.createFeedLike(feed1, "uuid-1")); - feedLikeRepository.save(FeedFixture.createFeedLike(feed1, "uuid-2")); - feedLikeRepository.save(FeedFixture.createFeedLike(feed1, "uuid-3")); - feedLikeRepository.save(FeedFixture.createFeedLike(feed2, "uuid-4")); - - List feedIds = List.of(feed1.getId(), feed2.getId(), feed3.getId()); - - // when - List result = feedLikeRepository.countsByFeedIds(feedIds); - - // then - Map countMap = result.stream() - .collect(Collectors.toMap(FeedCountDto::getFeedId, FeedCountDto::getCnt)); - - assertSoftly(softly -> { - softly.assertThat(countMap.getOrDefault(feed1.getId(), 0L)).isEqualTo(3); - softly.assertThat(countMap.getOrDefault(feed2.getId(), 0L)).isEqualTo(1); - softly.assertThat(countMap.containsKey(feed3.getId())).isFalse(); - }); - } - - @DisplayName("좋아요가 없는 피드는 집계 결과에 포함되지 않는다") - @Test - void countsByFeedIds_ReturnsEmptyWhenNoLikes() { - // given - Club club = clubRepository.save(ClubFixture.createClub()); - Feed feed = feedRepository.save(FeedFixture.createImageFeed(club, "피드")); - - // when - List result = feedLikeRepository.countsByFeedIds(List.of(feed.getId())); - - // then - assertThat(result).isEmpty(); - } -} diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepositoryTest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepositoryTest.java index 92048bcc..b9e0f95a 100644 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepositoryTest.java +++ b/src/test/java/ddingdong/ddingdongBE/domain/feed/repository/FeedRepositoryTest.java @@ -38,9 +38,6 @@ class FeedRepositoryTest extends DataJpaTestSupport { @Autowired private VodProcessingJobRepository vodProcessingJobRepository; - @Autowired - private FeedLikeRepository feedLikeRepository; - @Autowired private FeedCommentRepository feedCommentRepository; @@ -289,7 +286,7 @@ void findMonthlyRankingByClub_ReturnsRankingsWithCorrectScore() { Feed feedA2 = feedRepository.save(FeedFixture.createImageFeed(clubA, "A 피드 2")); Feed feedB1 = feedRepository.save(FeedFixture.createImageFeed(clubB, "B 피드 1")); - feedLikeRepository.save(FeedFixture.createFeedLike(feedA1, "uuid-1")); + feedRepository.addLikeCount(feedA1.getId(), 1); feedCommentRepository.save(FeedFixture.createFeedComment(feedA1, "uuid-2", 1, "댓글")); entityManager.flush(); diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedServiceTest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedServiceTest.java index ace2a52e..993098ac 100644 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedServiceTest.java +++ b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/FacadeFeedServiceTest.java @@ -11,10 +11,8 @@ import ddingdong.ddingdongBE.domain.club.repository.ClubRepository; import ddingdong.ddingdongBE.domain.feed.entity.Feed; import ddingdong.ddingdongBE.domain.feed.entity.FeedComment; -import ddingdong.ddingdongBE.domain.feed.entity.FeedLike; import ddingdong.ddingdongBE.domain.feed.entity.FeedType; import ddingdong.ddingdongBE.domain.feed.repository.FeedCommentRepository; -import ddingdong.ddingdongBE.domain.feed.repository.FeedLikeRepository; import ddingdong.ddingdongBE.domain.feed.repository.FeedRepository; import ddingdong.ddingdongBE.domain.feed.service.dto.query.FeedQuery; import ddingdong.ddingdongBE.domain.filemetadata.entity.DomainType; @@ -53,9 +51,6 @@ class FacadeFeedServiceTest extends TestContainerSupport { @Autowired private FileMetaDataRepository fileMetaDataRepository; - @Autowired - private FeedLikeRepository feedLikeRepository; - @Autowired private FeedCommentRepository feedCommentRepository; @@ -103,6 +98,7 @@ void getFeedById() { .set("club", savedClub) .set("activityContent", "카우 활동내역") .set("feedType", FeedType.IMAGE) + .set("likeCount", 0L) .set("createdAt", now) .sample(); Feed savedFeed = feedRepository.save(feed); @@ -170,6 +166,7 @@ void getFeedById_WithLikesAndComments() { .set("club", savedClub) .set("activityContent", "활동 내역") .set("feedType", FeedType.IMAGE) + .set("likeCount", 0L) .set("createdAt", LocalDateTime.now()) .sample(); Feed savedFeed = feedRepository.save(feed); @@ -184,8 +181,8 @@ void getFeedById_WithLikesAndComments() { .sample() ); - feedLikeRepository.save(FeedLike.builder().feed(savedFeed).uuid("uuid-1").build()); - feedLikeRepository.save(FeedLike.builder().feed(savedFeed).uuid("uuid-2").build()); + feedRepository.addLikeCount(savedFeed.getId(), 1); + feedRepository.addLikeCount(savedFeed.getId(), 1); feedCommentRepository.save(FeedComment.builder().feed(savedFeed).uuid("uuid-3").anonymousNumber(1).content("댓글 1").build()); BDDMockito.given(s3FileService.getUploadedFileUrl(any())) diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedLikeServiceTest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedLikeServiceTest.java deleted file mode 100644 index 1e31efa6..00000000 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedLikeServiceTest.java +++ /dev/null @@ -1,127 +0,0 @@ -package ddingdong.ddingdongBE.domain.feed.service; - -import static org.assertj.core.api.Assertions.assertThat; -import static org.assertj.core.api.Assertions.assertThatThrownBy; - -import ddingdong.ddingdongBE.common.exception.FeedException.DuplicatedFeedLikeException; -import ddingdong.ddingdongBE.common.exception.FeedException.FeedLikeNotFoundException; -import ddingdong.ddingdongBE.common.fixture.ClubFixture; -import ddingdong.ddingdongBE.common.fixture.FeedFixture; -import ddingdong.ddingdongBE.common.support.TestContainerSupport; -import ddingdong.ddingdongBE.domain.club.entity.Club; -import ddingdong.ddingdongBE.domain.club.repository.ClubRepository; -import ddingdong.ddingdongBE.domain.feed.entity.Feed; -import ddingdong.ddingdongBE.domain.feed.repository.FeedLikeRepository; -import ddingdong.ddingdongBE.domain.feed.repository.FeedRepository; -import java.util.UUID; -import org.junit.jupiter.api.DisplayName; -import org.junit.jupiter.api.Test; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class GeneralFeedLikeServiceTest extends TestContainerSupport { - - @Autowired - private FeedLikeService feedLikeService; - - @Autowired - private FeedRepository feedRepository; - - @Autowired - private FeedLikeRepository feedLikeRepository; - - @Autowired - private ClubRepository clubRepository; - - @DisplayName("피드 좋아요 생성 - 성공: 좋아요가 저장된다.") - @Test - void create_success() { - // given - String uuid = UUID.randomUUID().toString(); - Club club = clubRepository.save(ClubFixture.createClub()); - Feed feed = feedRepository.save(FeedFixture.createImageFeed(club, "활동 내용")); - - // when - feedLikeService.create(feed.getId(), uuid); - - // then - long count = feedLikeRepository.countByFeedId(feed.getId()); - assertThat(count).isEqualTo(1L); - } - - @DisplayName("피드 좋아요 생성 - 실패: 이미 좋아요한 피드에 좋아요하면 예외가 발생한다.") - @Test - void create_fail_duplicate() { - // given - String uuid = UUID.randomUUID().toString(); - Club club = clubRepository.save(ClubFixture.createClub()); - Feed feed = feedRepository.save(FeedFixture.createImageFeed(club, "활동 내용")); - feedLikeService.create(feed.getId(), uuid); - - // when & then - assertThatThrownBy(() -> feedLikeService.create(feed.getId(), uuid)) - .isInstanceOf(DuplicatedFeedLikeException.class); - } - - @DisplayName("피드 좋아요 취소 - 성공: 좋아요가 삭제된다.") - @Test - void delete_success() { - // given - String uuid = UUID.randomUUID().toString(); - Club club = clubRepository.save(ClubFixture.createClub()); - Feed feed = feedRepository.save(FeedFixture.createImageFeed(club, "활동 내용")); - feedLikeService.create(feed.getId(), uuid); - - // when - feedLikeService.delete(feed.getId(), uuid); - - // then - long count = feedLikeRepository.countByFeedId(feed.getId()); - assertThat(count).isEqualTo(0L); - } - - @DisplayName("피드 좋아요 취소 - 실패: 좋아요 기록이 없으면 예외가 발생한다.") - @Test - void delete_fail_notFound() { - // given - String uuid = UUID.randomUUID().toString(); - Club club = clubRepository.save(ClubFixture.createClub()); - Feed feed = feedRepository.save(FeedFixture.createImageFeed(club, "활동 내용")); - - // when & then - assertThatThrownBy(() -> feedLikeService.delete(feed.getId(), uuid)) - .isInstanceOf(FeedLikeNotFoundException.class); - } - - @DisplayName("피드 좋아요 여부 조회 - 성공: 좋아요한 피드는 true를 반환한다.") - @Test - void existsByFeedIdAndUuid_true() { - // given - String uuid = UUID.randomUUID().toString(); - Club club = clubRepository.save(ClubFixture.createClub()); - Feed feed = feedRepository.save(FeedFixture.createImageFeed(club, "활동 내용")); - feedLikeService.create(feed.getId(), uuid); - - // when - boolean exists = feedLikeService.existsByFeedIdAndUuid(feed.getId(), uuid); - - // then - assertThat(exists).isTrue(); - } - - @DisplayName("피드 좋아요 여부 조회 - 성공: 좋아요하지 않은 피드는 false를 반환한다.") - @Test - void existsByFeedIdAndUuid_false() { - // given - String uuid = UUID.randomUUID().toString(); - Club club = clubRepository.save(ClubFixture.createClub()); - Feed feed = feedRepository.save(FeedFixture.createImageFeed(club, "활동 내용")); - - // when - boolean exists = feedLikeService.existsByFeedIdAndUuid(feed.getId(), uuid); - - // then - assertThat(exists).isFalse(); - } -} diff --git a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java index 2fb2b82d..9c353fc1 100644 --- a/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java +++ b/src/test/java/ddingdong/ddingdongBE/domain/feed/service/GeneralFeedRankingServiceTest.java @@ -11,7 +11,6 @@ import ddingdong.ddingdongBE.domain.club.repository.ClubRepository; import ddingdong.ddingdongBE.domain.feed.entity.Feed; import ddingdong.ddingdongBE.domain.feed.repository.FeedCommentRepository; -import ddingdong.ddingdongBE.domain.feed.repository.FeedLikeRepository; import ddingdong.ddingdongBE.domain.feed.repository.FeedRepository; import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubFeedRankingQuery; import ddingdong.ddingdongBE.domain.feed.service.dto.query.ClubMonthlyStatusQuery; @@ -39,9 +38,6 @@ class GeneralFeedRankingServiceTest extends TestContainerSupport { @Autowired private FeedRepository feedRepository; - @Autowired - private FeedLikeRepository feedLikeRepository; - @Autowired private FeedCommentRepository feedCommentRepository; @@ -179,8 +175,8 @@ void getClubFeedRanking_withLikesAndComments() { Feed feed = feedRepository.save(FeedFixture.createImageFeed(club, "피드")); // 좋아요 2개 - feedLikeRepository.save(FeedFixture.createFeedLike(feed, "uuid-1")); - feedLikeRepository.save(FeedFixture.createFeedLike(feed, "uuid-2")); + feedRepository.addLikeCount(feed.getId(), 1); + feedRepository.addLikeCount(feed.getId(), 1); // 댓글 1개 feedCommentRepository.save(FeedFixture.createFeedComment(feed, "uuid-3", 1, "댓글"));