diff --git a/src/main/java/team/haedal/gifticionfunding/controller/FriendshipController.java b/src/main/java/team/haedal/gifticionfunding/controller/FriendshipController.java new file mode 100644 index 0000000..1ca3579 --- /dev/null +++ b/src/main/java/team/haedal/gifticionfunding/controller/FriendshipController.java @@ -0,0 +1,36 @@ +package team.haedal.gifticionfunding.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import team.haedal.gifticionfunding.annotation.UserId; +import team.haedal.gifticionfunding.dto.UserDto; +import team.haedal.gifticionfunding.dto.common.PagingResponse; +import team.haedal.gifticionfunding.dto.common.ResponseDto; +import team.haedal.gifticionfunding.service.FriendshipService; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class FriendshipController { + private final FriendshipService friendshipService; + + @GetMapping("/v1/profile/friends") + public ResponseEntity getFriends( + @UserId Long userId, + @RequestParam(value = "depth", defaultValue = "1") Integer depth, + @RequestParam(value = "page", defaultValue = "0") Integer page, + @RequestParam(value = "size", defaultValue = "10") Integer size + ) { + PagingResponse userDtoPage = friendshipService.getFriends(userId, depth, page, size); + return new ResponseEntity<>( + new ResponseDto<>(1, "친구 목록 조회 성공", userDtoPage), + HttpStatus.OK + ); + } + +} diff --git a/src/main/java/team/haedal/gifticionfunding/controller/FundingArticleController.java b/src/main/java/team/haedal/gifticionfunding/controller/FundingArticleController.java new file mode 100644 index 0000000..3acd262 --- /dev/null +++ b/src/main/java/team/haedal/gifticionfunding/controller/FundingArticleController.java @@ -0,0 +1,73 @@ +package team.haedal.gifticionfunding.controller; + +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.RestController; +import team.haedal.gifticionfunding.annotation.UserId; +import team.haedal.gifticionfunding.dto.FundingArticleDetailDto; +import team.haedal.gifticionfunding.dto.FundingArticleDto; +import team.haedal.gifticionfunding.dto.common.PagingResponse; +import team.haedal.gifticionfunding.dto.common.ResponseDto; +import team.haedal.gifticionfunding.service.FundingArticleService; + +@RestController +@RequestMapping("/api") +@RequiredArgsConstructor +public class FundingArticleController { + private final FundingArticleService fundingArticleService; + + @GetMapping("/v1/fundings/articles") + public ResponseEntity getFundingArticles( + @UserId Long userId, + @RequestParam(value = "page", defaultValue = "0") Integer page, + @RequestParam(value = "size", defaultValue = "10") Integer size + ) { + PagingResponse fundingArticleDtoPage = fundingArticleService.getFundingArticles(userId, page, size); + return new ResponseEntity<>( + new ResponseDto<>(1, "펀딩 게시글 목록 조회 성공", fundingArticleDtoPage), + HttpStatus.OK + ); + } + + @GetMapping("/v1/fundings/articles/{articleId}") + public ResponseEntity getFundingArticle( + @UserId Long userId, + @RequestParam(value = "articleId") Long articleId + ) { + FundingArticleDetailDto fundingArticleDto = fundingArticleService.getFundingArticle(userId, articleId); + return new ResponseEntity<>( + new ResponseDto<>(1, "펀딩 게시글 조회 성공", fundingArticleDto), + HttpStatus.OK + ); + } + + @PatchMapping("/v1/fundings/articles/{articleId}/expiration") + public ResponseEntity updateFundingArticleExpiration( + @UserId Long userId, + @RequestParam(value = "articleId") Long articleId + ) { + fundingArticleService.updateFundingArticleExpiration(userId, articleId); + return new ResponseEntity<>( + new ResponseDto<>(1, "펀딩 게시글 연장 성공", null), + HttpStatus.OK + ); + } + + @PostMapping("/v1/fundings/articles/gifticons/{fundingArticleGifticonId}/success") + public ResponseEntity receiveFunding( + @UserId Long userId, + @RequestParam(value = "fundingArticleGifticonId") Long fundingArticleGifticonId + ) { + fundingArticleService.receiveFunding(userId, fundingArticleGifticonId); + return new ResponseEntity<>( + new ResponseDto<>(1, "펀딩 게시글 성공 처리 성공", null), + HttpStatus.OK + ); + } +} diff --git a/src/main/java/team/haedal/gifticionfunding/dto/FundingArticleDetailDto.java b/src/main/java/team/haedal/gifticionfunding/dto/FundingArticleDetailDto.java new file mode 100644 index 0000000..e59dcb3 --- /dev/null +++ b/src/main/java/team/haedal/gifticionfunding/dto/FundingArticleDetailDto.java @@ -0,0 +1,37 @@ +package team.haedal.gifticionfunding.dto; + +import java.util.List; +import java.util.Map; +import team.haedal.gifticionfunding.entity.funding.FundingArticle; + +public record FundingArticleDetailDto( + String author, + String birthdate, + String title, + String content, + String endAt, + List goalGifticons +) { + + public static FundingArticleDetailDto of( + FundingArticle fundingArticle, + Map fundAmountMap, + Map numberOfSupportersMap + ) { + return new FundingArticleDetailDto( + fundingArticle.getAuthor().getNickname(), + fundingArticle.getAuthor().getBirthdate().toString(), + fundingArticle.getTitle(), + fundingArticle.getContent(), + fundingArticle.getEndAt().toString(), + fundingArticle.getGifticons().stream() + .map(fundingArticleGifticon -> FundingArticleGifticonDto.of( + fundingArticleGifticon, + fundAmountMap.getOrDefault(fundingArticleGifticon.getId(), 0), + numberOfSupportersMap.getOrDefault(fundingArticleGifticon.getId(), 0) + ) + ) + .toList() + ); + } +} diff --git a/src/main/java/team/haedal/gifticionfunding/dto/FundingArticleDto.java b/src/main/java/team/haedal/gifticionfunding/dto/FundingArticleDto.java new file mode 100644 index 0000000..f1bc3af --- /dev/null +++ b/src/main/java/team/haedal/gifticionfunding/dto/FundingArticleDto.java @@ -0,0 +1,29 @@ +package team.haedal.gifticionfunding.dto; + +import java.time.format.DateTimeFormatter; +import java.util.List; +import team.haedal.gifticionfunding.entity.funding.FundingArticle; + +public record FundingArticleDto( + String author, + String birthdate, + String title, + String content, + String endAt, + List gificonImageUrls +) { + public static FundingArticleDto fromEntity(FundingArticle fundingArticle) { + return new FundingArticleDto( + fundingArticle.getAuthor().getNickname(), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(fundingArticle.getAuthor().getBirthdate()), + fundingArticle.getTitle(), + fundingArticle.getContent(), + DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss").format(fundingArticle.getEndAt()), + fundingArticle.getGifticons().stream() + .map(g -> g.getGifticon().getImageUrl()) + .toList() + ); + + + } +} diff --git a/src/main/java/team/haedal/gifticionfunding/dto/FundingArticleGifticonDto.java b/src/main/java/team/haedal/gifticionfunding/dto/FundingArticleGifticonDto.java new file mode 100644 index 0000000..ea4e2a7 --- /dev/null +++ b/src/main/java/team/haedal/gifticionfunding/dto/FundingArticleGifticonDto.java @@ -0,0 +1,29 @@ +package team.haedal.gifticionfunding.dto; + +import team.haedal.gifticionfunding.entity.funding.FundingArticleGifticon; + +public record FundingArticleGifticonDto( + String gifticonName, + Integer price, + String category, + String imageUrl, + Integer achievementRate, + Integer currentFundAmount, + Integer numberOfSupporters +) { + + public static FundingArticleGifticonDto of(FundingArticleGifticon fundingArticleGifticon, Integer currentFundAmount, Integer numberOfSupporters) { + int achievementRate = (int) ((double) currentFundAmount / fundingArticleGifticon.getGifticon().getPrice() * 100); + int price = fundingArticleGifticon.getGifticon().getPrice().intValue(); + + return new FundingArticleGifticonDto( + fundingArticleGifticon.getGifticon().getName(), + price, + fundingArticleGifticon.getGifticon().getCategory().toString(), + fundingArticleGifticon.getGifticon().getImageUrl(), + achievementRate, + currentFundAmount, + numberOfSupporters + ); + } +} diff --git a/src/main/java/team/haedal/gifticionfunding/dto/UserDto.java b/src/main/java/team/haedal/gifticionfunding/dto/UserDto.java new file mode 100644 index 0000000..f7a9451 --- /dev/null +++ b/src/main/java/team/haedal/gifticionfunding/dto/UserDto.java @@ -0,0 +1,24 @@ +package team.haedal.gifticionfunding.dto; + +import java.time.format.DateTimeFormatter; +import lombok.Builder; +import team.haedal.gifticionfunding.entity.user.User; + +@Builder +public record UserDto ( + Long id, + String name, + String birthdate, + String profileImageUrl +){ + public static UserDto fromEntity(User user) { + DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyy-MM-dd"); + + return UserDto.builder() + .id(user.getId()) + .birthdate(user.getBirthdate().format(formatter)) + .profileImageUrl(user.getProfileImageUrl()) + .name(user.getNickname()) + .build(); + } +} diff --git a/src/main/java/team/haedal/gifticionfunding/entity/funding/FundingArticle.java b/src/main/java/team/haedal/gifticionfunding/entity/funding/FundingArticle.java index f5f4834..b97676c 100644 --- a/src/main/java/team/haedal/gifticionfunding/entity/funding/FundingArticle.java +++ b/src/main/java/team/haedal/gifticionfunding/entity/funding/FundingArticle.java @@ -1,7 +1,10 @@ package team.haedal.gifticionfunding.entity.funding; +import jakarta.persistence.CascadeType; import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -16,12 +19,15 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import org.hibernate.annotations.DynamicUpdate; import team.haedal.gifticionfunding.entity.common.BaseTimeEntity; +import team.haedal.gifticionfunding.entity.type.EFundingArticleStatus; import team.haedal.gifticionfunding.entity.user.User; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity +@DynamicUpdate public class FundingArticle extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -40,7 +46,11 @@ public class FundingArticle extends BaseTimeEntity { @Column(nullable = false) private LocalDateTime endAt; - @OneToMany(mappedBy = "fundingArticle", fetch = FetchType.LAZY) + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private EFundingArticleStatus status; + + @OneToMany(mappedBy = "fundingArticle", fetch = FetchType.LAZY, cascade = CascadeType.ALL) private List gifticons = new ArrayList<>(); @Builder private FundingArticle(User author, String title, String content, LocalDateTime endAt) { @@ -48,5 +58,15 @@ private FundingArticle(User author, String title, String content, LocalDateTime this.title = title; this.content = content; this.endAt = endAt; + + this.status = EFundingArticleStatus.PROCESSING; + } + + public void updateExpiration(int maxExtensionDate) { + this.endAt = this.endAt.plusDays(maxExtensionDate); + } + + public void updateStatus(EFundingArticleStatus status) { + this.status = status; } } diff --git a/src/main/java/team/haedal/gifticionfunding/entity/funding/FundingArticleGifticon.java b/src/main/java/team/haedal/gifticionfunding/entity/funding/FundingArticleGifticon.java index 6f1d94c..d40c0d6 100644 --- a/src/main/java/team/haedal/gifticionfunding/entity/funding/FundingArticleGifticon.java +++ b/src/main/java/team/haedal/gifticionfunding/entity/funding/FundingArticleGifticon.java @@ -1,6 +1,9 @@ package team.haedal.gifticionfunding.entity.funding; +import jakarta.persistence.Column; import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; @@ -12,6 +15,7 @@ import lombok.Getter; import lombok.NoArgsConstructor; import team.haedal.gifticionfunding.entity.gifticon.Gifticon; +import team.haedal.gifticionfunding.entity.type.EFundingArticleGifticonStatus; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -29,9 +33,17 @@ public class FundingArticleGifticon { @JoinColumn(name = "funding_article_id") private FundingArticle fundingArticle; + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private EFundingArticleGifticonStatus status; + @Builder private FundingArticleGifticon(Gifticon gifticon, FundingArticle fundingArticle) { this.gifticon = gifticon; this.fundingArticle = fundingArticle; } + + public void updateStatus(EFundingArticleGifticonStatus eFundingArticleGifticonStatus) { + this.status = eFundingArticleGifticonStatus; + } } diff --git a/src/main/java/team/haedal/gifticionfunding/entity/funding/FundingContribute.java b/src/main/java/team/haedal/gifticionfunding/entity/funding/FundingContribute.java index 485991c..547a42d 100644 --- a/src/main/java/team/haedal/gifticionfunding/entity/funding/FundingContribute.java +++ b/src/main/java/team/haedal/gifticionfunding/entity/funding/FundingContribute.java @@ -1,6 +1,7 @@ package team.haedal.gifticionfunding.entity.funding; import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; import jakarta.persistence.GeneratedValue; import jakarta.persistence.GenerationType; import jakarta.persistence.Id; @@ -11,8 +12,8 @@ import lombok.Getter; import lombok.NoArgsConstructor; import team.haedal.gifticionfunding.entity.common.BaseTimeEntity; -import team.haedal.gifticionfunding.entity.user.User; import team.haedal.gifticionfunding.entity.gifticon.UserGifticon; +import team.haedal.gifticionfunding.entity.user.User; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -24,23 +25,23 @@ public class FundingContribute extends BaseTimeEntity { private Long point; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_id") private User contributor; - @ManyToOne - @JoinColumn(name = "funding_article_id") - private FundingArticle fundingArticle; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "funding_article_giftcion_id") + private FundingArticleGifticon fundingArticleGifticon; - @ManyToOne + @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "user_gifticon_id") private UserGifticon userGifticon; @Builder - private FundingContribute(Long point, User contributor, FundingArticle fundingArticle, UserGifticon userGifticon) { + private FundingContribute(Long point, User contributor, FundingArticleGifticon fundingArticleGifticon, UserGifticon userGifticon) { this.point = point; this.contributor = contributor; - this.fundingArticle = fundingArticle; + this.fundingArticleGifticon = fundingArticleGifticon; this.userGifticon = userGifticon; } } diff --git a/src/main/java/team/haedal/gifticionfunding/entity/gifticon/StoreBrand.java b/src/main/java/team/haedal/gifticionfunding/entity/gifticon/StoreBrand.java index fd4f302..237617e 100644 --- a/src/main/java/team/haedal/gifticionfunding/entity/gifticon/StoreBrand.java +++ b/src/main/java/team/haedal/gifticionfunding/entity/gifticon/StoreBrand.java @@ -11,5 +11,8 @@ public enum StoreBrand { STARBUCKS, TWO_SOME_PLACE, EDIYA, HOLLYS, TOM_N_TOMS, COFFEE_BEAN, PARIS_BAGUETTE, BASKIN_ROBBINS, DUNKIN, //Chicken - BBQ, BHC, GOOBNE, KYOCHON, NENE, PELicana, MOMS_TOUCH + BBQ, BHC, GOOBNE, KYOCHON, NENE, + + //Clothes + ZARA, UNIQLO } diff --git a/src/main/java/team/haedal/gifticionfunding/entity/gifticon/UserGifticon.java b/src/main/java/team/haedal/gifticionfunding/entity/gifticon/UserGifticon.java index 39384b9..392741d 100644 --- a/src/main/java/team/haedal/gifticionfunding/entity/gifticon/UserGifticon.java +++ b/src/main/java/team/haedal/gifticionfunding/entity/gifticon/UserGifticon.java @@ -13,13 +13,14 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import team.haedal.gifticionfunding.entity.common.BaseTimeEntity; import team.haedal.gifticionfunding.entity.gifticon.Gifticon; import team.haedal.gifticionfunding.entity.user.User; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity -public class UserGifticon { +public class UserGifticon extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; diff --git a/src/main/java/team/haedal/gifticionfunding/entity/type/EFundingArticleGifticonStatus.java b/src/main/java/team/haedal/gifticionfunding/entity/type/EFundingArticleGifticonStatus.java new file mode 100644 index 0000000..848f0b4 --- /dev/null +++ b/src/main/java/team/haedal/gifticionfunding/entity/type/EFundingArticleGifticonStatus.java @@ -0,0 +1,15 @@ +package team.haedal.gifticionfunding.entity.type; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum EFundingArticleGifticonStatus { + PROCESSING("PROCESSING"), + FINISH_SUCCESS("FINISH_SUCCESS"), + FINISH_FAIL("FINISH_FAIL"), + ; + + private final String value; +} diff --git a/src/main/java/team/haedal/gifticionfunding/entity/type/EFundingArticleStatus.java b/src/main/java/team/haedal/gifticionfunding/entity/type/EFundingArticleStatus.java new file mode 100644 index 0000000..c4cf55f --- /dev/null +++ b/src/main/java/team/haedal/gifticionfunding/entity/type/EFundingArticleStatus.java @@ -0,0 +1,14 @@ +package team.haedal.gifticionfunding.entity.type; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum EFundingArticleStatus { + PROCESSING("PROCESSING"), + FINISH("FINISH"), + ; + + private final String value; +} diff --git a/src/main/java/team/haedal/gifticionfunding/entity/user/Friendship.java b/src/main/java/team/haedal/gifticionfunding/entity/user/Friendship.java index 7015445..326342b 100644 --- a/src/main/java/team/haedal/gifticionfunding/entity/user/Friendship.java +++ b/src/main/java/team/haedal/gifticionfunding/entity/user/Friendship.java @@ -10,26 +10,27 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import team.haedal.gifticionfunding.entity.common.BaseTimeEntity; @Getter @NoArgsConstructor(access = AccessLevel.PROTECTED) @Entity -public class Friendship { +public class Friendship extends BaseTimeEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @ManyToOne - @JoinColumn(name = "user1_id") - private User user1; + @JoinColumn(name = "from_user_id") + private User fromUser; @ManyToOne - @JoinColumn(name = "user2_id") - private User user2; + @JoinColumn(name = "to_user_id") + private User toUser; @Builder - private Friendship(User user1, User user2) { - this.user1 = user1; - this.user2 = user2; + private Friendship(User fromUser, User toUser) { + this.fromUser = fromUser; + this.toUser = toUser; } } diff --git a/src/main/java/team/haedal/gifticionfunding/entity/user/FriendshipProposal.java b/src/main/java/team/haedal/gifticionfunding/entity/user/FriendshipProposal.java deleted file mode 100644 index 603b948..0000000 --- a/src/main/java/team/haedal/gifticionfunding/entity/user/FriendshipProposal.java +++ /dev/null @@ -1,36 +0,0 @@ -package team.haedal.gifticionfunding.entity.user; - -import jakarta.persistence.Entity; -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; -import team.haedal.gifticionfunding.entity.common.BaseTimeEntity; - -@Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) -@Entity -public class FriendshipProposal extends BaseTimeEntity { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; - - @ManyToOne - @JoinColumn(name = "from_user_id") - private User fromUser; - - @ManyToOne - @JoinColumn(name = "to_user_id") - private User toUser; - - @Builder - private FriendshipProposal(User fromUser, User toUser) { - this.fromUser = fromUser; - this.toUser = toUser; - } -} diff --git a/src/main/java/team/haedal/gifticionfunding/repository/funding/FundingArticleGifticonRepository.java b/src/main/java/team/haedal/gifticionfunding/repository/funding/FundingArticleGifticonRepository.java index dfb1d81..b7d222f 100644 --- a/src/main/java/team/haedal/gifticionfunding/repository/funding/FundingArticleGifticonRepository.java +++ b/src/main/java/team/haedal/gifticionfunding/repository/funding/FundingArticleGifticonRepository.java @@ -1,7 +1,17 @@ package team.haedal.gifticionfunding.repository.funding; +import java.util.Optional; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import team.haedal.gifticionfunding.entity.funding.FundingArticleGifticon; public interface FundingArticleGifticonRepository extends JpaRepository { + @Query("SELECT fag FROM FundingArticleGifticon fag " + + "JOIN FETCH fag.fundingArticle " + + "JOIN FETCH fag.fundingArticle.author " + + "WHERE fag.id = :fundingArticleGifticonId") + Optional findWithArticleAndAuthorById( + @Param("fundingArticleGifticonId") Long fundingArticleGifticonId + ); } diff --git a/src/main/java/team/haedal/gifticionfunding/repository/funding/FundingArticleRepository.java b/src/main/java/team/haedal/gifticionfunding/repository/funding/FundingArticleRepository.java index 7d89113..0e825b7 100644 --- a/src/main/java/team/haedal/gifticionfunding/repository/funding/FundingArticleRepository.java +++ b/src/main/java/team/haedal/gifticionfunding/repository/funding/FundingArticleRepository.java @@ -1,7 +1,34 @@ package team.haedal.gifticionfunding.repository.funding; +import java.util.Optional; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.EntityGraph; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import team.haedal.gifticionfunding.entity.funding.FundingArticle; public interface FundingArticleRepository extends JpaRepository { + @Query( + "SELECT fa FROM FundingArticle fa " + + "JOIN FETCH fa.author " + + "JOIN FETCH fa.gifticons " + + "WHERE fa.author.id IN (" + + " SELECT f.toUser.id FROM Friendship f " + + "WHERE f.fromUser.id = :userId " + // cl + " OR f.toUser.id IN (" + + " SELECT f2.toUser.id FROM Friendship f1 JOIN Friendship f2 ON f1.toUser.id = f2.fromUser.id WHERE f1.fromUser.id = :userId" + + " )" + + ")" + ) + Page findAllWithAuthorAndGifticonsByFriendOfFriend( + @Param("userId") Long userId, + Pageable pageable); + + @EntityGraph(attributePaths = {"author", "gifticons"}) + Optional findAllWithAuthorAndGifticonsById(Long articleId); + + @EntityGraph(attributePaths = {"author"}) + Optional findWithAuthorById(Long articleId); } diff --git a/src/main/java/team/haedal/gifticionfunding/repository/funding/FundingContributeRepository.java b/src/main/java/team/haedal/gifticionfunding/repository/funding/FundingContributeRepository.java index 3cda247..e059864 100644 --- a/src/main/java/team/haedal/gifticionfunding/repository/funding/FundingContributeRepository.java +++ b/src/main/java/team/haedal/gifticionfunding/repository/funding/FundingContributeRepository.java @@ -1,7 +1,45 @@ package team.haedal.gifticionfunding.repository.funding; +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 team.haedal.gifticionfunding.entity.funding.FundingContribute; public interface FundingContributeRepository extends JpaRepository { + + @Query( + "SELECT fc.fundingArticleGifticon.id as fundingArticleGifticonId, " + + "COUNT(fc) as contributerNumber " + + "FROM FundingContribute fc " + + "WHERE fc.fundingArticleGifticon.id IN :fundingArticleGifticonIds " + + "GROUP BY fc.fundingArticleGifticon.id" + ) + List countByFundingArticleGifticonIdIn( + @Param("fundingArticleGifticonIds") List fundingArticleGifticonIds + ); + + @Query( + "SELECT fc.fundingArticleGifticon.id as fundingArticleGifticonId, " + + "SUM(" + + "CASE WHEN fc.point IS NULL THEN fc.userGifticon.gifticon.price " + + "WHEN fc.userGifticon IS NULL THEN fc.point END" + + ") as contributeAmount " + + "FROM FundingContribute fc " + + "WHERE fc.fundingArticleGifticon.id IN :fundingArticleGifticonIds " + + "GROUP BY fc.fundingArticleGifticon.id" + ) + List sumByFundingArticleGifticonIdIn( + @Param("fundingArticleGifticonIds") List fundingArticleGifticonIds + ); + + interface FundingContributerNumber { + Long getFundingArticleGifticonId(); + Integer getContributerNumber(); + } + + interface FundingContributeAmount { + Long getFundingArticleGifticonId(); + Integer getContributeAmount(); + } } diff --git a/src/main/java/team/haedal/gifticionfunding/repository/user/FriendshipProposalRepository.java b/src/main/java/team/haedal/gifticionfunding/repository/user/FriendshipProposalRepository.java deleted file mode 100644 index c4f36e3..0000000 --- a/src/main/java/team/haedal/gifticionfunding/repository/user/FriendshipProposalRepository.java +++ /dev/null @@ -1,7 +0,0 @@ -package team.haedal.gifticionfunding.repository.user; - -import org.springframework.data.jpa.repository.JpaRepository; -import team.haedal.gifticionfunding.entity.user.FriendshipProposal; - -public interface FriendshipProposalRepository extends JpaRepository { -} diff --git a/src/main/java/team/haedal/gifticionfunding/repository/user/FriendshipRepository.java b/src/main/java/team/haedal/gifticionfunding/repository/user/FriendshipRepository.java index d600a4b..f80db21 100644 --- a/src/main/java/team/haedal/gifticionfunding/repository/user/FriendshipRepository.java +++ b/src/main/java/team/haedal/gifticionfunding/repository/user/FriendshipRepository.java @@ -1,7 +1,36 @@ package team.haedal.gifticionfunding.repository.user; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import team.haedal.gifticionfunding.entity.user.Friendship; +import team.haedal.gifticionfunding.entity.user.User; public interface FriendshipRepository extends JpaRepository { + @Query("SELECT f.toUser FROM Friendship f WHERE f.fromUser.id = :userId") + Page findFriendsByUserId(@Param("userId") Long userId, Pageable pageable); + + @Query("SELECT DISTINCT u FROM User u " + + "WHERE u.id IN (" + + " SELECT f.toUser.id FROM Friendship f WHERE f.fromUser.id = :userId " + // cl + " OR f.toUser.id IN (" + + " SELECT f2.toUser.id FROM Friendship f1 JOIN Friendship f2 ON f1.toUser.id = f2.fromUser.id WHERE f1.fromUser.id = :userId" + + " )" + + ")") + Page findFriendsAndFriendsOfFriends( + @Param("userId") Long userId, + Pageable pageable + ); + + @Query("SELECT CASE WHEN COUNT(u) > 0 THEN TRUE ELSE FALSE END FROM User u " + + "WHERE u.id = :targetUserId AND u.id IN (" + + " SELECT f2.toUser.id FROM Friendship f1 " + + " JOIN Friendship f2 ON f1.toUser.id = f2.fromUser.id " + + " WHERE f1.fromUser.id = :userId)") + Boolean existsUserInFriendsOfFriends( + @Param("userId") Long userId, + @Param("targetUserId") Long targetUserId + ); } diff --git a/src/main/java/team/haedal/gifticionfunding/service/FriendshipService.java b/src/main/java/team/haedal/gifticionfunding/service/FriendshipService.java new file mode 100644 index 0000000..9877462 --- /dev/null +++ b/src/main/java/team/haedal/gifticionfunding/service/FriendshipService.java @@ -0,0 +1,51 @@ +package team.haedal.gifticionfunding.service; + + +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import team.haedal.gifticionfunding.dto.UserDto; +import team.haedal.gifticionfunding.dto.common.PagingResponse; +import team.haedal.gifticionfunding.entity.user.User; +import team.haedal.gifticionfunding.handler.ex.CustomApiException; +import team.haedal.gifticionfunding.repository.user.FriendshipRepository; + +@Service +@RequiredArgsConstructor +public class FriendshipService { + private final FriendshipRepository friendshipRepository; + + public PagingResponse getFriends(Long userId, Integer depth, Integer page, Integer size) { + if (page < 0 || size <= 0) { + throw new CustomApiException("page는 0이상, size는 1이상이어야 합니다."); + } + + Pageable pageable = PageRequest.of(page, size, Sort.by("nickname").ascending()); + + // 친구 목록 조회 + if (depth == 1) { + Page frinedPage = friendshipRepository.findFriendsByUserId(userId, pageable); + return PagingResponse.builder() + .hasNext(frinedPage.hasNext()) + .data(frinedPage.getContent().stream() + .map(UserDto::fromEntity) + .toList()) + .build(); + } + + // 친구의 친구까지 목록 조회 + if (depth == 2) { + Page friendPage = friendshipRepository.findFriendsAndFriendsOfFriends(userId, pageable); + return PagingResponse.builder() + .hasNext(friendPage.hasNext()) + .data(friendPage.getContent().stream() + .map(UserDto::fromEntity) + .toList()) + .build(); + } + throw new CustomApiException("depth는 1또는 2만 가능합니다."); + } +} diff --git a/src/main/java/team/haedal/gifticionfunding/service/FundingArticleService.java b/src/main/java/team/haedal/gifticionfunding/service/FundingArticleService.java new file mode 100644 index 0000000..c3d8eeb --- /dev/null +++ b/src/main/java/team/haedal/gifticionfunding/service/FundingArticleService.java @@ -0,0 +1,155 @@ +package team.haedal.gifticionfunding.service; + +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; +import team.haedal.gifticionfunding.dto.FundingArticleDetailDto; +import team.haedal.gifticionfunding.dto.FundingArticleDto; +import team.haedal.gifticionfunding.dto.common.PagingResponse; +import team.haedal.gifticionfunding.entity.funding.FundingArticle; +import team.haedal.gifticionfunding.entity.funding.FundingArticleGifticon; +import team.haedal.gifticionfunding.entity.gifticon.UserGifticon; +import team.haedal.gifticionfunding.entity.type.EFundingArticleGifticonStatus; +import team.haedal.gifticionfunding.entity.type.EFundingArticleStatus; +import team.haedal.gifticionfunding.repository.funding.FundingArticleGifticonRepository; +import team.haedal.gifticionfunding.repository.funding.FundingArticleRepository; +import team.haedal.gifticionfunding.repository.funding.FundingContributeRepository; +import team.haedal.gifticionfunding.repository.funding.FundingContributeRepository.FundingContributeAmount; +import team.haedal.gifticionfunding.repository.funding.FundingContributeRepository.FundingContributerNumber; +import team.haedal.gifticionfunding.repository.user.FriendshipRepository; +import team.haedal.gifticionfunding.repository.user.UserGifticonRepository; + +@Service +@RequiredArgsConstructor +public class FundingArticleService { + private static final int MAX_EXTENSION_DATE = 3; + + private final FundingArticleRepository fundingArticleRepository; + private final FundingContributeRepository fundingContributeRepository; + private final FundingArticleGifticonRepository fundingArticleGifticonRepository; + private final UserGifticonRepository userGifticonRepository; + private final FriendshipRepository friendshipRepository; + /** + * 친구 depth2 범위의 펀딩 게시글을 목록 조회한다. + * + * @param page + * @param size + * @return + */ + @Transactional(readOnly = true) + public PagingResponse getFundingArticles(Long userId, Integer page, Integer size) { + Pageable pageable = PageRequest.of(page, size, Sort.by("endAt").ascending()); + + // 친구 depth2 범위의 게시글 조회 + Page fundingArticlePage = fundingArticleRepository.findAllWithAuthorAndGifticonsByFriendOfFriend( + userId, pageable); + + // FundingArticleDto로 변환 + List fundingArticleDtoList = fundingArticlePage.getContent().stream() + .map(FundingArticleDto::fromEntity) + .toList(); + + return PagingResponse.builder() + .hasNext(fundingArticlePage.hasNext()) + .data(fundingArticleDtoList) + .build(); + } + + /** + * 펀딩 게시글 상세 조회. 해당 게시글의 후원 금액과 후원자 수를 함께 조회한다. + * + * @param userId + * @param articleId + * @return + */ + @Transactional(readOnly = true) + public FundingArticleDetailDto getFundingArticle(Long userId, Long articleId) { + // 펀딩 게시글 조회 + FundingArticle fundingArticle = fundingArticleRepository.findAllWithAuthorAndGifticonsById(articleId) + .orElseThrow(() -> new IllegalArgumentException("해당 펀딩 게시글이 존재하지 않습니다.")); + + Long authorId = fundingArticle.getAuthor().getId(); + if (!friendshipRepository.existsUserInFriendsOfFriends(userId, authorId)) { + throw new IllegalArgumentException("친구 depth 2 범위의 펀딩 게시글이 아닙니다."); + } + + List fundingArticleGifticonIds = fundingArticle.getGifticons().stream() + .map(fundingArticleGifticon -> fundingArticleGifticon.getId()) + .toList(); + + // 해당 게시글 기프티콘당 성취 금액 조회 + List fundingContributerAmounts = fundingContributeRepository + .sumByFundingArticleGifticonIdIn(fundingArticleGifticonIds); + + Map fundAmountMap = fundingContributerAmounts.stream() + .collect( + Collectors.toMap( + FundingContributeAmount::getFundingArticleGifticonId, + FundingContributeAmount::getContributeAmount + ) + ); + + // 해당 게시글 기프티콘당 후원자 수 조회 + List fundingContributerNumbers = fundingContributeRepository + .countByFundingArticleGifticonIdIn(fundingArticleGifticonIds); + + Map numberOfSupportersMap = fundingContributerNumbers.stream() + .collect( + Collectors.toMap( + FundingContributerNumber::getFundingArticleGifticonId, + FundingContributerNumber::getContributerNumber + ) + ); + + return FundingArticleDetailDto.of(fundingArticle, fundAmountMap, numberOfSupportersMap); + } + + @Transactional + public void updateFundingArticleExpiration(Long userId, Long articleId) { + FundingArticle fundingArticle = fundingArticleRepository.findWithAuthorById(articleId) + .orElseThrow(() -> new IllegalArgumentException("해당 펀딩 게시글이 존재하지 않습니다.")); + + if (!fundingArticle.getAuthor().getId().equals(userId)) { + throw new IllegalArgumentException("해당 펀딩 게시글의 작성자가 아닙니다."); + } + + fundingArticle.updateExpiration(MAX_EXTENSION_DATE); + } + + @Transactional + public void receiveFunding(Long userId, Long fundingArticleGifticonId) { + FundingArticleGifticon fundingArticleGifticon = fundingArticleGifticonRepository.findWithArticleAndAuthorById(fundingArticleGifticonId) + .orElseThrow(() -> new IllegalArgumentException("해당 펀딩 게시글 기프티콘이 존재하지 않습니다.")); + + if (!fundingArticleGifticon.getFundingArticle().getAuthor().getId().equals(userId)) { + throw new IllegalArgumentException("해당 펀딩 게시글의 작성자가 아닙니다."); + } + + // userGifticon 생성 + UserGifticon receivedGifticon = UserGifticon.builder() + .buyer(fundingArticleGifticon.getFundingArticle().getAuthor()) + .owner(fundingArticleGifticon.getFundingArticle().getAuthor()) + .gifticon(fundingArticleGifticon.getGifticon()) + .build(); + + // fundingArticleGifticon 상태 변경 + fundingArticleGifticon.updateStatus(EFundingArticleGifticonStatus.FINISH_SUCCESS); + + // 마지막 후원까지 종료되었을 경우, 펀딩 게시글 상태 변경 + int numberOfReceivedGifticons = (int) fundingArticleGifticon.getFundingArticle().getGifticons().stream() + .filter(fundingArticleGifticon1 -> fundingArticleGifticon1.getStatus().equals(EFundingArticleGifticonStatus.FINISH_SUCCESS)) + .count(); + if (numberOfReceivedGifticons == fundingArticleGifticon.getFundingArticle().getGifticons().size()) { + fundingArticleGifticon.getFundingArticle().updateStatus(EFundingArticleStatus.FINISH); + } + // userGifticon 저장 및 flush + userGifticonRepository.saveAndFlush(receivedGifticon); + } +} diff --git a/src/test/java/team/haedal/gifticionfunding/core/jwt/JwtAuthorizationFilterTest.java b/src/test/java/team/haedal/gifticionfunding/core/jwt/JwtAuthorizationFilterTest.java index 26c5b96..f8e8cd0 100644 --- a/src/test/java/team/haedal/gifticionfunding/core/jwt/JwtAuthorizationFilterTest.java +++ b/src/test/java/team/haedal/gifticionfunding/core/jwt/JwtAuthorizationFilterTest.java @@ -30,6 +30,8 @@ class JwtAuthorizationFilterTest extends DummyFactory { private MockMvc mvc; @Autowired private UserRepository userRepository; + @Autowired + private JwtProvider jwtProvider; @Test @DisplayName("관리자가 아닌 사용자가 접근하면 403을 반환한다.") @@ -38,7 +40,8 @@ void authorization_admin_fail_test() throws Exception { User user = newUser("jae@naver.com", "jaehyeon1114", "1234"); User savedUser = userRepository.save(user); - String jwtToken = JwtProvider.create(savedUser); + String jwtToken = jwtProvider.generateAccessToken(savedUser.getId(), savedUser.getRole()) + .accessToken(); System.out.println("테스트 : " + jwtToken); //when @@ -53,8 +56,9 @@ void authorization_admin_fail_test() throws Exception { void authorization_admin_success_test() throws Exception { //given User admin = newUser("jae@naver.com", "jaehyeon1114", "1234"); - PrincipalDetails loginAdmin = new PrincipalDetails(admin); - String jwtToken = JwtProvider.create(admin); + PrincipalDetails loginAdmin = new PrincipalDetails(1L, admin.getRole()); + String jwtToken = jwtProvider.generateAccessToken(1L, loginAdmin.getRole()) + .accessToken();; System.out.println("테스트 : " + jwtToken); //when diff --git a/src/test/java/team/haedal/gifticionfunding/dummy/DummyFactory.java b/src/test/java/team/haedal/gifticionfunding/dummy/DummyFactory.java index 0cb4655..dbb4906 100644 --- a/src/test/java/team/haedal/gifticionfunding/dummy/DummyFactory.java +++ b/src/test/java/team/haedal/gifticionfunding/dummy/DummyFactory.java @@ -14,7 +14,7 @@ protected static User newUser(String email, String nickname, String password) { .email(email) .password(passwordEncoder.encode(password)) .point(0L) - .userRole(UserRole.ROLE_USER) + .role(UserRole.ROLE_USER) .nickname(nickname) .birthdate(LocalDate.of(1999, 1, 1)) .build(); @@ -26,9 +26,11 @@ protected static User newAdmin(String email, String nickname, String password) { .email(email) .password(passwordEncoder.encode(password)) .point(0L) - .userRole(UserRole.ROLE_ADMIN) + .role(UserRole.ROLE_ADMIN) .nickname(nickname) .birthdate(LocalDate.of(1999, 1, 1)) .build(); } + + } diff --git a/src/test/java/team/haedal/gifticionfunding/service/FundingArticleServiceTest.java b/src/test/java/team/haedal/gifticionfunding/service/FundingArticleServiceTest.java new file mode 100644 index 0000000..fa20c32 --- /dev/null +++ b/src/test/java/team/haedal/gifticionfunding/service/FundingArticleServiceTest.java @@ -0,0 +1,173 @@ +package team.haedal.gifticionfunding.service; + + +import static java.time.LocalDate.now; +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertThrows; + +import java.util.List; +import org.junit.jupiter.api.AfterEach; +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 team.haedal.gifticionfunding.dto.FundingArticleDetailDto; +import team.haedal.gifticionfunding.dto.FundingArticleDto; +import team.haedal.gifticionfunding.dto.common.PagingResponse; +import team.haedal.gifticionfunding.dummy.DummyFactory; +import team.haedal.gifticionfunding.entity.funding.FundingArticle; +import team.haedal.gifticionfunding.entity.user.Friendship; +import team.haedal.gifticionfunding.entity.user.User; +import team.haedal.gifticionfunding.repository.funding.FundingArticleRepository; +import team.haedal.gifticionfunding.repository.user.FriendshipRepository; +import team.haedal.gifticionfunding.repository.user.UserRepository; + +@SpringBootTest +class FundingArticleServiceTest extends DummyFactory { + @Autowired private FundingArticleService fundingArticleService; + @Autowired private UserRepository userRepository; + @Autowired private FriendshipRepository friendshipRepository; + @Autowired private FundingArticleRepository fundingArticleRepository; + + @AfterEach + void tearDown() { + fundingArticleRepository.deleteAllInBatch(); + friendshipRepository.deleteAllInBatch(); + userRepository.deleteAllInBatch(); + } + + @Test + @DisplayName("존재하지 않는 펀딩 게시글을 조회하면, IllegalArgumentException 예외가 발생한다.") + void throw_when_read_non_existed_article() { + //given + Long nonExistentId = 1L; + + //when & then + assertThrows(IllegalArgumentException.class, () -> fundingArticleService.getFundingArticle(1L, nonExistentId)); + } + + @Test + @DisplayName("친구 depth 2 범위가 아닌 펀딩 게시글을 조회하면, IllegalArgumentException 예외가 발생한다.") + void throw_when_read_out_of_depth2() { + //given + //유저 생성 + User user = newUser("jae@naver.com", "jaehyeon1114", "1234"); + User depth1Friend = newUser("jae2@naver.com", "jaehyeon1115", "1234"); + User depth2Friend = newUser("jae3@naver.com", "jaehyeon1116", "1234"); + User depth3Friend = newUser("jae4@naver.com", "jaehyeon1117", "1234"); + + userRepository.saveAll(List.of(user, depth1Friend, depth2Friend, depth3Friend)); + + //친구관계 생성 + Friendship friendship1 = Friendship.builder().fromUser(user).toUser(depth1Friend).build(); + Friendship friendship2 = Friendship.builder().fromUser(depth1Friend).toUser(depth2Friend).build(); + Friendship friendship3 = Friendship.builder().fromUser(depth2Friend).toUser(depth3Friend).build(); + + friendshipRepository.saveAll(List.of(friendship1, friendship2, friendship3)); + + // depth3 친구 게시글 생성 + FundingArticle fundingArticle = FundingArticle.builder() + .author(depth3Friend) + .title("title") + .content("content") + .endAt(now().plusDays(1).atStartOfDay()) + .build(); + + fundingArticleRepository.save(fundingArticle); + + //when & then + assertThrows(IllegalArgumentException.class, () -> fundingArticleService.getFundingArticle(user.getId(), fundingArticle.getId())); + } + + @Test + @DisplayName("친구 depth 2 범위의 펀딩 게시글을 정상적으로 조회한다.") + void success_when_read_article_in_depth2() { + //given + //유저 생성 + User user = newUser("jae@naver.com", "jaehyeon1114", "1234"); + User depth1Friend = newUser("jae2@naver.com", "jaehyeon1115", "1234"); + User depth2Friend = newUser("jae3@naver.com", "jaehyeon1116", "1234"); + + userRepository.saveAll(List.of(user, depth1Friend, depth2Friend)); + + //친구관계 생성 + Friendship friendship1 = Friendship.builder().fromUser(user).toUser(depth1Friend).build(); + Friendship friendship2 = Friendship.builder().fromUser(depth1Friend).toUser(depth2Friend).build(); + + friendshipRepository.saveAll(List.of(friendship1, friendship2)); + + // depth3 친구 게시글 생성 + FundingArticle fundingArticle = FundingArticle.builder() + .author(depth2Friend) + .title("title") + .content("content") + .endAt(now().plusDays(1).atStartOfDay()) + .build(); + + fundingArticleRepository.save(fundingArticle); + + //when + FundingArticleDetailDto fundingArticleDetailDto = fundingArticleService.getFundingArticle(user.getId(), fundingArticle.getId()); + + //then + assertEquals(fundingArticle.getAuthor().getNickname(), fundingArticleDetailDto.author()); + } + @Test + @DisplayName("친구 depth 2 범위의 펀딩 게시글 목록을 정상적으로 조회한다.") + void success_when_read_articles_in_depth2() { + //유저 생성 + User user = newUser("jae@naver.com", "jaehyeon1114", "1234"); + User depth1Friend = newUser("jae2@naver.com", "jaehyeon1115", "1234"); + User depth2Friend = newUser("jae3@naver.com", "jaehyeon1116", "1234"); + User depth3Friend = newUser("jae4@naver.com", "jaehyeon1117", "1234"); + + userRepository.saveAll(List.of(user, depth1Friend, depth2Friend, depth3Friend)); + + //친구관계 생성 + Friendship friendship1 = Friendship.builder().fromUser(user).toUser(depth1Friend).build(); + Friendship friendship2 = Friendship.builder().fromUser(depth1Friend).toUser(depth2Friend).build(); + Friendship friendship3 = Friendship.builder().fromUser(depth2Friend).toUser(depth3Friend).build(); + + friendshipRepository.saveAll(List.of(friendship1, friendship2, friendship3)); + + // depth1 친구 게시글 생성 + FundingArticle fundingArticle1 = FundingArticle.builder() + .author(depth1Friend) + .title("title") + .content("content") + .endAt(now().plusDays(1).atStartOfDay()) + .build(); + + // depth2 친구 게시글 생성 + FundingArticle fundingArticle2 = FundingArticle.builder() + .author(depth2Friend) + .title("title") + .content("content") + .endAt(now().plusDays(1).atStartOfDay()) + .build(); + + // depth3 친구 게시글 생성 + FundingArticle fundingArticle3 = FundingArticle.builder() + .author(depth3Friend) + .title("title") + .content("content") + .endAt(now().plusDays(1).atStartOfDay()) + .build(); + + fundingArticleRepository.saveAll(List.of(fundingArticle1, fundingArticle2, fundingArticle3)); + + //when + PagingResponse fundingArticleDtos = fundingArticleService.getFundingArticles(user.getId(), 0, 10); + + //then + assertEquals(2, fundingArticleDtos.getData().size()); + } + + @Test + void updateFundingArticleExpiration() { + } + + @Test + void receiveFunding() { + } +} \ No newline at end of file