diff --git a/README.md b/README.md
index dcf7f1c29..31243f217 100644
--- a/README.md
+++ b/README.md
@@ -32,9 +32,13 @@ https://www.smody.co.kr
+- [Figma 링크](https://www.figma.com/file/HQinpzR8FuXUFxTZzzzCpf/Smody-Design-System)
+- [Storybook 링크](https://woowacourse-teams.github.io/2022-smody)
+
### Backend
-
+
+
### Infra
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/SmodyApplication.java b/backend/smody/src/main/java/com/woowacourse/smody/SmodyApplication.java
index f2034d5a6..2fd01929a 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/SmodyApplication.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/SmodyApplication.java
@@ -1,14 +1,11 @@
package com.woowacourse.smody;
import java.util.TimeZone;
-
import javax.annotation.PostConstruct;
-
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
-
@SpringBootApplication
@EnableJpaAuditing
public class SmodyApplication {
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/auth/dto/TokenPayload.java b/backend/smody/src/main/java/com/woowacourse/smody/auth/dto/TokenPayload.java
index 83936c0e1..b4f258f27 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/auth/dto/TokenPayload.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/auth/dto/TokenPayload.java
@@ -12,6 +12,8 @@
@EqualsAndHashCode
public class TokenPayload {
+ public static final TokenPayload NOT_LOGIN_TOKEN_PAYLOAD = new TokenPayload(0L);
+
private Long id;
public Map toMap() {
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/challenge/controller/ChallengeController.java b/backend/smody/src/main/java/com/woowacourse/smody/challenge/controller/ChallengeController.java
index a33331c5a..18ccf95ef 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/challenge/controller/ChallengeController.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/challenge/controller/ChallengeController.java
@@ -43,10 +43,11 @@ public ResponseEntity> findAllWithChallengerCountByFi
@RequiredLogin
public ResponseEntity> findAllWithChallengerCountByFilter(
@LoginMember TokenPayload tokenPayload,
- @ModelAttribute PagingParams pagingParams) {
+ @ModelAttribute PagingParams pagingParams
+ ) {
return ResponseEntity.ok(challengeApiService.findAllWithChallengerCountByFilter(
- tokenPayload, LocalDateTime.now(), pagingParams)
- );
+ tokenPayload, LocalDateTime.now(), pagingParams
+ ));
}
@GetMapping(value = "/me")
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/challenge/domain/ChallengingRecord.java b/backend/smody/src/main/java/com/woowacourse/smody/challenge/domain/ChallengingRecord.java
index 43970d1eb..abc55cd0c 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/challenge/domain/ChallengingRecord.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/challenge/domain/ChallengingRecord.java
@@ -28,7 +28,7 @@ public ChallengingRecord(List cycles) {
.count();
}
- /*
+ /**
* QueryDSL Projection 을 위한 생성자
*/
public ChallengingRecord(Cycle cycle, Long successCount) {
@@ -104,7 +104,7 @@ public Cycle getLatestCycle() {
public Integer getCycleDetailCount() {
return cycles.stream()
- .mapToInt(cycle -> cycle.getCycleDetailsOrderByProgress().size())
+ .mapToInt(Cycle::getCycleDetailCount)
.sum();
}
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/challenge/repository/ChallengeRepository.java b/backend/smody/src/main/java/com/woowacourse/smody/challenge/repository/ChallengeRepository.java
index a948d5da2..c0a906a15 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/challenge/repository/ChallengeRepository.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/challenge/repository/ChallengeRepository.java
@@ -1,10 +1,17 @@
package com.woowacourse.smody.challenge.repository;
import com.woowacourse.smody.challenge.domain.Challenge;
+import java.util.List;
import java.util.Optional;
import org.springframework.data.jpa.repository.JpaRepository;
+import org.springframework.data.jpa.repository.Query;
public interface ChallengeRepository extends JpaRepository, DynamicChallengeRepository {
Optional findByName(String name);
+
+ @Query("select c.id from Challenge c")
+ List findAllIds();
+
+ List findAllByIdIn(List challengeIds);
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/challenge/service/ChallengeApiService.java b/backend/smody/src/main/java/com/woowacourse/smody/challenge/service/ChallengeApiService.java
index 841424fe2..ea9dbee2b 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/challenge/service/ChallengeApiService.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/challenge/service/ChallengeApiService.java
@@ -19,7 +19,6 @@
import com.woowacourse.smody.member.domain.Member;
import com.woowacourse.smody.member.service.MemberService;
import java.time.LocalDateTime;
-import java.util.Comparator;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
@@ -36,7 +35,7 @@ public class ChallengeApiService {
public List findAllWithChallengerCountByFilter(LocalDateTime searchTime,
PagingParams pagingParams) {
- return findAllWithChallengerCountByFilter(new TokenPayload(0L), searchTime, pagingParams);
+ return findAllWithChallengerCountByFilter(TokenPayload.NOT_LOGIN_TOKEN_PAYLOAD, searchTime, pagingParams);
}
public List findAllWithChallengerCountByFilter(TokenPayload tokenPayload,
@@ -46,6 +45,9 @@ public List findAllWithChallengerCountByFilter(TokenPayloa
if (pagingParams.getSort().equals("popular")) {
return getChallengeTabResponsesWhenPopular(searchTime, pagingParams, member);
}
+ if (pagingParams.getSort().equals("random")) {
+ return getChallengeTabResponsesWhenRandom(searchTime, pagingParams, member);
+ }
List challenges = challengeService.findAllByFilter(pagingParams);
List cycles = cycleService.findInProgressByChallenges(searchTime, challenges);
ChallengingRecords challengingRecords = ChallengingRecords.from(cycles);
@@ -62,6 +64,15 @@ private List getChallengeTabResponsesWhenPopular(LocalDate
return getChallengeTabResponses(pagedChallenges, member, challengingRecords);
}
+ private List getChallengeTabResponsesWhenRandom(final LocalDateTime searchTime,
+ final PagingParams pagingParams, final Member member) {
+ List randomChallenges = challengeService.findRandomChallenges(pagingParams.getSize());
+ List cycles = cycleService.findInProgress(searchTime);
+ ChallengingRecords challengingRecords = ChallengingRecords.from(cycles);
+ List pagedChallenges = CursorPaging.apply(randomChallenges, null, pagingParams.getSize());
+ return getChallengeTabResponses(pagedChallenges, member, challengingRecords);
+ }
+
private List getChallengeTabResponses(List challenges,
Member member,
ChallengingRecords challengingRecords) {
@@ -74,7 +85,7 @@ private List getChallengeTabResponses(List chal
}
public ChallengeResponse findWithChallengerCount(LocalDateTime searchTime, Long challengeId) {
- return findWithChallengerCount(new TokenPayload(0L), searchTime, challengeId);
+ return findWithChallengerCount(TokenPayload.NOT_LOGIN_TOKEN_PAYLOAD, searchTime, challengeId);
}
public ChallengeResponse findWithChallengerCount(
@@ -99,7 +110,7 @@ public List findAllByMeAndFilter(TokenPayload tokenPayl
);
List sortedRecords = challengingRecords.sortByLatestProgressTime();
- ChallengingRecord cursorChallengingRecord = getCursorMemberChallenge(pagingParams, sortedRecords);
+ ChallengingRecord cursorChallengingRecord = getCursorChallengeRecord(pagingParams, sortedRecords);
List pagedMyChallengeHistories = CursorPaging.apply(
sortedRecords, cursorChallengingRecord, pagingParams.getSize()
);
@@ -109,7 +120,7 @@ public List findAllByMeAndFilter(TokenPayload tokenPayl
.collect(toList());
}
- private ChallengingRecord getCursorMemberChallenge(PagingParams pagingParams,
+ private ChallengingRecord getCursorChallengeRecord(PagingParams pagingParams,
List myChallengeHistories) {
return challengeService.findById(pagingParams.getCursorId())
.map(cursor -> extractMatchChallenge(myChallengeHistories, cursor))
@@ -134,7 +145,7 @@ public List findAllChallengers(Long challengeId) {
}
public ChallengeHistoryResponse findByMeAndChallenge(TokenPayload tokenPayload, Long challengeId) {
- List cycles = cycleService.findAllByChallengeIdAndMemberId(challengeId, tokenPayload.getId());
+ List cycles = cycleService.findAllByChallengeAndMember(challengeId, tokenPayload.getId());
ChallengingRecord challengingRecord = new ChallengingRecord(cycles);
return new ChallengeHistoryResponse(
challengingRecord.getChallenge(),
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/challenge/service/ChallengeService.java b/backend/smody/src/main/java/com/woowacourse/smody/challenge/service/ChallengeService.java
index cee8e8be1..ba26da989 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/challenge/service/ChallengeService.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/challenge/service/ChallengeService.java
@@ -2,11 +2,10 @@
import com.woowacourse.smody.challenge.domain.Challenge;
import com.woowacourse.smody.challenge.repository.ChallengeRepository;
-import com.woowacourse.smody.cycle.domain.Cycle;
import com.woowacourse.smody.db_support.PagingParams;
import com.woowacourse.smody.exception.BusinessException;
import com.woowacourse.smody.exception.ExceptionData;
-import java.time.LocalDateTime;
+import java.util.Collections;
import java.util.List;
import java.util.Optional;
import lombok.RequiredArgsConstructor;
@@ -55,4 +54,11 @@ private void validateDuplicatedName(String name) {
throw new BusinessException(ExceptionData.DUPLICATE_NAME);
}
}
+
+ public List findRandomChallenges(Integer size) {
+ List allIds = challengeRepository.findAllIds();
+ Collections.shuffle(allIds);
+ List randomIds = allIds.subList(0, Math.min(size, allIds.size()));
+ return challengeRepository.findAllByIdIn(randomIds);
+ }
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/comment/controller/CommentController.java b/backend/smody/src/main/java/com/woowacourse/smody/comment/controller/CommentController.java
index 40406997b..ea717f088 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/comment/controller/CommentController.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/comment/controller/CommentController.java
@@ -7,7 +7,6 @@
import com.woowacourse.smody.comment.dto.CommentResponse;
import com.woowacourse.smody.comment.dto.CommentUpdateRequest;
import com.woowacourse.smody.comment.service.CommentApiService;
-import com.woowacourse.smody.comment.service.CommentService;
import java.net.URI;
import java.util.List;
import lombok.RequiredArgsConstructor;
@@ -28,7 +27,8 @@ public class CommentController {
@PostMapping("/feeds/{cycleDetailId}/comments")
@RequiredLogin
- public ResponseEntity create(@LoginMember TokenPayload tokenPayload, @PathVariable Long cycleDetailId,
+ public ResponseEntity create(@LoginMember TokenPayload tokenPayload,
+ @PathVariable Long cycleDetailId,
@RequestBody CommentRequest commentRequest) {
Long commentId = commentApiService.create(tokenPayload, cycleDetailId, commentRequest);
return ResponseEntity.created(URI.create("/comments/" + commentId)).build();
@@ -52,7 +52,9 @@ public ResponseEntity delete(@LoginMember TokenPayload tokenPayload, @Path
@GetMapping("/feeds/{cycleDetailId}/comments")
public ResponseEntity> findAllByCycleDetailId(@PathVariable Long cycleDetailId) {
- return ResponseEntity.ok(commentApiService.findAllByCycleDetailId(new TokenPayload(0L), cycleDetailId));
+ return ResponseEntity.ok(
+ commentApiService.findAllByCycleDetailId(TokenPayload.NOT_LOGIN_TOKEN_PAYLOAD, cycleDetailId)
+ );
}
@GetMapping("/feeds/{cycleDetailId}/comments/auth")
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/comment/domain/Comment.java b/backend/smody/src/main/java/com/woowacourse/smody/comment/domain/Comment.java
index 3b1281b41..be564901e 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/comment/domain/Comment.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/comment/domain/Comment.java
@@ -54,7 +54,7 @@ private void validateContent(String content) {
}
}
- public boolean isCommentByMemberId(Long memberId) {
+ public boolean isWriter(Long memberId) {
return this.member.getId().equals(memberId);
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/comment/repository/CommentRepository.java b/backend/smody/src/main/java/com/woowacourse/smody/comment/repository/CommentRepository.java
index 0f61adc95..0d6309ab3 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/comment/repository/CommentRepository.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/comment/repository/CommentRepository.java
@@ -2,11 +2,14 @@
import com.woowacourse.smody.comment.domain.Comment;
import java.util.List;
-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;
public interface CommentRepository extends JpaRepository {
- @EntityGraph(attributePaths = {"member", "cycleDetail"})
- List findAllByCycleDetailId(Long cycleDetailId);
+ @Query("select cm from Comment cm "
+ + "join fetch cm.member join fetch cm.cycleDetail "
+ + "where cm.cycleDetail.id = :cycleDetailId")
+ List findAllByCycleDetailId(@Param("cycleDetailId") Long cycleDetailId);
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/comment/service/CommentApiService.java b/backend/smody/src/main/java/com/woowacourse/smody/comment/service/CommentApiService.java
index 2971a7542..60e967aa9 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/comment/service/CommentApiService.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/comment/service/CommentApiService.java
@@ -5,7 +5,6 @@
import com.woowacourse.smody.comment.dto.CommentRequest;
import com.woowacourse.smody.comment.dto.CommentResponse;
import com.woowacourse.smody.comment.dto.CommentUpdateRequest;
-import com.woowacourse.smody.comment.repository.CommentRepository;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
@@ -19,13 +18,6 @@ public class CommentApiService {
private final CommentService commentService;
- public List findAllByCycleDetailId(TokenPayload tokenPayload, Long cycleDetailId) {
- List comments = commentService.findAllByCycleDetailId(cycleDetailId);
- return comments.stream()
- .map(comment -> new CommentResponse(comment, comment.isCommentByMemberId(tokenPayload.getId())))
- .collect(Collectors.toList());
- }
-
@Transactional
public Long create(TokenPayload tokenPayload, Long cycleDetailId, CommentRequest commentRequest) {
Comment comment = commentService.create(
@@ -35,9 +27,7 @@ public Long create(TokenPayload tokenPayload, Long cycleDetailId, CommentRequest
}
@Transactional
- public void update(
- TokenPayload tokenPayload, Long commentId, CommentUpdateRequest commentUpdateRequest
- ) {
+ public void update(TokenPayload tokenPayload, Long commentId, CommentUpdateRequest commentUpdateRequest) {
commentService.update(tokenPayload.getId(), commentId, commentUpdateRequest.getContent());
}
@@ -45,4 +35,11 @@ public void update(
public void delete(TokenPayload tokenPayload, Long commentId) {
commentService.delete(tokenPayload.getId(), commentId);
}
+
+ public List findAllByCycleDetailId(TokenPayload tokenPayload, Long cycleDetailId) {
+ List comments = commentService.findAllByCycleDetailId(cycleDetailId);
+ return comments.stream()
+ .map(comment -> new CommentResponse(comment, comment.isWriter(tokenPayload.getId())))
+ .collect(Collectors.toList());
+ }
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/comment/service/CommentService.java b/backend/smody/src/main/java/com/woowacourse/smody/comment/service/CommentService.java
index ea13acfbf..f803eeced 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/comment/service/CommentService.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/comment/service/CommentService.java
@@ -1,15 +1,12 @@
package com.woowacourse.smody.comment.service;
-import com.woowacourse.smody.auth.dto.TokenPayload;
import com.woowacourse.smody.comment.domain.Comment;
import com.woowacourse.smody.comment.domain.CommentCreateEvent;
-import com.woowacourse.smody.comment.dto.CommentRequest;
-import com.woowacourse.smody.comment.dto.CommentUpdateRequest;
import com.woowacourse.smody.comment.repository.CommentRepository;
import com.woowacourse.smody.cycle.domain.CycleDetail;
+import com.woowacourse.smody.cycle.service.CycleService;
import com.woowacourse.smody.exception.BusinessException;
import com.woowacourse.smody.exception.ExceptionData;
-import com.woowacourse.smody.feed.repository.FeedRepository;
import com.woowacourse.smody.member.domain.Member;
import com.woowacourse.smody.member.service.MemberService;
import java.util.List;
@@ -24,17 +21,15 @@
public class CommentService {
private final MemberService memberService;
+ private final CycleService cycleService;
private final CommentRepository commentRepository;
- private final FeedRepository feedRepository;
private final ApplicationEventPublisher applicationEventPublisher;
@Transactional
public Comment create(Long memberId, Long cycleDetailId, String content) {
Member member = memberService.search(memberId);
- CycleDetail cycleDetail = feedRepository.findById(cycleDetailId)
- .orElseThrow(() -> new BusinessException(ExceptionData.NOT_FOUND_CYCLE_DETAIL));
+ CycleDetail cycleDetail = cycleService.searchCycleDetail(cycleDetailId);
Comment comment = commentRepository.save(new Comment(cycleDetail, member, content));
-
applicationEventPublisher.publishEvent(new CommentCreateEvent(comment));
return comment;
}
@@ -46,6 +41,12 @@ public void update(Long memberId, Long commentId, String content) {
comment.updateContent(content);
}
+ private void validateMember(Long memberId, Comment comment) {
+ if (!comment.isWriter(memberId)) {
+ throw new BusinessException(ExceptionData.UNAUTHORIZED_MEMBER);
+ }
+ }
+
@Transactional
public void delete(Long memberId, Long commentId) {
Comment comment = search(commentId);
@@ -58,12 +59,6 @@ public Comment search(Long commentId) {
.orElseThrow(() -> new BusinessException(ExceptionData.NOT_FOUND_COMMENT));
}
- private void validateMember(Long memberId, Comment comment) {
- if (!comment.isCommentByMemberId(memberId)) {
- throw new BusinessException(ExceptionData.UNAUTHORIZED_MEMBER);
- }
- }
-
public List findAllByCycleDetailId(Long cycleDetailId) {
return commentRepository.findAllByCycleDetailId(cycleDetailId);
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/config/AsyncConfig.java b/backend/smody/src/main/java/com/woowacourse/smody/config/AsyncConfig.java
index 5f4098668..00b566c8e 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/config/AsyncConfig.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/config/AsyncConfig.java
@@ -11,15 +11,15 @@
@EnableAsync
public class AsyncConfig {
- @Value("${async.threads.max}")
- private Integer threadMax;
+ @Value("${async.threads.max}")
+ private Integer threadMax;
- @Bean
- public TaskExecutor asyncExecutor() {
- ThreadPoolTaskExecutor asyncExecutor = new ThreadPoolTaskExecutor();
- asyncExecutor.setThreadNamePrefix("async-pool");
- asyncExecutor.setCorePoolSize(threadMax);
- asyncExecutor.initialize();
- return asyncExecutor;
- }
+ @Bean
+ public TaskExecutor asyncExecutor() {
+ ThreadPoolTaskExecutor asyncExecutor = new ThreadPoolTaskExecutor();
+ asyncExecutor.setThreadNamePrefix("async-pool");
+ asyncExecutor.setCorePoolSize(threadMax);
+ asyncExecutor.initialize();
+ return asyncExecutor;
+ }
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/config/WebConfig.java b/backend/smody/src/main/java/com/woowacourse/smody/config/WebConfig.java
index 02fc66823..f9621d0d6 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/config/WebConfig.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/config/WebConfig.java
@@ -25,7 +25,6 @@ public class WebConfig implements WebMvcConfigurer {
private final AuthInterceptor authInterceptor;
private final LogInterceptor logInterceptor;
private final LoginMemberArgumentResolver loginMemberArgumentResolver;
-
private final TokenChekcerArgumentResolver tokenChekcerArgumentResolver;
@Override
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/config/WebPushSchedulerConfig.java b/backend/smody/src/main/java/com/woowacourse/smody/config/WebPushSchedulerConfig.java
index 0450900a5..95c719540 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/config/WebPushSchedulerConfig.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/config/WebPushSchedulerConfig.java
@@ -1,15 +1,13 @@
package com.woowacourse.smody.config;
+import com.woowacourse.smody.push.WebPushBatch;
+import com.woowacourse.smody.push.WebPushScheduler;
+import lombok.RequiredArgsConstructor;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.scheduling.annotation.EnableScheduling;
-import com.woowacourse.smody.push.WebPushBatch;
-import com.woowacourse.smody.push.WebPushScheduler;
-
-import lombok.RequiredArgsConstructor;
-
@Configuration
@EnableScheduling
@RequiredArgsConstructor
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/cycle/controller/CycleController.java b/backend/smody/src/main/java/com/woowacourse/smody/cycle/controller/CycleController.java
index 44c6acbb9..b78ced43b 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/cycle/controller/CycleController.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/cycle/controller/CycleController.java
@@ -53,8 +53,7 @@ public ResponseEntity increase(@LoginMember TokenPayload token
@RequiredLogin
public ResponseEntity> findAllInProgressByMe(@LoginMember TokenPayload tokenPayload,
@ModelAttribute PagingParams pagingParams) {
- return ResponseEntity.ok(
- cycleApiService.findInProgressByMe(tokenPayload, LocalDateTime.now(), pagingParams));
+ return ResponseEntity.ok(cycleApiService.findInProgressByMe(tokenPayload, LocalDateTime.now(), pagingParams));
}
@GetMapping("/{cycleId}")
@@ -73,9 +72,11 @@ public ResponseEntity searchStat(@LoginMember TokenPayload tokenPa
public ResponseEntity> findAllByMemberAndChallengeWithFilter(
@LoginMember TokenPayload tokenPayload,
@PathVariable Long challengeId,
- @ModelAttribute PagingParams pagingParams) {
+ @ModelAttribute PagingParams pagingParams
+ ) {
return ResponseEntity.ok(
- cycleApiService.findAllByMemberAndChallenge(tokenPayload, challengeId, pagingParams));
+ cycleApiService.findAllByMemberAndChallenge(tokenPayload, challengeId, pagingParams)
+ );
}
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/cycle/domain/Cycle.java b/backend/smody/src/main/java/com/woowacourse/smody/cycle/domain/Cycle.java
index 4031256fd..39d0770be 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/cycle/domain/Cycle.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/cycle/domain/Cycle.java
@@ -35,6 +35,7 @@
public class Cycle {
public static final long DAYS = 3L;
+ private static final int MAX_CYCLE_DETAILS_SIZE = 3;
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@@ -80,9 +81,10 @@ private void validateStartTime(Progress progress, LocalDateTime startTime) {
public void increaseProgress(LocalDateTime progressTime, Image progressImage, String description) {
this.progress = progress.increase(startTime, progressTime);
- if (this.cycleDetails.size() <= 2) {
- this.cycleDetails.add(new CycleDetail(this, progressTime, progressImage.getUrl(), description,
- progress));
+ if (this.cycleDetails.size() < MAX_CYCLE_DETAILS_SIZE) {
+ this.cycleDetails.add(
+ new CycleDetail(this, progressTime, progressImage.getUrl(), description, progress)
+ );
}
}
@@ -117,6 +119,11 @@ public int getInterval() {
return progress.getCount() + 1;
}
+ public CycleDetail getLatestCycleDetail() {
+ int lastIndex = this.cycleDetails.size() - 1;
+ return getCycleDetailsOrderByProgress().get(lastIndex);
+ }
+
public LocalDateTime getLatestProgressTime() {
if (this.cycleDetails.isEmpty()) {
return this.startTime;
@@ -124,8 +131,7 @@ public LocalDateTime getLatestProgressTime() {
return getLatestCycleDetail().getProgressTime();
}
- public CycleDetail getLatestCycleDetail() {
- int lastIndex = this.cycleDetails.size() - 1;
- return getCycleDetailsOrderByProgress().get(lastIndex);
+ public int getCycleDetailCount() {
+ return this.cycleDetails.size();
}
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/cycle/domain/CycleDetail.java b/backend/smody/src/main/java/com/woowacourse/smody/cycle/domain/CycleDetail.java
index 6a9c47a94..0c0e1725b 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/cycle/domain/CycleDetail.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/cycle/domain/CycleDetail.java
@@ -54,8 +54,11 @@ public class CycleDetail {
@Column(nullable = false)
private Progress progress;
- public CycleDetail(Cycle cycle, LocalDateTime progressTime, String progressImage,
- String description, Progress progress) {
+ public CycleDetail(Cycle cycle,
+ LocalDateTime progressTime,
+ String progressImage,
+ String description,
+ Progress progress) {
validateDescription(description);
this.cycle = cycle;
this.progressTime = progressTime;
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/cycle/dto/StatResponse.java b/backend/smody/src/main/java/com/woowacourse/smody/cycle/dto/StatResponse.java
index 6ab592a82..e56f11de8 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/cycle/dto/StatResponse.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/cycle/dto/StatResponse.java
@@ -8,6 +8,7 @@
@AllArgsConstructor
@Getter
public class StatResponse {
+
private Integer totalCount;
private Integer successCount;
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/cycle/repository/CycleDetailRepository.java b/backend/smody/src/main/java/com/woowacourse/smody/cycle/repository/CycleDetailRepository.java
new file mode 100644
index 000000000..b90dfd7f4
--- /dev/null
+++ b/backend/smody/src/main/java/com/woowacourse/smody/cycle/repository/CycleDetailRepository.java
@@ -0,0 +1,7 @@
+package com.woowacourse.smody.cycle.repository;
+
+import com.woowacourse.smody.cycle.domain.CycleDetail;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface CycleDetailRepository extends JpaRepository {
+}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/cycle/repository/CycleRepository.java b/backend/smody/src/main/java/com/woowacourse/smody/cycle/repository/CycleRepository.java
index 56b3c27fa..a2360e3ec 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/cycle/repository/CycleRepository.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/cycle/repository/CycleRepository.java
@@ -32,7 +32,7 @@ public interface CycleRepository extends JpaRepository, DynamicCycl
+ "where c.member_id = :memberId and c.challenge_id = :challengeId " +
"order by c.start_time DESC limit 1",
nativeQuery = true)
- Optional findRecent(@Param("memberId") Member member, @Param("challengeId") Challenge challenge);
+ Optional findRecent(@Param("memberId") Long memberId, @Param("challengeId") Long challengeId);
List findByMember(Member member);
@@ -41,13 +41,12 @@ public interface CycleRepository extends JpaRepository, DynamicCycl
void deleteByMember(@Param("member") Member member);
@Lock(LockModeType.PESSIMISTIC_WRITE)
- @Query("select c from Cycle c where c.id = :id")
- Optional findByIdWithLock(@Param("id") Long id);
+ @Query("select c from Cycle c where c.id = :cycleId")
+ Optional findByIdWithLock(@Param("cycleId") Long cycleId);
@Query("select c from Cycle c "
+ "join fetch c.challenge join fetch c.member "
+ "where c.challenge.id = :challengeId and c.member.id = :memberId")
- List findAllByChallengeIdAndMemberId(@Param("challengeId") Long challengeId,
- @Param("memberId") Long memberId);
-
+ List findAllByChallengeAndMember(@Param("challengeId") Long challengeId,
+ @Param("memberId") Long memberId);
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/cycle/repository/DynamicCycleRepositoryImpl.java b/backend/smody/src/main/java/com/woowacourse/smody/cycle/repository/DynamicCycleRepositoryImpl.java
index 7f5685123..3fc04a37d 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/cycle/repository/DynamicCycleRepositoryImpl.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/cycle/repository/DynamicCycleRepositoryImpl.java
@@ -1,5 +1,6 @@
package com.woowacourse.smody.cycle.repository;
+import static com.querydsl.core.types.ExpressionUtils.as;
import static com.querydsl.core.types.ExpressionUtils.count;
import static com.woowacourse.smody.challenge.domain.QChallenge.challenge;
import static com.woowacourse.smody.cycle.domain.QCycle.cycle;
@@ -7,7 +8,6 @@
import com.querydsl.core.BooleanBuilder;
import com.querydsl.core.types.ConstructorExpression;
import com.querydsl.core.types.Expression;
-import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.impl.JPAQueryFactory;
@@ -27,7 +27,9 @@ public class DynamicCycleRepositoryImpl implements DynamicCycleRepository {
private final JPAQueryFactory queryFactory;
@Override
- public List findAllByMemberAndChallengeAndFilter(Long memberId, Long challengeId, PagingParams pagingParams) {
+ public List findAllByMemberAndChallengeAndFilter(Long memberId,
+ Long challengeId,
+ PagingParams pagingParams) {
return queryFactory
.selectFrom(cycle)
.where(buildDynamicQuery(memberId, challengeId, pagingParams))
@@ -66,8 +68,7 @@ public List findAllByMemberAndFilter(Long memberId, PagingParams pagingPa
.and(() -> cycle.progress.eq(Progress.from(pagingParams.getFilter())
.orElse(null)))
.build()
- )
- .distinct()
+ ).distinct()
.fetch();
}
@@ -81,9 +82,8 @@ public List findAllChallengingRecordByMemberAfterTime(Long me
.where(DynamicQuery.builder()
.and(() -> cycle.member.id.eq(memberId))
.and(() -> cycle.startTime.after(time))
- .build()
- )
- .fetch();
+ .build())
+ .fetch();
}
private ConstructorExpression challengingRecordConstructor() {
@@ -95,13 +95,13 @@ private ConstructorExpression challengingRecordConstructor()
private Expression successCountSubQuery() {
QCycle subCycle = new QCycle("subCycle");
- return ExpressionUtils.as(
+ return as(
JPAExpressions.select(count(subCycle.id))
.from(subCycle)
.where(new BooleanBuilder()
- .and(subCycle.challenge.eq(cycle.challenge))
- .and(subCycle.progress.eq(Progress.SUCCESS))
- .and(subCycle.member.eq(cycle.member))
+ .and(subCycle.challenge.eq(cycle.challenge))
+ .and(subCycle.progress.eq(Progress.SUCCESS))
+ .and(subCycle.member.eq(cycle.member))
),
"successCount");
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/cycle/service/CycleApiService.java b/backend/smody/src/main/java/com/woowacourse/smody/cycle/service/CycleApiService.java
index 8958ff43e..db0f1f51b 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/cycle/service/CycleApiService.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/cycle/service/CycleApiService.java
@@ -16,7 +16,6 @@
import com.woowacourse.smody.cycle.dto.ProgressRequest;
import com.woowacourse.smody.cycle.dto.ProgressResponse;
import com.woowacourse.smody.cycle.dto.StatResponse;
-import com.woowacourse.smody.cycle.repository.CycleRepository;
import com.woowacourse.smody.db_support.CursorPaging;
import com.woowacourse.smody.db_support.PagingParams;
import com.woowacourse.smody.exception.BusinessException;
@@ -69,19 +68,16 @@ private void validateAuthorizedMember(TokenPayload tokenPayload, Cycle cycle) {
}
}
- public CycleResponse findWithSuccessCountById(Long cycleId) {
- Cycle cycle = cycleService.search(cycleId);
- return new CycleResponse(cycle, cycleService.countSuccess(cycle));
- }
-
public List findInProgressByMe(TokenPayload tokenPayload,
LocalDateTime searchTime,
PagingParams pagingParams) {
Member member = memberService.search(tokenPayload.getId());
Integer size = pagingParams.getSize();
- List challengingRecords = cursorPaging(pagingParams, size,
- sortByLatest(cycleService.findAllChallengingRecordByMember(member), searchTime));
-
+ List challengingRecords = cursorPaging(
+ pagingParams,
+ size,
+ sortByDeadLineToMillis(cycleService.findAllChallengingRecordByMember(member), searchTime)
+ );
return challengingRecords.stream()
.map(challengingRecord -> new InProgressCycleResponse(
challengingRecord.getLatestCycle(), challengingRecord.getSuccessCount()
@@ -89,15 +85,16 @@ public List findInProgressByMe(TokenPayload tokenPayloa
.collect(toList());
}
- private List sortByLatest(List challengingRecords,
- LocalDateTime searchTime) {
+ private List sortByDeadLineToMillis(List challengingRecords,
+ LocalDateTime searchTime) {
return challengingRecords.stream()
.filter(challengingRecord -> challengingRecord.isInProgress(searchTime))
.sorted(Comparator.comparing(challengingRecord -> challengingRecord.getDeadLineToMillis(searchTime)))
.collect(toList());
}
- private List cursorPaging(PagingParams pagingParams, Integer size,
+ private List cursorPaging(PagingParams pagingParams,
+ Integer size,
List challengingRecords) {
Cycle cycle = cycleService.findById(pagingParams.getCursorId())
.orElse(null);
@@ -108,9 +105,14 @@ private List cursorPaging(PagingParams pagingParams, Integer
.orElse(CursorPaging.apply(challengingRecords, null, size));
}
+ public CycleResponse findWithSuccessCountById(Long cycleId) {
+ Cycle cycle = cycleService.searchCycle(cycleId);
+ return new CycleResponse(cycle, cycleService.countSuccess(cycle.getMember(), cycle.getChallenge()));
+ }
+
public StatResponse searchStat(TokenPayload tokenPayload) {
Member member = memberService.search(tokenPayload.getId());
- List cycles = cycleService.searchByMember(member);
+ List cycles = cycleService.findByMember(member);
int successCount = (int) cycles.stream()
.filter(Cycle::isSuccess)
.count();
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/cycle/service/CycleService.java b/backend/smody/src/main/java/com/woowacourse/smody/cycle/service/CycleService.java
index 800c0f45d..a0fab5ee6 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/cycle/service/CycleService.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/cycle/service/CycleService.java
@@ -6,7 +6,9 @@
import com.woowacourse.smody.challenge.domain.ChallengingRecord;
import com.woowacourse.smody.challenge.service.ChallengeService;
import com.woowacourse.smody.cycle.domain.Cycle;
+import com.woowacourse.smody.cycle.domain.CycleDetail;
import com.woowacourse.smody.cycle.domain.Progress;
+import com.woowacourse.smody.cycle.repository.CycleDetailRepository;
import com.woowacourse.smody.cycle.repository.CycleRepository;
import com.woowacourse.smody.db_support.PagingParams;
import com.woowacourse.smody.exception.BusinessException;
@@ -26,6 +28,7 @@
public class CycleService {
private final CycleRepository cycleRepository;
+ private final CycleDetailRepository cycleDetailRepository;
private final MemberService memberService;
private final ChallengeService challengeService;
@@ -33,8 +36,7 @@ public class CycleService {
public Cycle create(Long memberId, Long challengeId, LocalDateTime startTime) {
Member member = memberService.search(memberId);
Challenge challenge = challengeService.search(challengeId);
- Optional optionalCycle = cycleRepository.findRecent(member, challenge);
-
+ Optional optionalCycle = cycleRepository.findRecent(member.getId(), challenge.getId());
if (optionalCycle.isPresent()) {
startTime = calculateNewStartTime(startTime, optionalCycle.get());
}
@@ -45,22 +47,31 @@ private LocalDateTime calculateNewStartTime(LocalDateTime startTime, Cycle cycle
if (cycle.isInProgress(startTime)) {
throw new BusinessException(ExceptionData.DUPLICATE_IN_PROGRESS_CHALLENGE);
}
- if (cycle.isSuccess() && cycle.isInDays(startTime)) {
+ if (isRetry(startTime, cycle)) {
return cycle.getStartTime().plusDays(Cycle.DAYS);
}
return startTime;
}
- public int countSuccess(Cycle cycle) {
- return cycleRepository.countSuccess(cycle.getMember(), cycle.getChallenge())
+ private boolean isRetry(final LocalDateTime startTime, final Cycle cycle) {
+ return cycle.isSuccess() && cycle.isInDays(startTime);
+ }
+
+ public int countSuccess(Member member, Challenge challenge) {
+ return cycleRepository.countSuccess(member, challenge)
.intValue();
}
- public Cycle search(Long cycleId) {
+ public Cycle searchCycle(Long cycleId) {
return cycleRepository.findById(cycleId)
.orElseThrow(() -> new BusinessException(ExceptionData.NOT_FOUND_CYCLE));
}
+ public CycleDetail searchCycleDetail(Long cycleDetailId) {
+ return cycleDetailRepository.findById(cycleDetailId)
+ .orElseThrow(() -> new BusinessException(ExceptionData.NOT_FOUND_CYCLE_DETAIL));
+ }
+
public Cycle searchWithLock(Long cycleId) {
return cycleRepository.findByIdWithLock(cycleId)
.orElseThrow(() -> new BusinessException(ExceptionData.NOT_FOUND_CYCLE));
@@ -70,12 +81,12 @@ public Optional findById(Long id) {
return cycleRepository.findById(id);
}
- public List searchByMember(Member member) {
+ public List findByMember(Member member) {
return cycleRepository.findByMember(member);
}
- public List findAllByChallengeIdAndMemberId(Long challengeId, Long memberId) {
- return cycleRepository.findAllByChallengeIdAndMemberId(challengeId, memberId);
+ public List findAllByChallengeAndMember(Long challengeId, Long memberId) {
+ return cycleRepository.findAllByChallengeAndMember(challengeId, memberId);
}
public List findAllByMemberAndFilter(Member member, PagingParams pagingParams) {
@@ -96,7 +107,8 @@ public List findInProgressByChallenge(LocalDateTime searchTime, Challenge
.collect(toList());
}
- public List findAllByMemberAndChallengeAndFilter(Long memberId, Long challengeId,
+ public List findAllByMemberAndChallengeAndFilter(Long memberId,
+ Long challengeId,
PagingParams pagingParams) {
return cycleRepository.findAllByMemberAndChallengeAndFilter(memberId, challengeId, pagingParams);
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/db_support/DynamicQuery.java b/backend/smody/src/main/java/com/woowacourse/smody/db_support/DynamicQuery.java
index b9b1ab246..6b89d12c5 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/db_support/DynamicQuery.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/db_support/DynamicQuery.java
@@ -20,9 +20,9 @@ public DynamicQuery and(Supplier expressionSupplier) {
try {
booleanBuilder.and(expressionSupplier.get());
} catch (IllegalArgumentException | NullPointerException ignored) {
- /*
- * 위의 예외가 터지면 null이 들어왔다는 뜻이다.
- * null이면 where 쿼리에 and 조건을 넣지 않기 위해 예외를 무시했다.
+ /**
+ * 위의 예외가 터지면 null 이 들어왔다는 뜻이다.
+ * null 이면 where 쿼리에 and 조건을 넣지 않기 위해 예외를 무시했다.
*/
}
return this;
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/db_support/PagingParams.java b/backend/smody/src/main/java/com/woowacourse/smody/db_support/PagingParams.java
index cf906596b..132258da1 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/db_support/PagingParams.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/db_support/PagingParams.java
@@ -22,12 +22,12 @@ public PagingParams(String sort) {
this.sort = sort;
}
- public PagingParams(String sort, int size) {
+ public PagingParams(String sort, Integer size) {
this.sort = sort;
this.size = size;
}
- public PagingParams(String sort, int size, Long cursorId) {
+ public PagingParams(String sort, Integer size, Long cursorId) {
this.sort = sort;
this.size = size;
this.cursorId = cursorId;
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/db_support/SortSelection.java b/backend/smody/src/main/java/com/woowacourse/smody/db_support/SortSelection.java
index 84cb59fab..0f858be2a 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/db_support/SortSelection.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/db_support/SortSelection.java
@@ -6,14 +6,11 @@
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.dsl.NumberExpression;
-import com.woowacourse.smody.cycle.domain.QCycle;
import com.woowacourse.smody.exception.BusinessException;
import com.woowacourse.smody.exception.ExceptionData;
-import com.woowacourse.smody.ranking.domain.QRankingPeriod;
import java.util.Arrays;
import org.springframework.data.domain.Sort;
-// TODO: 파라미터 네임 컨벤션 통일화
public enum SortSelection {
DEFAULT("") {
@@ -85,7 +82,6 @@ public OrderSpecifier>[] getOrderSpecifiers() {
};
}
}
-
;
private final String parameter;
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/exception/ControllerAdvice.java b/backend/smody/src/main/java/com/woowacourse/smody/exception/ControllerAdvice.java
index 8fd79b3a3..f8cc6d64f 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/exception/ControllerAdvice.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/exception/ControllerAdvice.java
@@ -1,17 +1,16 @@
package com.woowacourse.smody.exception;
+import com.woowacourse.smody.exception.api.GithubApi;
+import com.woowacourse.smody.exception.dto.ExceptionResponse;
import java.util.Arrays;
-
+import lombok.RequiredArgsConstructor;
+import lombok.extern.slf4j.Slf4j;
import org.springframework.core.env.Environment;
+import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
-import com.woowacourse.smody.exception.dto.ExceptionResponse;
-
-import lombok.RequiredArgsConstructor;
-import lombok.extern.slf4j.Slf4j;
-
@RestControllerAdvice
@RequiredArgsConstructor
@Slf4j
@@ -25,22 +24,27 @@ public class ControllerAdvice {
@ExceptionHandler
public ResponseEntity handleBusinessException(BusinessException businessException) {
ExceptionData exceptionData = businessException.getExceptionData();
- ExceptionResponse exceptionResponse = new ExceptionResponse(exceptionData);
log.info("[비즈니스 예외 발생] 에러 코드 : {}, 메세지 : {}", exceptionData.getCode(), exceptionData.getMessage());
+ if (exceptionData.getStatusCode() == HttpStatus.INTERNAL_SERVER_ERROR.value()
+ && isProdProfile()) {
+ githubApi.create(businessException);
+ }
return ResponseEntity.status(exceptionData.getStatusCode())
- .body(exceptionResponse);
+ .body(new ExceptionResponse(exceptionData));
}
@ExceptionHandler
- public void handleUnExpectedException(Exception exception) {
+ public ResponseEntity handleUnExpectedException(Exception exception) {
log.error("[예상치 못한 예외 발생] ", exception);
- if (isProd(environment.getActiveProfiles())) {
+ if (isProdProfile()) {
githubApi.create(exception);
}
+ return ResponseEntity.internalServerError()
+ .body(ExceptionUtils.extractStackTrace(exception));
}
- private boolean isProd(String[] activeProfiles) {
- return Arrays.stream(activeProfiles)
- .anyMatch(env -> env.equalsIgnoreCase(ISSUE_CREATE_PROFILE));
+ private boolean isProdProfile() {
+ return Arrays.stream(environment.getActiveProfiles())
+ .anyMatch(env -> env.equalsIgnoreCase(ISSUE_CREATE_PROFILE));
}
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/exception/ExceptionData.java b/backend/smody/src/main/java/com/woowacourse/smody/exception/ExceptionData.java
index f6a533192..05cebef40 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/exception/ExceptionData.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/exception/ExceptionData.java
@@ -28,6 +28,8 @@ public enum ExceptionData {
NOT_FOUND_COMMENT(4005, "존재하지 않는 댓글입니다.", 404),
NOT_FOUND_RANKING_PERIOD(4006, "존재하지 않는 랭킹 기간입니다.", 404),
NOT_FOUND_RANKING_ACTIVITY(4007, "존재하지 않는 랭킹 활동입니다.", 404),
+ NOT_FOUND_PUSH_NOTIFICATION(4008, "알림을 찾을 수 없습니다.", 404),
+ NOT_FOUND_SORT(4009, "정렬 기준을 찾을 수 없습니다.", 404),
EMPTY_IMAGE(5001, "이미지의 바이트코드가 비었습니다.", 400),
@@ -37,14 +39,11 @@ public enum ExceptionData {
DUPLICATE_NAME(7002, "중복된 챌린지 이름입니다.", 400),
INVALID_CHALLENGE_NAME(7003, "챌린지 이름 형식이 올바르지 않습니다.", 400),
INVALID_SEARCH_NAME(7004, "검색 이름 형식이 올바르지 않습니다.", 400),
- ANY_PROGRESS_RECORD_CHALLENGE(7005, "인증 기록이 없는 챌린지입니다.", 400),
WEB_PUSH_ERROR(8001, "웹 푸시 라이브러리 관련 예외입니다.", 400),
AUTHORIZATION_SERVER_ERROR(9001, "인가 관련 서버 내부의 오류입니다.", 500),
- IMAGE_UPLOAD_ERROR(9002, "이미지 업로드 관련 서버 내부의 오류입니다.", 500),
DATA_INTEGRITY_ERROR(9003, "데이터 정합성 관련 서버 내부의 오류입니다.", 500),
- NOT_FOUND_SORT(9004, "정렬 기준을 찾을 수 없습니다.", 500)
;
private final int code;
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/exception/ExceptionUtils.java b/backend/smody/src/main/java/com/woowacourse/smody/exception/ExceptionUtils.java
new file mode 100644
index 000000000..1bf2724ce
--- /dev/null
+++ b/backend/smody/src/main/java/com/woowacourse/smody/exception/ExceptionUtils.java
@@ -0,0 +1,13 @@
+package com.woowacourse.smody.exception;
+
+import java.io.PrintWriter;
+import java.io.StringWriter;
+
+public class ExceptionUtils {
+
+ public static String extractStackTrace(Exception exception) {
+ StringWriter stackTrace = new StringWriter();
+ exception.printStackTrace(new PrintWriter(stackTrace));
+ return String.valueOf(stackTrace);
+ }
+}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/exception/GithubApi.java b/backend/smody/src/main/java/com/woowacourse/smody/exception/api/GithubApi.java
similarity index 80%
rename from backend/smody/src/main/java/com/woowacourse/smody/exception/GithubApi.java
rename to backend/smody/src/main/java/com/woowacourse/smody/exception/api/GithubApi.java
index fa3f8c63d..6b53fe889 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/exception/GithubApi.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/exception/api/GithubApi.java
@@ -1,10 +1,11 @@
-package com.woowacourse.smody.exception;
+package com.woowacourse.smody.exception.api;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
+import com.woowacourse.smody.exception.BusinessException;
+import com.woowacourse.smody.exception.ExceptionData;
+import com.woowacourse.smody.exception.ExceptionUtils;
import com.woowacourse.smody.exception.dto.IssueCreateRequest;
-import java.io.PrintWriter;
-import java.io.StringWriter;
import java.nio.charset.StandardCharsets;
import java.util.List;
import lombok.RequiredArgsConstructor;
@@ -20,7 +21,7 @@
@RequiredArgsConstructor
public class GithubApi {
- private static final String REPO_URL = "https://api.github.com/repos/woowacourse-teams/2022-smody/issues";
+ private static final String REPOSITORY_URL = "https://api.github.com/repos/woowacourse-teams/2022-smody/issues";
private static final List ASSIGNEES = List.of("ldk980130", "bcc0830", "jojogreen91", "tonic523");
private static final List LABELS = List.of("장애", "에러 해결", "백엔드");
private static final MediaType JSON_MEDIA_TYPE = new MediaType("application", "json", StandardCharsets.UTF_8);
@@ -32,7 +33,7 @@ public class GithubApi {
public void create(Exception exception) {
restTemplate.exchange(
- REPO_URL,
+ REPOSITORY_URL,
HttpMethod.POST,
new HttpEntity<>(createRequestJson(exception), setAuthorization()),
String.class
@@ -42,7 +43,7 @@ public void create(Exception exception) {
private String createRequestJson(Exception exception) {
return parseJsonString(new IssueCreateRequest(
"[ERROR] 서버 장애 발생 " + exception.getMessage(),
- createIssueBody(exception),
+ "```\n" + ExceptionUtils.extractStackTrace(exception) + "\n```",
ASSIGNEES,
LABELS
));
@@ -56,12 +57,6 @@ private String parseJsonString(IssueCreateRequest request) {
}
}
- private String createIssueBody(Exception exception) {
- StringWriter stringWriter = new StringWriter();
- exception.printStackTrace(new PrintWriter(stringWriter));
- return "```\n" + stringWriter + "\n```";
- }
-
private HttpHeaders setAuthorization() {
HttpHeaders httpHeaders = new HttpHeaders();
httpHeaders.set("Authorization", "token " + accessToken);
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/exception/dto/ExceptionResponse.java b/backend/smody/src/main/java/com/woowacourse/smody/exception/dto/ExceptionResponse.java
index 3ef0e12b5..166b45e52 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/exception/dto/ExceptionResponse.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/exception/dto/ExceptionResponse.java
@@ -1,7 +1,6 @@
package com.woowacourse.smody.exception.dto;
import com.woowacourse.smody.exception.ExceptionData;
-
import lombok.Getter;
import lombok.NoArgsConstructor;
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/exception/dto/IssueCreateRequest.java b/backend/smody/src/main/java/com/woowacourse/smody/exception/dto/IssueCreateRequest.java
index 69836b43f..493d1ceab 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/exception/dto/IssueCreateRequest.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/exception/dto/IssueCreateRequest.java
@@ -1,7 +1,6 @@
package com.woowacourse.smody.exception.dto;
import java.util.List;
-
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
@@ -11,8 +10,8 @@
@Getter
public class IssueCreateRequest {
- private String title;
- private String body;
- private List assignees;
- private List labels;
+ private String title;
+ private String body;
+ private List assignees;
+ private List labels;
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/feed/controller/FeedController.java b/backend/smody/src/main/java/com/woowacourse/smody/feed/controller/FeedController.java
index 6d23a84ad..bb65856ec 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/feed/controller/FeedController.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/feed/controller/FeedController.java
@@ -27,7 +27,7 @@ public ResponseEntity> findAll(@ModelAttribute PagingParams p
@GetMapping("/{cycleDetailId}")
public ResponseEntity findById(@PathVariable Long cycleDetailId) {
- FeedResponse feedResponse = feedApiService.searchById(cycleDetailId);
+ FeedResponse feedResponse = feedApiService.findById(cycleDetailId);
return ResponseEntity.ok(feedResponse);
}
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/feed/domain/Feed.java b/backend/smody/src/main/java/com/woowacourse/smody/feed/domain/Feed.java
index 2815824dc..3e1091e0b 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/feed/domain/Feed.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/feed/domain/Feed.java
@@ -42,7 +42,7 @@ public Feed(
this.commentCount = commentCount;
}
- /*
+ /**
* QueryDSL Projection 을 위한 생성자
*/
public Feed(
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/feed/repository/FeedDynamicRepository.java b/backend/smody/src/main/java/com/woowacourse/smody/feed/repository/FeedDynamicRepository.java
index 4d267d135..6dd91418c 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/feed/repository/FeedDynamicRepository.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/feed/repository/FeedDynamicRepository.java
@@ -6,5 +6,5 @@
public interface FeedDynamicRepository {
- List searchAll(PagingParams pagingParams);
+ List findAll(PagingParams pagingParams);
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/feed/repository/FeedDynamicRepositoryImpl.java b/backend/smody/src/main/java/com/woowacourse/smody/feed/repository/FeedDynamicRepositoryImpl.java
index 91542ebde..b01e5b3e9 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/feed/repository/FeedDynamicRepositoryImpl.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/feed/repository/FeedDynamicRepositoryImpl.java
@@ -1,5 +1,6 @@
package com.woowacourse.smody.feed.repository;
+import static com.querydsl.core.types.ExpressionUtils.as;
import static com.woowacourse.smody.challenge.domain.QChallenge.challenge;
import static com.woowacourse.smody.comment.domain.QComment.comment;
import static com.woowacourse.smody.cycle.domain.QCycle.cycle;
@@ -7,7 +8,6 @@
import static com.woowacourse.smody.member.domain.QMember.member;
import com.querydsl.core.types.Expression;
-import com.querydsl.core.types.ExpressionUtils;
import com.querydsl.core.types.OrderSpecifier;
import com.querydsl.core.types.Projections;
import com.querydsl.jpa.JPAExpressions;
@@ -23,7 +23,7 @@
@RequiredArgsConstructor
public class FeedDynamicRepositoryImpl implements FeedDynamicRepository {
- private static final Expression COMMENT_COUNT = ExpressionUtils.as(
+ private static final Expression COMMENT_COUNT = as(
JPAExpressions.select(comment.count())
.from(comment)
.where(comment.cycleDetail.eq(cycleDetail)),
@@ -41,12 +41,12 @@ public class FeedDynamicRepositoryImpl implements FeedDynamicRepository {
private final JPAQueryFactory queryFactory;
@Override
- public List searchAll(PagingParams pagingParams) {
- OrderSpecifier>[] orders = SortSelection.findByParameter(pagingParams.getSort())
+ public List findAll(PagingParams pagingParams) {
+ OrderSpecifier>[] orderSpecifiers = SortSelection.findByParameter(pagingParams.getSort())
.getOrderSpecifiers();
- Long cycleDetailId = pagingParams.getCursorId();
- LocalDateTime progressTime = getCursorProgressTime(cycleDetailId);
+ Long cursorCycleDetailId = pagingParams.getCursorId();
+ LocalDateTime cursorProgressTime = getCursorProgressTime(cursorCycleDetailId);
return queryFactory
.select(Projections.constructor(Feed.class, FEED_FIELDS))
@@ -55,11 +55,11 @@ public List searchAll(PagingParams pagingParams) {
.join(cycle.member, member)
.join(cycle.challenge, challenge)
.where(DynamicQuery.builder()
- .and(() -> cycleDetail.id.ne(cycleDetailId))
- .and(() -> cycleDetail.progressTime.loe(progressTime))
+ .and(() -> cycleDetail.id.ne(cursorCycleDetailId))
+ .and(() -> cycleDetail.progressTime.loe(cursorProgressTime))
.build()
)
- .orderBy(orders)
+ .orderBy(orderSpecifiers)
.limit(pagingParams.getSize())
.fetch();
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/feed/service/FeedApiService.java b/backend/smody/src/main/java/com/woowacourse/smody/feed/service/FeedApiService.java
index 11d5842f3..8118d2162 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/feed/service/FeedApiService.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/feed/service/FeedApiService.java
@@ -3,7 +3,6 @@
import com.woowacourse.smody.db_support.PagingParams;
import com.woowacourse.smody.feed.domain.Feed;
import com.woowacourse.smody.feed.dto.FeedResponse;
-import com.woowacourse.smody.feed.repository.FeedRepository;
import java.util.List;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
@@ -18,13 +17,13 @@ public class FeedApiService {
private final FeedService feedService;
public List findAll(PagingParams pagingParams) {
- List feeds = feedService.searchAll(pagingParams);
+ List feeds = feedService.findAll(pagingParams);
return feeds.stream()
.map(FeedResponse::new)
.collect(Collectors.toList());
}
- public FeedResponse searchById(Long cycleDetailId) {
+ public FeedResponse findById(Long cycleDetailId) {
Feed feed = feedService.search(cycleDetailId);
return new FeedResponse(feed);
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/feed/service/FeedService.java b/backend/smody/src/main/java/com/woowacourse/smody/feed/service/FeedService.java
index 3d9118e16..4c8cf187d 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/feed/service/FeedService.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/feed/service/FeedService.java
@@ -15,12 +15,12 @@ public class FeedService {
private final FeedRepository feedRepository;
+ public List findAll(PagingParams pagingParams) {
+ return feedRepository.findAll(pagingParams);
+ }
+
public Feed search(Long cycleDetailId) {
return feedRepository.findByCycleDetailId(cycleDetailId)
.orElseThrow(() -> new BusinessException(ExceptionData.NOT_FOUND_CYCLE_DETAIL));
}
-
- public List searchAll(PagingParams pagingParams) {
- return feedRepository.searchAll(pagingParams);
- }
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/image/strategy/ImgBBImageStrategy.java b/backend/smody/src/main/java/com/woowacourse/smody/image/strategy/ImgBBImageStrategy.java
index 452683c22..e19d01647 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/image/strategy/ImgBBImageStrategy.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/image/strategy/ImgBBImageStrategy.java
@@ -1,5 +1,9 @@
package com.woowacourse.smody.image.strategy;
+import com.woowacourse.smody.exception.BusinessException;
+import com.woowacourse.smody.exception.ExceptionData;
+import com.woowacourse.smody.image.dto.ImageUrlResponse;
+import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
@@ -9,12 +13,6 @@
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
-import com.woowacourse.smody.exception.BusinessException;
-import com.woowacourse.smody.exception.ExceptionData;
-import com.woowacourse.smody.image.dto.ImageUrlResponse;
-
-import lombok.RequiredArgsConstructor;
-
@Component
@RequiredArgsConstructor
public class ImgBBImageStrategy implements ImageStrategy {
@@ -26,18 +24,9 @@ public class ImgBBImageStrategy implements ImageStrategy {
@Override
public String extractUrl(MultipartFile rawImage) {
validateEmptyImage(rawImage);
-
String requestUri = "https://api.imgbb.com/1/upload?key=" + IMGBB_KEY;
-
- MultiValueMap body = new LinkedMultiValueMap<>();
- body.add("image", rawImage.getResource());
-
- HttpHeaders headers = new HttpHeaders();
- headers.setContentType(MediaType.MULTIPART_FORM_DATA);
-
- HttpEntity> httpEntity = new HttpEntity<>(body, headers);
ImageUrlResponse response = restTemplate
- .postForEntity(requestUri, httpEntity, ImageUrlResponse.class)
+ .postForEntity(requestUri, generateRequestHttpEntity(rawImage), ImageUrlResponse.class)
.getBody();
if (response == null) {
throw new IllegalStateException("ImgBB 서버로부터 응답을 받아오지 못했습니다.");
@@ -50,4 +39,23 @@ private void validateEmptyImage(MultipartFile image) {
throw new BusinessException(ExceptionData.EMPTY_IMAGE);
}
}
+
+ private HttpEntity> generateRequestHttpEntity(MultipartFile rawImage) {
+ MultiValueMap body = generateBody(rawImage);
+ HttpHeaders headers = generateHeaders();
+ HttpEntity> httpEntity = new HttpEntity<>(body, headers);
+ return httpEntity;
+ }
+
+ private MultiValueMap generateBody(MultipartFile rawImage) {
+ MultiValueMap body = new LinkedMultiValueMap<>();
+ body.add("image", rawImage.getResource());
+ return body;
+ }
+
+ private HttpHeaders generateHeaders() {
+ HttpHeaders headers = new HttpHeaders();
+ headers.setContentType(MediaType.MULTIPART_FORM_DATA);
+ return headers;
+ }
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/image/strategy/SmodyImageStrategy.java b/backend/smody/src/main/java/com/woowacourse/smody/image/strategy/SmodyImageStrategy.java
index 8f8969596..1751e933b 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/image/strategy/SmodyImageStrategy.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/image/strategy/SmodyImageStrategy.java
@@ -1,5 +1,8 @@
package com.woowacourse.smody.image.strategy;
+import com.woowacourse.smody.exception.BusinessException;
+import com.woowacourse.smody.exception.ExceptionData;
+import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Primary;
import org.springframework.http.HttpEntity;
@@ -12,14 +15,9 @@
import org.springframework.web.client.RestTemplate;
import org.springframework.web.multipart.MultipartFile;
-import com.woowacourse.smody.exception.BusinessException;
-import com.woowacourse.smody.exception.ExceptionData;
-
-import lombok.RequiredArgsConstructor;
-
@Component
-@RequiredArgsConstructor
@Primary
+@RequiredArgsConstructor
public class SmodyImageStrategy implements ImageStrategy {
@Value("${secret.key.upload}")
@@ -32,7 +30,7 @@ public String extractUrl(MultipartFile rawImage) {
ResponseEntity imageServerResponse = restTemplate
.postForEntity(
"https://images.smody.co.kr/images/upload",
- generateImageRequest(rawImage),
+ generateRequestHttpEntity(rawImage),
String.class
);
if (imageServerResponse.getStatusCode().is4xxClientError()) {
@@ -47,14 +45,20 @@ private void validateEmptyImage(MultipartFile image) {
}
}
- private HttpEntity> generateImageRequest(final MultipartFile rawImage) {
+ private HttpEntity> generateRequestHttpEntity(MultipartFile rawImage) {
+ return new HttpEntity<>(generateBody(rawImage), generateHeaders());
+ }
+
+ private MultiValueMap generateBody(MultipartFile rawImage) {
MultiValueMap body = new LinkedMultiValueMap<>();
body.add("rawImage", rawImage.getResource());
body.add("secretKeyUpload", secretKeyUpload);
+ return body;
+ }
+ private HttpHeaders generateHeaders() {
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.MULTIPART_FORM_DATA);
-
- return new HttpEntity<>(body, headers);
+ return headers;
}
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/logging/LogInterceptor.java b/backend/smody/src/main/java/com/woowacourse/smody/logging/LogInterceptor.java
index 4482e5c73..f822b5538 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/logging/LogInterceptor.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/logging/LogInterceptor.java
@@ -13,7 +13,7 @@ public class LogInterceptor implements HandlerInterceptor {
@Override
public boolean preHandle(HttpServletRequest request,
HttpServletResponse response,
- Object handler) throws Exception {
+ Object handler) {
log.info("REQUEST URI : {} {}", request.getMethod(), request.getRequestURI());
return true;
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/member/domain/Member.java b/backend/smody/src/main/java/com/woowacourse/smody/member/domain/Member.java
index 97c4d43f2..e5f1272ff 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/member/domain/Member.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/member/domain/Member.java
@@ -3,7 +3,6 @@
import com.woowacourse.smody.exception.BusinessException;
import com.woowacourse.smody.exception.ExceptionData;
import com.woowacourse.smody.image.domain.Image;
-import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/push/WebPushBatch.java b/backend/smody/src/main/java/com/woowacourse/smody/push/WebPushBatch.java
index d2670e505..cd1dc460b 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/push/WebPushBatch.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/push/WebPushBatch.java
@@ -1,6 +1,6 @@
package com.woowacourse.smody.push;
-import static java.util.stream.Collectors.*;
+import static java.util.stream.Collectors.groupingBy;
import java.util.ArrayList;
import java.util.List;
@@ -15,8 +15,7 @@
import com.woowacourse.smody.push.domain.PushSubscription;
import com.woowacourse.smody.push.service.PushNotificationService;
import com.woowacourse.smody.push.service.PushSubscriptionService;
-import com.woowacourse.smody.push.service.WebPushService;
-
+import com.woowacourse.smody.push.api.WebPushApi;
import lombok.RequiredArgsConstructor;
@Component
@@ -26,7 +25,7 @@ public class WebPushBatch {
private final PushNotificationService pushNotificationService;
private final PushSubscriptionService pushSubscriptionService;
- private final WebPushService webPushService;
+ private final WebPushApi webPushApi;
@Transactional
public void sendPushNotifications() {
@@ -59,7 +58,7 @@ private void sendNotificationsToMember(List notifications,
private void sendNotificationsToSubscriptions(List notifications, PushNotification notification,
List subscriptions) {
for (PushSubscription pushSubscription : subscriptions) {
- boolean isValidSubscription = webPushService.sendNotification(pushSubscription, notification);
+ boolean isValidSubscription = webPushApi.sendNotification(pushSubscription, notification);
handleFailedPush(notifications, notification, pushSubscription, isValidSubscription);
}
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/push/WebPushScheduler.java b/backend/smody/src/main/java/com/woowacourse/smody/push/WebPushScheduler.java
index c223b025b..ce69f9f4e 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/push/WebPushScheduler.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/push/WebPushScheduler.java
@@ -1,8 +1,7 @@
package com.woowacourse.smody.push;
-import org.springframework.scheduling.annotation.Scheduled;
-
import lombok.RequiredArgsConstructor;
+import org.springframework.scheduling.annotation.Scheduled;
@RequiredArgsConstructor
public class WebPushScheduler {
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/push/service/WebPushService.java b/backend/smody/src/main/java/com/woowacourse/smody/push/api/WebPushApi.java
similarity index 83%
rename from backend/smody/src/main/java/com/woowacourse/smody/push/service/WebPushService.java
rename to backend/smody/src/main/java/com/woowacourse/smody/push/api/WebPushApi.java
index b013d8c30..ceff31832 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/push/service/WebPushService.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/push/api/WebPushApi.java
@@ -1,4 +1,4 @@
-package com.woowacourse.smody.push.service;
+package com.woowacourse.smody.push.api;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.woowacourse.smody.exception.BusinessException;
@@ -17,20 +17,19 @@
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.jose4j.lang.JoseException;
import org.springframework.beans.factory.annotation.Value;
-import org.springframework.stereotype.Service;
+import org.springframework.stereotype.Component;
-@Service
+@Component
@Slf4j
-public class WebPushService {
+public class WebPushApi {
private final String publicKey;
private final PushService pushService;
private final ObjectMapper objectMapper;
- public WebPushService(
- @Value("${vapid.public.key}") String publicKey,
- @Value("${vapid.private.key}") String privateKey) throws GeneralSecurityException {
+ public WebPushApi(@Value("${vapid.public.key}") String publicKey,
+ @Value("${vapid.private.key}") String privateKey) throws GeneralSecurityException {
this.publicKey = publicKey;
Security.addProvider(new BouncyCastleProvider());
this.pushService = new PushService(publicKey, privateKey);
@@ -51,8 +50,7 @@ public boolean sendNotification(PushSubscription pushSubscription, PushNotificat
log.warn("스레드가 interrupted 되었습니다.");
Thread.currentThread().interrupt();
throw new BusinessException(ExceptionData.WEB_PUSH_ERROR);
- } catch (GeneralSecurityException | IOException
- | JoseException | ExecutionException exception) {
+ } catch (GeneralSecurityException | IOException | JoseException | ExecutionException exception) {
throw new BusinessException(ExceptionData.WEB_PUSH_ERROR);
}
return httpResponse != null && httpResponse.getStatusLine().getStatusCode() == 201;
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/push/controller/PushNotificationController.java b/backend/smody/src/main/java/com/woowacourse/smody/push/controller/PushNotificationController.java
index 87e9a1dd9..ab1ef9298 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/push/controller/PushNotificationController.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/push/controller/PushNotificationController.java
@@ -24,12 +24,13 @@ public class PushNotificationController {
private final PushNotificationApiService pushNotificationApiService;
- //TODO: api 수정(/push-notifications/me)
+ // TODO-api 수정(/push-notifications/me)
@GetMapping
@RequiredLogin
- public ResponseEntity> searchNotificationsByMe(
- @LoginMember TokenPayload tokenPayload) {
- return ResponseEntity.ok(pushNotificationApiService.searchNotificationsByMe(tokenPayload));
+ public ResponseEntity> findCompleteNotificationsByMe(
+ @LoginMember TokenPayload tokenPayload
+ ) {
+ return ResponseEntity.ok(pushNotificationApiService.findCompleteNotificationsByMe(tokenPayload));
}
@DeleteMapping("{id}")
@@ -39,18 +40,19 @@ public ResponseEntity deleteNotification(@PathVariable Long id) {
return ResponseEntity.noContent().build();
}
+ // TODO-api 수정(/push-notifications/mention)
@PostMapping
@RequiredLogin
- public ResponseEntity saveNotification(@LoginMember TokenPayload tokenPayload,
- @RequestBody MentionNotificationRequest mentionNotificationRequest) {
- pushNotificationApiService.saveNotification(tokenPayload, mentionNotificationRequest);
+ public ResponseEntity createMentionNotification(@LoginMember TokenPayload tokenPayload,
+ @RequestBody MentionNotificationRequest mentionNotificationRequest) {
+ pushNotificationApiService.createMentionNotification(tokenPayload, mentionNotificationRequest);
return ResponseEntity.ok().build();
}
@DeleteMapping("/me")
@RequiredLogin
- public ResponseEntity deleteMyCompleteNotifications(@LoginMember TokenPayload tokenPayload) {
- pushNotificationApiService.deleteMyCompleteNotifications(tokenPayload);
+ public ResponseEntity deleteCompleteNotificationsByMe(@LoginMember TokenPayload tokenPayload) {
+ pushNotificationApiService.deleteCompleteNotificationsByMe(tokenPayload);
return ResponseEntity.noContent().build();
}
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/push/controller/PushSubscriptionController.java b/backend/smody/src/main/java/com/woowacourse/smody/push/controller/PushSubscriptionController.java
index d5308d954..03bb94c2a 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/push/controller/PushSubscriptionController.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/push/controller/PushSubscriptionController.java
@@ -7,7 +7,7 @@
import com.woowacourse.smody.push.dto.UnSubscriptionRequest;
import com.woowacourse.smody.push.dto.VapidPublicKeyResponse;
import com.woowacourse.smody.push.service.PushSubscriptionApiService;
-import com.woowacourse.smody.push.service.WebPushService;
+import com.woowacourse.smody.push.api.WebPushApi;
import lombok.RequiredArgsConstructor;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
@@ -22,11 +22,11 @@
public class PushSubscriptionController {
private final PushSubscriptionApiService pushSubscriptionApiService;
- private final WebPushService webPushService;
+ private final WebPushApi webPushApi;
@GetMapping("/public-key")
public ResponseEntity sendPublicKey() {
- return ResponseEntity.ok(new VapidPublicKeyResponse(webPushService.getPublicKey()));
+ return ResponseEntity.ok(new VapidPublicKeyResponse(webPushApi.getPublicKey()));
}
@PostMapping("/subscribe")
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/push/domain/PushNotification.java b/backend/smody/src/main/java/com/woowacourse/smody/push/domain/PushNotification.java
index 98e023a3c..463462c27 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/push/domain/PushNotification.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/push/domain/PushNotification.java
@@ -51,8 +51,12 @@ public class PushNotification {
private Long pathId;
@Builder
- public PushNotification(String message, LocalDateTime pushTime, PushStatus pushStatus, Member member,
- PushCase pushCase, Long pathId) {
+ public PushNotification(String message,
+ LocalDateTime pushTime,
+ PushStatus pushStatus,
+ Member member,
+ PushCase pushCase,
+ Long pathId) {
this.message = message;
this.pushTime = pushTime;
this.pushStatus = pushStatus;
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/push/domain/PushSubscription.java b/backend/smody/src/main/java/com/woowacourse/smody/push/domain/PushSubscription.java
index d1497e6c9..83df11426 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/push/domain/PushSubscription.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/push/domain/PushSubscription.java
@@ -51,8 +51,7 @@ public PushSubscription(String endpoint, String p256dh, String auth, Member memb
this.member = member;
}
- public PushSubscription updateMember(Member member) {
+ public void updateMember(Member member) {
this.member = member;
- return this;
}
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/push/dto/SubscriptionRequest.java b/backend/smody/src/main/java/com/woowacourse/smody/push/dto/SubscriptionRequest.java
index 1e2b57e31..9af971f29 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/push/dto/SubscriptionRequest.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/push/dto/SubscriptionRequest.java
@@ -12,7 +12,7 @@ public SubscriptionRequest(String endpoint, String p256dh, String auth) {
super(endpoint, new Keys(p256dh, auth));
}
- public PushSubscription toEntity(Member member) {
+ public PushSubscription toPushSubscriptionEntity(Member member) {
return new PushSubscription(endpoint, keys.p256dh, keys.auth, member);
}
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/push/event/ChallengePushEventListener.java b/backend/smody/src/main/java/com/woowacourse/smody/push/event/ChallengePushEventListener.java
index 6b2513b6a..cc27352ef 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/push/event/ChallengePushEventListener.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/push/event/ChallengePushEventListener.java
@@ -38,7 +38,7 @@ public void handle(CycleProgressEvent event) {
}
private void executePush(Cycle cycle) {
- cycle = cycleService.search(cycle.getId());
+ cycle = cycleService.searchCycle(cycle.getId());
deleteInCompleteNotificationIfSamePathIdPresent(cycle);
if (cycle.isSuccess()) {
return;
@@ -47,7 +47,7 @@ private void executePush(Cycle cycle) {
}
private void deleteInCompleteNotificationIfSamePathIdPresent(Cycle cycle) {
- pushNotificationService.searchSamePathAndStatus(cycle.getId(), PushStatus.IN_COMPLETE)
+ pushNotificationService.searchByPathAndStatusAndPushCase(cycle.getId(), PushStatus.IN_COMPLETE, PushCase.CHALLENGE)
.ifPresent(notification -> pushNotificationService.deleteById(notification.getId()));
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/push/repository/PushNotificationRepository.java b/backend/smody/src/main/java/com/woowacourse/smody/push/repository/PushNotificationRepository.java
index 0827983a5..fd45a3710 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/push/repository/PushNotificationRepository.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/push/repository/PushNotificationRepository.java
@@ -1,6 +1,7 @@
package com.woowacourse.smody.push.repository;
import com.woowacourse.smody.member.domain.Member;
+import com.woowacourse.smody.push.domain.PushCase;
import com.woowacourse.smody.push.domain.PushNotification;
import com.woowacourse.smody.push.domain.PushStatus;
import java.util.List;
@@ -14,22 +15,25 @@ public interface PushNotificationRepository extends JpaRepository findByPushStatus(PushStatus pushStatus);
- Optional findByPathIdAndPushStatus(Long pathId, PushStatus pushStatus);
+ Optional findByPathIdAndPushStatusAndPushCase(Long pathId, PushStatus pushStatus, PushCase pushCase);
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("delete from PushNotification pn where pn.member = :member")
void deleteByMember(@Param("member") Member member);
@Modifying(clearAutomatically = true, flushAutomatically = true)
- @Query("delete from PushNotification pn where pn.member = :member and pn.pushStatus = :pushStatus")
+ @Query("delete from PushNotification pn "
+ + "where pn.member = :member and pn.pushStatus = :pushStatus")
void deleteByMemberAndPushStatus(@Param("member") Member member, @Param("pushStatus") PushStatus pushStatus);
- @Query("select pn from PushNotification pn where pn.member = :member and pn.pushStatus = :pushStatus "
+ @Query("select pn from PushNotification pn "
+ + "where pn.member = :member and pn.pushStatus = :pushStatus "
+ "order by pn.pushTime desc")
- List findAllLatest(@Param("member") Member member, @Param("pushStatus") PushStatus pushStatus);
+ List findAllLatestOrderByDesc(@Param("member") Member member,
+ @Param("pushStatus") PushStatus pushStatus);
@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("update PushNotification pn set pn.pushStatus = :pushStatus where pn in :notifications")
- void updatePushStatusIn(@Param("notifications") List notifications,
- @Param("pushStatus") PushStatus pushStatus);
+ void updatePushStatusIn(@Param("notifications") List notifications,
+ @Param("pushStatus") PushStatus pushStatus);
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/push/repository/PushSubscriptionRepository.java b/backend/smody/src/main/java/com/woowacourse/smody/push/repository/PushSubscriptionRepository.java
index 747ee0e37..d24933fe2 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/push/repository/PushSubscriptionRepository.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/push/repository/PushSubscriptionRepository.java
@@ -17,7 +17,7 @@ public interface PushSubscriptionRepository extends JpaRepository findByMemberIn(List members);
- @Modifying(clearAutomatically = true)
+ @Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("delete from PushSubscription ps where ps.member = :member")
void deleteByMember(@Param("member") Member member);
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/push/service/PushNotificationApiService.java b/backend/smody/src/main/java/com/woowacourse/smody/push/service/PushNotificationApiService.java
index 12a7e49cb..bd8183418 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/push/service/PushNotificationApiService.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/push/service/PushNotificationApiService.java
@@ -23,11 +23,10 @@ public class PushNotificationApiService {
private final PushNotificationService pushNotificationService;
private final MemberService memberService;
- public List searchNotificationsByMe(TokenPayload tokenPayload) {
+ public List findCompleteNotificationsByMe(TokenPayload tokenPayload) {
Member member = memberService.search(tokenPayload.getId());
- List pushNotifications = pushNotificationService
- .findAllLatest(member, PushStatus.COMPLETE);
-
+ List pushNotifications =
+ pushNotificationService.findAllLatestOrderByDesc(member, PushStatus.COMPLETE);
return pushNotifications.stream()
.map(PushNotificationResponse::new)
.collect(Collectors.toList());
@@ -39,42 +38,37 @@ public void deleteById(Long id) {
}
@Transactional
- public void saveNotification(TokenPayload tokenPayload,
- MentionNotificationRequest mentionNotificationRequest) {
- List mentionedIds = mentionNotificationRequest.getMemberIds();
- Long mentioningId = tokenPayload.getId();
- Long cycleDetailId = mentionNotificationRequest.getPathId();
- if (mentionedIds.isEmpty() || mentioningId == null || cycleDetailId == null) {
- return;
- }
-
- List mentionedMembers = memberService.searchByIdIn(mentionedIds);
+ public void createMentionNotification(TokenPayload tokenPayload,
+ MentionNotificationRequest mentionNotificationRequest) {
+ List mentionedMembers = memberService.searchByIdIn(mentionNotificationRequest.getMemberIds());
Member mentioningMember = memberService.searchLoginMember(tokenPayload.getId());
List pushNotifications = generatePushNotifications(
- mentionedMembers, mentioningMember, cycleDetailId);
+ mentionedMembers, mentioningMember, mentionNotificationRequest.getPathId()
+ );
pushNotifications.forEach(pushNotificationService::create);
}
- private List generatePushNotifications(List mentionedMembers, Member mentioningMember,
+ private List generatePushNotifications(List mentionedMembers,
+ Member mentioningMember,
Long pathId) {
return mentionedMembers.stream()
- .map(each -> buildNotification(mentioningMember, each, pathId))
+ .map(mentionedMember -> buildNotification(mentioningMember, mentionedMember, pathId))
.collect(Collectors.toUnmodifiableList());
}
- private PushNotification buildNotification(Member mentioning, Member mentioned, Long pathId) {
+ private PushNotification buildNotification(Member mentioning, Member mentionedMember, Long pathId) {
return PushNotification.builder()
.message(mentioning.getNickname() + "님께서 회원님을 언급하셨습니다!")
.pushTime(LocalDateTime.now())
.pushStatus(PushStatus.IN_COMPLETE)
.pushCase(PushCase.MENTION)
- .member(mentioned)
+ .member(mentionedMember)
.pathId(pathId)
.build();
}
@Transactional
- public void deleteMyCompleteNotifications(TokenPayload tokenPayload) {
+ public void deleteCompleteNotificationsByMe(TokenPayload tokenPayload) {
Member member = memberService.search(tokenPayload.getId());
pushNotificationService.deleteCompletedByMember(member);
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/push/service/PushNotificationService.java b/backend/smody/src/main/java/com/woowacourse/smody/push/service/PushNotificationService.java
index 290c6ecdf..673931b4d 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/push/service/PushNotificationService.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/push/service/PushNotificationService.java
@@ -1,6 +1,9 @@
package com.woowacourse.smody.push.service;
+import com.woowacourse.smody.exception.BusinessException;
+import com.woowacourse.smody.exception.ExceptionData;
import com.woowacourse.smody.member.domain.Member;
+import com.woowacourse.smody.push.domain.PushCase;
import com.woowacourse.smody.push.domain.PushNotification;
import com.woowacourse.smody.push.domain.PushStatus;
import com.woowacourse.smody.push.repository.PushNotificationRepository;
@@ -9,6 +12,7 @@
import java.util.Optional;
import java.util.stream.Collectors;
import lombok.RequiredArgsConstructor;
+import org.springframework.dao.EmptyResultDataAccessException;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@@ -24,8 +28,8 @@ public void create(PushNotification pushNotification) {
pushNotificationRepository.save(pushNotification);
}
- public Optional searchSamePathAndStatus(Long pathId, PushStatus status) {
- return pushNotificationRepository.findByPathIdAndPushStatus(pathId, status);
+ public Optional searchByPathAndStatusAndPushCase(Long pathId, PushStatus status, PushCase pushCase) {
+ return pushNotificationRepository.findByPathIdAndPushStatusAndPushCase(pathId, status, pushCase);
}
public List searchPushable() {
@@ -41,13 +45,17 @@ public void completeAll(List notifications) {
pushNotificationRepository.updatePushStatusIn(notifications, PushStatus.COMPLETE);
}
- public List findAllLatest(Member member, PushStatus pushStatus) {
- return pushNotificationRepository.findAllLatest(member, pushStatus);
+ public List findAllLatestOrderByDesc(Member member, PushStatus pushStatus) {
+ return pushNotificationRepository.findAllLatestOrderByDesc(member, pushStatus);
}
@Transactional
public void deleteById(Long pushNotificationId) {
- pushNotificationRepository.deleteById(pushNotificationId);
+ try {
+ pushNotificationRepository.deleteById(pushNotificationId);
+ } catch (EmptyResultDataAccessException e) {
+ throw new BusinessException(ExceptionData.NOT_FOUND_PUSH_NOTIFICATION);
+ }
}
@Transactional
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/push/service/PushSubscriptionApiService.java b/backend/smody/src/main/java/com/woowacourse/smody/push/service/PushSubscriptionApiService.java
index 221345964..eb0b6dd9c 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/push/service/PushSubscriptionApiService.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/push/service/PushSubscriptionApiService.java
@@ -25,20 +25,20 @@ public class PushSubscriptionApiService {
public void subscribe(TokenPayload tokenPayload, SubscriptionRequest subscriptionRequest) {
Member member = memberService.search(tokenPayload.getId());
PushSubscription subscription = pushSubscriptionService.findByEndpoint(subscriptionRequest.endpoint)
- .map(pushSubscription -> pushSubscription.updateMember(member))
- .orElseGet(() -> pushSubscriptionService.create(subscriptionRequest.toEntity(member)));
+ .map(pushSubscription -> getUpdatedSubscription(member, pushSubscription))
+ .orElseGet(() -> pushSubscriptionService.create(subscriptionRequest.toPushSubscriptionEntity(member)));
applicationEventPublisher.publishEvent(new PushSubscribeEvent(subscription));
}
+ private PushSubscription getUpdatedSubscription(final Member member, final PushSubscription pushSubscription) {
+ pushSubscription.updateMember(member);
+ return pushSubscription;
+ }
+
@Transactional
public void unSubscribe(TokenPayload tokenPayload, UnSubscriptionRequest unSubscription) {
memberService.search(tokenPayload.getId());
pushSubscriptionService.deleteByEndpoint(unSubscription.getEndpoint());
}
-
- @Transactional
- public void delete(PushSubscription pushSubscription) {
- pushSubscriptionService.delete(pushSubscription);
- }
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/ranking/controller/RankingController.java b/backend/smody/src/main/java/com/woowacourse/smody/ranking/controller/RankingController.java
index a5c4732ec..055846d05 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/ranking/controller/RankingController.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/ranking/controller/RankingController.java
@@ -24,19 +24,19 @@ public class RankingController {
private final RankingApiService rankingApiService;
@GetMapping
- public ResponseEntity> findAll(@ModelAttribute PagingParams pagingParams) {
- return ResponseEntity.ok(rankingApiService.findAllPeriod(pagingParams));
+ public ResponseEntity> findAllRankingPeriod(@ModelAttribute PagingParams pagingParams) {
+ return ResponseEntity.ok(rankingApiService.findAllRankingPeriod(pagingParams));
}
@GetMapping("/{rankingPeriodId}/ranking-activities")
- public ResponseEntity> findAllActivity(@PathVariable Long rankingPeriodId) {
- return ResponseEntity.ok(rankingApiService.findAllRankedActivityByPeriodId(rankingPeriodId));
+ public ResponseEntity> findAllRankingActivity(@PathVariable Long rankingPeriodId) {
+ return ResponseEntity.ok(rankingApiService.findAllRankingActivityByPeriodId(rankingPeriodId));
}
@GetMapping("/{rankingPeriodId}/ranking-activities/me")
@RequiredLogin
- public ResponseEntity findActivityOfMine(@LoginMember TokenPayload tokenPayload,
- @PathVariable Long rankingPeriodId) {
+ public ResponseEntity findRankingActivityOfMine(@LoginMember TokenPayload tokenPayload,
+ @PathVariable Long rankingPeriodId) {
return ResponseEntity.ok(rankingApiService.findActivityOfMine(tokenPayload, rankingPeriodId));
}
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/ranking/domain/RankManager.java b/backend/smody/src/main/java/com/woowacourse/smody/ranking/domain/RankManager.java
index 7122a9791..0d024a3d3 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/ranking/domain/RankManager.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/ranking/domain/RankManager.java
@@ -1,27 +1,37 @@
package com.woowacourse.smody.ranking.domain;
+import java.util.Comparator;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
+import java.util.stream.Collectors;
public class RankManager {
- public final Map activityRanks;
+ public final Map ranksOfActivities;
- private RankManager(Map activityRanks) {
- this.activityRanks = activityRanks;
+ private RankManager(Map ranksOfActivities) {
+ this.ranksOfActivities = ranksOfActivities;
}
- public static RankManager rank(List activities) {
+ public static RankManager of(List rankingActivities) {
+ List sortedRankinActivities = sortByPointDesc(rankingActivities);
Map cache = new HashMap<>();
- for (int ranking = 0; ranking < activities.size(); ranking++) {
- RankingActivity rankingActivity = activities.get(ranking);
+ for (int ranking = 0; ranking < sortedRankinActivities.size(); ranking++) {
+ RankingActivity rankingActivity = sortedRankinActivities.get(ranking);
cache.putIfAbsent(rankingActivity.getPoint(), ranking + 1);
}
return new RankManager(cache);
}
+ private static List sortByPointDesc(final List rankingActivities) {
+ List sortedRankinActivities = rankingActivities.stream()
+ .sorted(Comparator.comparing(RankingActivity::getPoint).reversed())
+ .collect(Collectors.toList());
+ return sortedRankinActivities;
+ }
+
public Integer getRanking(RankingActivity rankingActivity) {
- return activityRanks.get(rankingActivity.getPoint());
+ return ranksOfActivities.get(rankingActivity.getPoint());
}
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/ranking/domain/RankingActivity.java b/backend/smody/src/main/java/com/woowacourse/smody/ranking/domain/RankingActivity.java
index 9ef9ffa3a..8c6ab8e97 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/ranking/domain/RankingActivity.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/ranking/domain/RankingActivity.java
@@ -30,6 +30,7 @@
public class RankingActivity {
private static final int DEFAULT_POINT = 0;
+
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "ranking_activity_id")
@@ -57,7 +58,7 @@ public static RankingActivity ofZeroPoint(Member member, RankingPeriod period) {
return new RankingActivity(member, period, DEFAULT_POINT);
}
- public void active(Progress progress) {
+ public void plusPoint(Progress progress) {
this.point += Point.calculate(progress);
}
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/ranking/event/RankingPointEventListener.java b/backend/smody/src/main/java/com/woowacourse/smody/ranking/event/RankingPointEventListener.java
index e7f120cd2..4b76e346c 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/ranking/event/RankingPointEventListener.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/ranking/event/RankingPointEventListener.java
@@ -30,7 +30,7 @@ public class RankingPointEventListener {
private final RankingService rankingService;
private final TransactionTemplate transactionTemplate;
-
+
@TransactionalEventListener
@Async("asyncExecutor")
public void handle(CycleProgressEvent event) {
@@ -42,9 +42,9 @@ public void handle(CycleProgressEvent event) {
try {
applyRankingPointWithTransaction(cycle);
} catch (DataIntegrityViolationException e) {
- /*
- * 여러 사람이 동시에 랭킹 점수에 관한 이벤트를 발생했을 경우
- * unique 제약조건으로 예외가 발생한 이벤트를 새로운 트랜잭션으로 처리한다.
+ /**
+ * 여러 사람이 동시에 랭킹 점수에 관한 이벤트를 발생했을 경우
+ * unique 제약조건으로 예외가 발생한 이벤트를 새로운 트랜잭션으로 처리한다.
*/
applyRankingPointWithTransaction(cycle);
}
@@ -87,7 +87,7 @@ private void addActivityIfAbsent(List periods, Member member, Lis
private void updateActivities(CycleDetail cycleDetail, List activities) {
for (RankingActivity activity : activities) {
- activity.active(cycleDetail.getProgress());
+ activity.plusPoint(cycleDetail.getProgress());
}
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/ranking/repository/RankingActivityRepository.java b/backend/smody/src/main/java/com/woowacourse/smody/ranking/repository/RankingActivityRepository.java
index 63a62cf6a..5c6e8d304 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/ranking/repository/RankingActivityRepository.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/ranking/repository/RankingActivityRepository.java
@@ -4,13 +4,17 @@
import com.woowacourse.smody.ranking.domain.RankingActivity;
import com.woowacourse.smody.ranking.domain.RankingPeriod;
import java.util.List;
-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;
public interface RankingActivityRepository extends JpaRepository {
- @EntityGraph(attributePaths = "member")
- List findAllByRankingPeriodOrderByPointDesc(RankingPeriod rankingPeriod);
+ @Query("select ra from RankingActivity ra "
+ + "join fetch ra.member "
+ + "where ra.rankingPeriod = :rankingPeriod "
+ + "order by ra.point desc")
+ List findAllByRankingPeriodOrderByPointDesc(@Param("rankingPeriod") RankingPeriod rankingPeriod);
List findAllByMemberAndRankingPeriodIn(Member member, List rankingPeriods);
}
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/ranking/service/RankingApiService.java b/backend/smody/src/main/java/com/woowacourse/smody/ranking/service/RankingApiService.java
index bd6b2bc6d..7af813f9a 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/ranking/service/RankingApiService.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/ranking/service/RankingApiService.java
@@ -22,23 +22,23 @@ public class RankingApiService {
private final RankingService rankingService;
- public List findAllPeriod(PagingParams pagingParams) {
- List rankingPeriods = rankingService.findAllPeriod(pagingParams);
+ public List findAllRankingPeriod(PagingParams pagingParams) {
+ List rankingPeriods = rankingService.findAllRankingPeriod(pagingParams);
return rankingPeriods.stream()
.map(RankingPeriodResponse::new)
.collect(Collectors.toList());
}
- public List findAllRankedActivityByPeriodId(Long rankingPeriodId) {
- List rankingActivities = rankingService.findAllRankedActivityByPeriodId(rankingPeriodId);
- RankManager rankManager = RankManager.rank(rankingActivities);
+ public List findAllRankingActivityByPeriodId(Long rankingPeriodId) {
+ List rankingActivities = rankingService.findAllRankingActivityByPeriodId(rankingPeriodId);
+ RankManager rankManager = RankManager.of(rankingActivities);
return rankingActivities.stream()
.map(activity -> new RankingActivityResponse(rankManager.getRanking(activity), activity))
.collect(Collectors.toList());
}
public RankingActivityResponse findActivityOfMine(TokenPayload tokenPayload, Long rankingPeriodId) {
- List responses = findAllRankedActivityByPeriodId(rankingPeriodId);
+ List responses = findAllRankingActivityByPeriodId(rankingPeriodId);
return responses.stream()
.filter(response -> response.getMemberId().equals(tokenPayload.getId()))
.findFirst()
diff --git a/backend/smody/src/main/java/com/woowacourse/smody/ranking/service/RankingService.java b/backend/smody/src/main/java/com/woowacourse/smody/ranking/service/RankingService.java
index 863701416..6c908e710 100644
--- a/backend/smody/src/main/java/com/woowacourse/smody/ranking/service/RankingService.java
+++ b/backend/smody/src/main/java/com/woowacourse/smody/ranking/service/RankingService.java
@@ -26,15 +26,14 @@ public class RankingService {
private final RankingPeriodRepository rankingPeriodRepository;
private final RankingActivityRepository rankingActivityRepository;
- public List findAllPeriod(PagingParams pagingParams) {
+ public List findAllRankingPeriod(PagingParams pagingParams) {
Sort sort = SortSelection.findByParameter(pagingParams.getSort()).getSort();
return rankingPeriodRepository.findAll(sort);
}
- public List findAllRankedActivityByPeriodId(Long rankingPeriodId) {
+ public List findAllRankingActivityByPeriodId(Long rankingPeriodId) {
RankingPeriod rankingPeriod = search(rankingPeriodId);
- return rankingActivityRepository.findAllByRankingPeriodOrderByPointDesc(
- rankingPeriod);
+ return rankingActivityRepository.findAllByRankingPeriodOrderByPointDesc(rankingPeriod);
}
private RankingPeriod search(Long rankingPeriodId) {
diff --git a/backend/smody/src/test/java/com/woowacourse/smody/challenge/service/ChallengeApiServiceTest.java b/backend/smody/src/test/java/com/woowacourse/smody/challenge/service/ChallengeApiServiceTest.java
index bb3f16092..95351b67e 100644
--- a/backend/smody/src/test/java/com/woowacourse/smody/challenge/service/ChallengeApiServiceTest.java
+++ b/backend/smody/src/test/java/com/woowacourse/smody/challenge/service/ChallengeApiServiceTest.java
@@ -10,18 +10,30 @@
import static com.woowacourse.smody.support.ResourceFixture.조조그린_ID;
import static com.woowacourse.smody.support.ResourceFixture.토닉_ID;
import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertAll;
import com.woowacourse.smody.auth.dto.TokenPayload;
import com.woowacourse.smody.challenge.domain.Challenge;
import com.woowacourse.smody.challenge.dto.ChallengeHistoryResponse;
+import com.woowacourse.smody.challenge.dto.ChallengeOfMineResponse;
+import com.woowacourse.smody.challenge.dto.ChallengeResponse;
import com.woowacourse.smody.challenge.dto.ChallengeTabResponse;
+import com.woowacourse.smody.challenge.dto.ChallengersResponse;
+import com.woowacourse.smody.cycle.domain.Progress;
import com.woowacourse.smody.db_support.PagingParams;
+import com.woowacourse.smody.exception.BusinessException;
+import com.woowacourse.smody.exception.ExceptionData;
+import com.woowacourse.smody.member.domain.Member;
import com.woowacourse.smody.support.IntegrationTest;
import java.time.LocalDateTime;
import java.util.List;
+import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
+import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.ValueSource;
import org.springframework.beans.factory.annotation.Autowired;
class ChallengeApiServiceTest extends IntegrationTest {
@@ -29,105 +41,781 @@ class ChallengeApiServiceTest extends IntegrationTest {
@Autowired
private ChallengeApiService challengeApiService;
- @DisplayName("나의 특정 챌린지에 대한 사이클을 성공 횟수, 인증 횟수와 함께 조회")
- @Test
- void findByMeAndChallenge() {
- // given
- fixture.사이클_생성_SUCCESS(조조그린_ID, 미라클_모닝_ID, LocalDateTime.now());
- fixture.사이클_생성_SECOND(조조그린_ID, 미라클_모닝_ID, LocalDateTime.now());
- fixture.사이클_생성_FIRST(조조그린_ID, 미라클_모닝_ID, LocalDateTime.now());
- fixture.사이클_생성_NOTHING(조조그린_ID, 미라클_모닝_ID, LocalDateTime.now());
-
- // when
- ChallengeHistoryResponse challengeHistoryResponse =
- challengeApiService.findByMeAndChallenge(new TokenPayload(조조그린_ID), 미라클_모닝_ID);
-
- // then
- assertAll(
- () -> assertThat(challengeHistoryResponse.getChallengeName()).isEqualTo("미라클 모닝"),
- () -> assertThat(challengeHistoryResponse.getSuccessCount()).isEqualTo(1),
- () -> assertThat(challengeHistoryResponse.getCycleDetailCount()).isEqualTo(6)
- );
+ private final LocalDateTime now = LocalDateTime.now();
+
+ @DisplayName("비회원이 챌린지를 도전자 수와 함께 조회 할 때")
+ @Nested
+ class FindAllWithChallengerCountByFilter {
+
+ @BeforeEach
+ void setUp() {
+ // given
+ fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.NOTHING, now); // 진행 중
+ fixture.사이클_생성(더즈_ID, 미라클_모닝_ID, Progress.FIRST, now.minusDays(3L)); // 실패
+ fixture.사이클_생성(토닉_ID, 미라클_모닝_ID, Progress.SUCCESS, now.minusDays(3L)); // 성공
+ fixture.사이클_생성(더즈_ID, 스모디_방문하기_ID, Progress.FIRST, now.minusDays(1L)); // 진행 중
+ fixture.사이클_생성(조조그린_ID, 스모디_방문하기_ID, Progress.NOTHING, now); // 진행 중
+ fixture.사이클_생성(토닉_ID, 스모디_방문하기_ID, Progress.SUCCESS, now.minusDays(3L)); //성공
+ }
+
+ @DisplayName("참가자 순으로 size에 맞게 조회")
+ @Test
+ void findAllWithChallengerCountByFilter_popular_size() {
+ // given
+ fixture.사이클_생성_NOTHING(조조그린_ID, 미라클_모닝_ID, LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(더즈_ID, 미라클_모닝_ID, LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(토닉_ID, 미라클_모닝_ID, LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(알파_ID, 미라클_모닝_ID, LocalDateTime.now());
+
+ fixture.사이클_생성_NOTHING(조조그린_ID, 오늘의_운동_ID, LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(더즈_ID, 오늘의_운동_ID, LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(토닉_ID, 오늘의_운동_ID, LocalDateTime.now());
+
+ fixture.사이클_생성_NOTHING(조조그린_ID, 스모디_방문하기_ID, LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(더즈_ID, 스모디_방문하기_ID, LocalDateTime.now());
+
+ // when
+ List actual = challengeApiService.findAllWithChallengerCountByFilter(
+ LocalDateTime.now(),
+ new PagingParams("popular", 2));
+
+ // then
+ assertThat(actual).map(ChallengeTabResponse::getChallengeName)
+ .containsExactly("미라클 모닝", "오늘의 운동");
+ }
+
+ @DisplayName("참가자 순으로 조회할 때 참가자가 없는 챌린지는 조회하지 않는다.")
+ @Test
+ void findAllWithChallengerCountByFilter_popular_notExistEmptyChallengers() {
+ // given
+ Challenge 미라클_모닝 = fixture.챌린지_조회(미라클_모닝_ID);
+ Challenge 오늘의_운동 = fixture.챌린지_조회(오늘의_운동_ID);
+ Challenge 스모디_방문하기 = fixture.챌린지_조회(스모디_방문하기_ID);
+ fixture.챌린지_조회(알고리즘_풀기_ID);
+ fixture.챌린지_조회(JPA_공부_ID);
+
+ fixture.사이클_생성_NOTHING(조조그린_ID, 미라클_모닝.getId(), LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(더즈_ID, 미라클_모닝.getId(), LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(토닉_ID, 미라클_모닝.getId(), LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(알파_ID, 미라클_모닝.getId(), LocalDateTime.now());
+
+ fixture.사이클_생성_NOTHING(조조그린_ID, 오늘의_운동.getId(), LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(더즈_ID, 오늘의_운동.getId(), LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(토닉_ID, 오늘의_운동.getId(), LocalDateTime.now());
+
+ fixture.사이클_생성_NOTHING(조조그린_ID, 스모디_방문하기.getId(), LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(더즈_ID, 스모디_방문하기.getId(), LocalDateTime.now());
+
+ // when
+ List actual = challengeApiService.findAllWithChallengerCountByFilter(
+ LocalDateTime.now(),
+ new PagingParams("popular", 5));
+
+ // then
+ assertThat(actual).map(ChallengeTabResponse::getChallengeName)
+ .containsExactly("미라클 모닝", "오늘의 운동", "스모디 방문하기");
+ }
+
+ @DisplayName("참가자 순으로 정렬하여 조회")
+ @Test
+ void findAllWithChallengerCountByFilter_popular() {
+ // given
+ fixture.사이클_생성_NOTHING(조조그린_ID, 미라클_모닝_ID, LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(더즈_ID, 미라클_모닝_ID, LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(토닉_ID, 미라클_모닝_ID, LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(알파_ID, 미라클_모닝_ID, LocalDateTime.now());
+
+ fixture.사이클_생성_NOTHING(조조그린_ID, 오늘의_운동_ID, LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(더즈_ID, 오늘의_운동_ID, LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(토닉_ID, 오늘의_운동_ID, LocalDateTime.now());
+
+ fixture.사이클_생성_NOTHING(조조그린_ID, 스모디_방문하기_ID, LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(더즈_ID, 스모디_방문하기_ID, LocalDateTime.now());
+
+ // when
+ List actual = challengeApiService.findAllWithChallengerCountByFilter(
+ LocalDateTime.now(),
+ new PagingParams("popular", 10));
+
+ // then
+ assertThat(actual).map(ChallengeTabResponse::getChallengeName)
+ .containsExactly("미라클 모닝", "오늘의 운동", "스모디 방문하기");
+ }
+
+ @DisplayName("조회")
+ @Test
+ void findAllWithChallengerCount_sort() {
+ // when
+ List challengeResponses = challengeApiService.findAllWithChallengerCountByFilter(
+ now, new PagingParams(null, null, 0L, null));
+
+ // then
+ assertAll(
+ () -> assertThat(challengeResponses).hasSize(5),
+ () -> assertThat(challengeResponses.stream().mapToLong(ChallengeTabResponse::getChallengeId))
+ .containsExactly(스모디_방문하기_ID, 미라클_모닝_ID, 오늘의_운동_ID, 알고리즘_풀기_ID, JPA_공부_ID),
+ () -> assertThat(challengeResponses.stream().mapToInt(ChallengeTabResponse::getChallengerCount))
+ .containsExactly(2, 1, 0, 0, 0)
+ );
+ }
+
+ @DisplayName("2개만 조회")
+ @Test
+ void findAllWithChallengerCount_pageFullSize() {
+ // when
+ List challengeResponses = challengeApiService.findAllWithChallengerCountByFilter(now,
+ new PagingParams(null, 2, 0L, null));
+
+ // then
+ assertAll(
+ () -> assertThat(challengeResponses).hasSize(2),
+ () -> assertThat(challengeResponses.stream().map(ChallengeTabResponse::getChallengeId))
+ .containsExactly(스모디_방문하기_ID, 미라클_모닝_ID),
+ () -> assertThat(challengeResponses.stream().mapToInt(ChallengeTabResponse::getChallengerCount))
+ .containsExactly(2, 1)
+ );
+ }
+
+ @DisplayName("커서 기준 2개만 조회")
+ @Test
+ void findAllWithChallengerCount_pagePartialSize() {
+ // when
+ List challengeResponses = challengeApiService.findAllWithChallengerCountByFilter(
+ now, new PagingParams(null, 2, 미라클_모닝_ID, null));
+
+ // then
+ assertAll(
+ () -> assertThat(challengeResponses).hasSize(2),
+ () -> assertThat(challengeResponses.stream().mapToLong(ChallengeTabResponse::getChallengeId))
+ .containsExactly(오늘의_운동_ID, 알고리즘_풀기_ID),
+ () -> assertThat(challengeResponses.stream().mapToInt(ChallengeTabResponse::getChallengerCount))
+ .containsExactly(0, 0)
+ );
+ }
+
+ @DisplayName("데이터의 마지막이 커서이면서 3개 조회")
+ @Test
+ void findAllWithChallengerCount_pageOverMaxPage() {
+ // when
+ List challengeResponses = challengeApiService.findAllWithChallengerCountByFilter(
+ now, new PagingParams(null, 3, JPA_공부_ID, null));
+
+ // then
+ assertThat(challengeResponses).isEmpty();
+ }
+
+ @DisplayName("이름 기준으로 검색하는 경우")
+ @Test
+ void searchByName_unAuthorized() {
+ //given
+ fixture.사이클_생성_NOTHING(알파_ID, 알고리즘_풀기_ID, now);
+ fixture.사이클_생성_NOTHING(토닉_ID, 스모디_방문하기_ID, now);
+ fixture.사이클_생성_NOTHING(더즈_ID, 스모디_방문하기_ID, now);
+
+ // when
+ List challengeTabResponse = challengeApiService.findAllWithChallengerCountByFilter(
+ now, new PagingParams(null, null, 0L, "기"));
+
+ // then
+ assertAll(
+ () -> assertThat(challengeTabResponse).hasSize(2),
+ () -> assertThat(challengeTabResponse.stream().map(ChallengeTabResponse::getChallengeId))
+ .containsExactly(스모디_방문하기_ID, 알고리즘_풀기_ID),
+ () -> assertThat(challengeTabResponse.stream().map(ChallengeTabResponse::getChallengerCount))
+ .containsExactly(3, 1),
+ () -> assertThat(challengeTabResponse.stream().map(ChallengeTabResponse::getIsInProgress))
+ .containsExactly(false, false)
+ );
+ }
+
+ @DisplayName("이름 기준으로 검색 후 페이지네이션")
+ @Test
+ void searchByName_unAuthorizedCursorPaging() {
+ //given
+ fixture.사이클_생성_NOTHING(알파_ID, 알고리즘_풀기_ID, now);
+ fixture.사이클_생성_NOTHING(토닉_ID, 스모디_방문하기_ID, now);
+ fixture.사이클_생성_NOTHING(더즈_ID, 스모디_방문하기_ID, now);
+
+ // when
+ List challengeTabResponse = challengeApiService.findAllWithChallengerCountByFilter(
+ now, new PagingParams(null, null, 스모디_방문하기_ID, "기"));
+
+ // then
+ assertAll(
+ () -> assertThat(challengeTabResponse).hasSize(1),
+ () -> assertThat(challengeTabResponse.stream().map(ChallengeTabResponse::getChallengeId))
+ .containsExactly(알고리즘_풀기_ID),
+ () -> assertThat(challengeTabResponse.stream().map(ChallengeTabResponse::getChallengerCount))
+ .containsExactly(1),
+ () -> assertThat(challengeTabResponse.stream().map(ChallengeTabResponse::getIsInProgress))
+ .containsExactly(false)
+ );
+ }
+
+ @DisplayName("이름 기준으로 검색하고 마지막 커서로 페이지네이션")
+ @Test
+ void searchByName_unAuthorizedLastCursor() {
+ //given
+ fixture.사이클_생성_NOTHING(알파_ID, 알고리즘_풀기_ID, now);
+ fixture.사이클_생성_NOTHING(토닉_ID, 스모디_방문하기_ID, now);
+ fixture.사이클_생성_NOTHING(더즈_ID, 스모디_방문하기_ID, now);
+
+ // when
+ List challengeTabResponse = challengeApiService.findAllWithChallengerCountByFilter(
+ now, new PagingParams(null, null, 알고리즘_풀기_ID, "기"));
+
+ // then
+ assertThat(challengeTabResponse).isEmpty();
+ }
+
+ @DisplayName("빈 이름 혹은 공백 문자로 검색하는 경우")
+ @ParameterizedTest
+ @ValueSource(strings = {" ", ""})
+ void searchByName_unAuthorizedWithNoName(String invalidSearchingName) {
+ //given
+ fixture.사이클_생성_NOTHING(알파_ID, 알고리즘_풀기_ID, now);
+ fixture.사이클_생성_NOTHING(토닉_ID, 스모디_방문하기_ID, now);
+ fixture.사이클_생성_NOTHING(더즈_ID, 스모디_방문하기_ID, now);
+
+ // when then
+ assertThatThrownBy(() -> challengeApiService.findAllWithChallengerCountByFilter(
+ now, new PagingParams(null, null, 0L, invalidSearchingName)))
+ .isInstanceOf(BusinessException.class)
+ .extracting("exceptionData")
+ .isEqualTo(ExceptionData.INVALID_SEARCH_NAME);
+ }
}
- @DisplayName("참가자 순으로 정렬하여 조회")
- @Test
- void findAllWithChallengerCountByFilter_popular() {
- // given
- fixture.사이클_생성_NOTHING(조조그린_ID, 미라클_모닝_ID, LocalDateTime.now());
- fixture.사이클_생성_NOTHING(더즈_ID, 미라클_모닝_ID, LocalDateTime.now());
- fixture.사이클_생성_NOTHING(토닉_ID, 미라클_모닝_ID, LocalDateTime.now());
- fixture.사이클_생성_NOTHING(알파_ID, 미라클_모닝_ID, LocalDateTime.now());
-
- fixture.사이클_생성_NOTHING(조조그린_ID, 오늘의_운동_ID, LocalDateTime.now());
- fixture.사이클_생성_NOTHING(더즈_ID, 오늘의_운동_ID, LocalDateTime.now());
- fixture.사이클_생성_NOTHING(토닉_ID, 오늘의_운동_ID, LocalDateTime.now());
-
- fixture.사이클_생성_NOTHING(조조그린_ID, 스모디_방문하기_ID, LocalDateTime.now());
- fixture.사이클_생성_NOTHING(더즈_ID, 스모디_방문하기_ID, LocalDateTime.now());
-
- // when
- List actual = challengeApiService.findAllWithChallengerCountByFilter(LocalDateTime.now(),
- new PagingParams("popular", 10));
-
- // then
- assertThat(actual).map(ChallengeTabResponse::getChallengeName)
- .containsExactly("미라클 모닝", "오늘의 운동", "스모디 방문하기");
+ @DisplayName("회원이 챌린지를 도전자 수와 함께 조회 할 때")
+ @Nested
+ class FindAllWithChallengerCountByFilter_Login {
+
+ @BeforeEach
+ void setUp() {
+ // given
+ fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.NOTHING, now); // 진행 중
+ fixture.사이클_생성(더즈_ID, 미라클_모닝_ID, Progress.FIRST, now.minusDays(3L)); // 실패
+ fixture.사이클_생성(토닉_ID, 미라클_모닝_ID, Progress.SUCCESS, now.minusDays(3L)); // 성공
+ fixture.사이클_생성(더즈_ID, 스모디_방문하기_ID, Progress.FIRST, now.minusDays(1L)); // 진행 중
+ fixture.사이클_생성(조조그린_ID, 스모디_방문하기_ID, Progress.NOTHING, now); // 진행 중
+ fixture.사이클_생성(토닉_ID, 스모디_방문하기_ID, Progress.SUCCESS, now.minusDays(3L)); //성공
+ }
+
+ @DisplayName("조회")
+ @Test
+ void findAllWithChallengerCount_sortAuth() {
+ // when
+ TokenPayload tokenPayload = new TokenPayload(조조그린_ID);
+ List challengeResponses = challengeApiService.findAllWithChallengerCountByFilter(
+ tokenPayload, now, new PagingParams(null, null, 0L, null));
+
+ // then
+ assertAll(
+ () -> assertThat(challengeResponses).hasSize(5),
+ () -> assertThat(challengeResponses.stream().mapToLong(ChallengeTabResponse::getChallengeId))
+ .containsExactly(스모디_방문하기_ID, 미라클_모닝_ID, 오늘의_운동_ID, 알고리즘_풀기_ID, JPA_공부_ID),
+ () -> assertThat(challengeResponses.stream().map(ChallengeTabResponse::getIsInProgress))
+ .containsExactly(true, true, false, false, false)
+ );
+ }
+
+ @DisplayName("2개 조회")
+ @Test
+ void findAllWithChallengerCount_pageFullSizeAuth() {
+ // when
+ TokenPayload tokenPayload = new TokenPayload(조조그린_ID);
+ List challengeResponses = challengeApiService.findAllWithChallengerCountByFilter(
+ tokenPayload, now, new PagingParams(null, 2, 0L, null));
+
+ // then
+ assertAll(
+ () -> assertThat(challengeResponses).hasSize(2),
+ () -> assertThat(challengeResponses.stream().map(ChallengeTabResponse::getChallengeId))
+ .containsExactly(스모디_방문하기_ID, 미라클_모닝_ID),
+ () -> assertThat(challengeResponses.stream().mapToInt(ChallengeTabResponse::getChallengerCount))
+ .containsExactly(2, 1),
+ () -> assertThat(challengeResponses.stream().map(ChallengeTabResponse::getIsInProgress))
+ .containsExactly(true, true)
+ );
+ }
+
+ @DisplayName("커서를 기준으로 2개를 조회")
+ @Test
+ void findAllWithChallengerCount_pagePartialSizeAuth() {
+ // when
+ TokenPayload tokenPayload = new TokenPayload(조조그린_ID);
+ List challengeResponses = challengeApiService.findAllWithChallengerCountByFilter(
+ tokenPayload, now, new PagingParams(null, 2, 미라클_모닝_ID, null));
+
+ // then
+ assertAll(
+ () -> assertThat(challengeResponses).hasSize(2),
+ () -> assertThat(challengeResponses.stream().mapToInt(ChallengeTabResponse::getChallengerCount))
+ .containsExactly(0, 0),
+ () -> assertThat(challengeResponses.stream().mapToLong(ChallengeTabResponse::getChallengeId))
+ .containsExactly(오늘의_운동_ID, 알고리즘_풀기_ID),
+ () -> assertThat(challengeResponses.stream().map(ChallengeTabResponse::getIsInProgress))
+ .containsExactly(false, false)
+ );
+ }
+
+ @DisplayName("데이터의 마지막이 커서이면서 3개 조회")
+ @Test
+ void findAllWithChallengerCount_pageOverMaxPageAuth() {
+ // when
+ TokenPayload tokenPayload = new TokenPayload(조조그린_ID);
+ List challengeResponses = challengeApiService.findAllWithChallengerCountByFilter(
+ tokenPayload, now, new PagingParams(null, 3, JPA_공부_ID, null));
+
+ // then
+ assertThat(challengeResponses).isEmpty();
+ }
+
+ @DisplayName("이름 기준으로 검색 후 페이지네이션")
+ @Test
+ void searchByName_authorizedCursorPaging() {
+ //given
+ fixture.사이클_생성_NOTHING(알파_ID, 알고리즘_풀기_ID, now);
+ fixture.사이클_생성_NOTHING(토닉_ID, 스모디_방문하기_ID, now);
+ fixture.사이클_생성_NOTHING(더즈_ID, 스모디_방문하기_ID, now);
+ TokenPayload tokenPayload = new TokenPayload(알파_ID);
+
+ // when
+ List challengeTabResponse = challengeApiService.findAllWithChallengerCountByFilter(
+ tokenPayload, now, new PagingParams(null, null, 스모디_방문하기_ID, "기"));
+
+ // then
+ assertAll(
+ () -> assertThat(challengeTabResponse).hasSize(1),
+ () -> assertThat(challengeTabResponse.stream().map(ChallengeTabResponse::getChallengeId))
+ .containsExactly(알고리즘_풀기_ID),
+ () -> assertThat(challengeTabResponse.stream().map(ChallengeTabResponse::getChallengerCount))
+ .containsExactly(1),
+ () -> assertThat(challengeTabResponse.stream().map(ChallengeTabResponse::getIsInProgress))
+ .containsExactly(true)
+ );
+ }
+
+ @DisplayName("이름 기준으로 검색하고 마지막 커서로 페이지네이션")
+ @Test
+ void searchByName_authorizedLastCursor() {
+ //given
+ fixture.사이클_생성_NOTHING(알파_ID, 알고리즘_풀기_ID, now);
+ fixture.사이클_생성_NOTHING(토닉_ID, 스모디_방문하기_ID, now);
+ fixture.사이클_생성_NOTHING(더즈_ID, 스모디_방문하기_ID, now);
+ TokenPayload tokenPayload = new TokenPayload(알파_ID);
+
+ // when
+ List challengeTabResponse = challengeApiService.findAllWithChallengerCountByFilter(
+ tokenPayload, now, new PagingParams(null, null, 알고리즘_풀기_ID, "기"));
+
+ // then
+ assertThat(challengeTabResponse).isEmpty();
+ }
+
+ @DisplayName("빈 이름 혹은 공백 문자로 검색하는 경우")
+ @ParameterizedTest
+ @ValueSource(strings = {" ", ""})
+ void searchByName_authorizedWithNoName(String invalidSearchingName) {
+ //given
+ fixture.사이클_생성_NOTHING(알파_ID, 알고리즘_풀기_ID, now);
+ fixture.사이클_생성_NOTHING(토닉_ID, 스모디_방문하기_ID, now);
+ fixture.사이클_생성_NOTHING(더즈_ID, 스모디_방문하기_ID, now);
+ TokenPayload tokenPayload = new TokenPayload(알파_ID);
+
+ // when then
+ assertThatThrownBy(() -> challengeApiService.findAllWithChallengerCountByFilter(
+ tokenPayload, now, new PagingParams(null, null, 0L, invalidSearchingName)))
+ .isInstanceOf(BusinessException.class)
+ .extracting("exceptionData")
+ .isEqualTo(ExceptionData.INVALID_SEARCH_NAME);
+ }
}
- @DisplayName("참가자 순으로 size에 맞게 조회")
- @Test
- void findAllWithChallengerCountByFilter_popular_size() {
- // given
- fixture.사이클_생성_NOTHING(조조그린_ID, 미라클_모닝_ID, LocalDateTime.now());
- fixture.사이클_생성_NOTHING(더즈_ID, 미라클_모닝_ID, LocalDateTime.now());
- fixture.사이클_생성_NOTHING(토닉_ID, 미라클_모닝_ID, LocalDateTime.now());
- fixture.사이클_생성_NOTHING(알파_ID, 미라클_모닝_ID, LocalDateTime.now());
-
- fixture.사이클_생성_NOTHING(조조그린_ID, 오늘의_운동_ID, LocalDateTime.now());
- fixture.사이클_생성_NOTHING(더즈_ID, 오늘의_운동_ID, LocalDateTime.now());
- fixture.사이클_생성_NOTHING(토닉_ID, 오늘의_운동_ID, LocalDateTime.now());
-
- fixture.사이클_생성_NOTHING(조조그린_ID, 스모디_방문하기_ID, LocalDateTime.now());
- fixture.사이클_생성_NOTHING(더즈_ID, 스모디_방문하기_ID, LocalDateTime.now());
-
- // when
- List actual = challengeApiService.findAllWithChallengerCountByFilter(LocalDateTime.now(),
- new PagingParams("popular", 2));
-
- // then
- assertThat(actual).map(ChallengeTabResponse::getChallengeName)
- .containsExactly("미라클 모닝", "오늘의 운동");
+ @DisplayName("나의 특정 챌린지에 대한 사이클 성공 횟수, 인증 횟수를 조회 할 때")
+ @Nested
+ class FindByMeAndChallenge {
+
+ @DisplayName("조회")
+ @Test
+ void findByMeAndChallenge() {
+ // given
+ fixture.사이클_생성_SUCCESS(조조그린_ID, 미라클_모닝_ID, LocalDateTime.now());
+ fixture.사이클_생성_SECOND(조조그린_ID, 미라클_모닝_ID, LocalDateTime.now());
+ fixture.사이클_생성_FIRST(조조그린_ID, 미라클_모닝_ID, LocalDateTime.now());
+ fixture.사이클_생성_NOTHING(조조그린_ID, 미라클_모닝_ID, LocalDateTime.now());
+
+ // when
+ ChallengeHistoryResponse challengeHistoryResponse =
+ challengeApiService.findByMeAndChallenge(new TokenPayload(조조그린_ID), 미라클_모닝_ID);
+
+ // then
+ assertAll(
+ () -> assertThat(challengeHistoryResponse.getChallengeName()).isEqualTo("미라클 모닝"),
+ () -> assertThat(challengeHistoryResponse.getSuccessCount()).isEqualTo(1),
+ () -> assertThat(challengeHistoryResponse.getCycleDetailCount()).isEqualTo(6)
+ );
+ }
+
+ @DisplayName("회원이 참가한 챌린지 하나를 조회하는 경우")
+ @Test
+ void findOneWithMine() {
+ //given
+ fixture.사이클_생성_FIRST(알파_ID, 알고리즘_풀기_ID, now);
+ fixture.사이클_생성_SUCCESS(알파_ID, 알고리즘_풀기_ID, now.minusDays(3L));
+ fixture.사이클_생성_SUCCESS(알파_ID, 알고리즘_풀기_ID, now.minusDays(6L));
+ fixture.사이클_생성_SECOND(알파_ID, 알고리즘_풀기_ID, now.minusDays(9L));
+
+ TokenPayload tokenPayload = new TokenPayload(알파_ID);
+
+ // when
+ ChallengeHistoryResponse challengeHistoryResponse = challengeApiService.findByMeAndChallenge(
+ tokenPayload, 알고리즘_풀기_ID);
+
+ // then
+ assertAll(
+ () -> assertThat(challengeHistoryResponse.getChallengeName()).isEqualTo("알고리즘 풀기"),
+ () -> assertThat(challengeHistoryResponse.getSuccessCount()).isEqualTo(2),
+ () -> assertThat(challengeHistoryResponse.getCycleDetailCount()).isEqualTo(9)
+ );
+ }
+
+ @DisplayName("회원이 참가하지 않은 챌린지를 조회하는 경우 예외를 던진다.")
+ @Test
+ void findOneWithMine_notParticipate() {
+ //given
+ fixture.사이클_생성_NOTHING(알파_ID, 알고리즘_풀기_ID, now);
+ fixture.사이클_생성_SUCCESS(알파_ID, 알고리즘_풀기_ID, now.minusDays(3L));
+ fixture.사이클_생성_SUCCESS(알파_ID, 알고리즘_풀기_ID, now.minusDays(6L));
+ fixture.사이클_생성_SECOND(알파_ID, 알고리즘_풀기_ID, now.minusDays(9L));
+
+ TokenPayload tokenPayload = new TokenPayload(알파_ID);
+
+ // when, then
+ assertThatThrownBy(() -> challengeApiService.findByMeAndChallenge(tokenPayload, 오늘의_운동_ID))
+ .isInstanceOf(BusinessException.class)
+ .extracting("ExceptionData")
+ .isEqualTo(ExceptionData.DATA_INTEGRITY_ERROR);
+ }
}
- @DisplayName("참가자 순으로 조회할 때 참가자가 없는 챌린지는 조회하지 않는다.")
- @Test
- void findAllWithChallengerCountByFilter_popular_notExistEmptyChallengers() {
- // given
- Challenge 미라클_모닝 = fixture.챌린지_조회(미라클_모닝_ID);
- Challenge 오늘의_운동 = fixture.챌린지_조회(오늘의_운동_ID);
- Challenge 스모디_방문하기 = fixture.챌린지_조회(스모디_방문하기_ID);
- Challenge 알고리즘_풀기 = fixture.챌린지_조회(알고리즘_풀기_ID);
- Challenge JPA_공부 = fixture.챌린지_조회(JPA_공부_ID);
-
- fixture.사이클_생성_NOTHING(조조그린_ID, 미라클_모닝.getId(), LocalDateTime.now());
- fixture.사이클_생성_NOTHING(더즈_ID, 미라클_모닝.getId(), LocalDateTime.now());
- fixture.사이클_생성_NOTHING(토닉_ID, 미라클_모닝.getId(), LocalDateTime.now());
- fixture.사이클_생성_NOTHING(알파_ID, 미라클_모닝.getId(), LocalDateTime.now());
-
- fixture.사이클_생성_NOTHING(조조그린_ID, 오늘의_운동.getId(), LocalDateTime.now());
- fixture.사이클_생성_NOTHING(더즈_ID, 오늘의_운동.getId(), LocalDateTime.now());
- fixture.사이클_생성_NOTHING(토닉_ID, 오늘의_운동.getId(), LocalDateTime.now());
-
- fixture.사이클_생성_NOTHING(조조그린_ID, 스모디_방문하기.getId(), LocalDateTime.now());
- fixture.사이클_생성_NOTHING(더즈_ID, 스모디_방문하기.getId(), LocalDateTime.now());
-
- // when
- List actual = challengeApiService.findAllWithChallengerCountByFilter(LocalDateTime.now(),
- new PagingParams("popular", 5));
-
- // then
- assertThat(actual).map(ChallengeTabResponse::getChallengeName)
- .containsExactly("미라클 모닝", "오늘의 운동", "스모디 방문하기");
+ @DisplayName("나의 모든 챌린지를 조회할 때")
+ @Nested
+ class FindAllByMeAndFilter {
+
+ private TokenPayload tokenPayload;
+
+ @BeforeEach
+ void setUp() {
+ tokenPayload = new TokenPayload(조조그린_ID);
+
+ fixture.사이클_생성(조조그린_ID, 스모디_방문하기_ID, Progress.NOTHING, now);
+ fixture.사이클_생성(조조그린_ID, 스모디_방문하기_ID, Progress.FIRST, now.minusDays(3L));
+
+ fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.NOTHING, now.minusSeconds(1L));
+ fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.FIRST, now.minusDays(1L));
+ fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.SUCCESS, now.minusDays(3L));
+ fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.SUCCESS, now.minusDays(6L));
+
+ fixture.사이클_생성(조조그린_ID, 오늘의_운동_ID, Progress.SUCCESS, now.minusDays(9L));
+
+ fixture.사이클_생성(조조그린_ID, JPA_공부_ID, Progress.SUCCESS, now.minusDays(20L));
+ fixture.사이클_생성(조조그린_ID, JPA_공부_ID, Progress.SUCCESS, now.minusDays(23L));
+ fixture.사이클_생성(조조그린_ID, JPA_공부_ID, Progress.SUCCESS, now.minusDays(26L));
+
+ fixture.사이클_생성(조조그린_ID, 알고리즘_풀기_ID, Progress.SUCCESS, now.minusDays(30L));
+ }
+
+ @DisplayName("조회")
+ @Test
+ void searchOfMine() {
+ // when
+ List responses = challengeApiService.findAllByMeAndFilter(
+ tokenPayload, new PagingParams(null, null, 0L, null));
+
+ // then
+ assertAll(
+ () -> assertThat(responses).hasSize(5),
+ () -> assertThat(responses)
+ .map(ChallengeOfMineResponse::getSuccessCount)
+ .containsExactly(0, 2, 1, 3, 1),
+ () -> assertThat(responses)
+ .map(ChallengeOfMineResponse::getChallengeId)
+ .containsExactly(스모디_방문하기_ID, 미라클_모닝_ID, 오늘의_운동_ID, JPA_공부_ID, 알고리즘_풀기_ID)
+ );
+ }
+
+ @DisplayName("0페이지의 3개만 조회")
+ @Test
+ void searchOfMine_pageFullSize() {
+ // when
+ List responses = challengeApiService.findAllByMeAndFilter(
+ tokenPayload, new PagingParams(null, 3, 0L, null));
+
+ // then
+ assertAll(
+ () -> assertThat(responses).hasSize(3),
+ () -> assertThat(responses)
+ .map(ChallengeOfMineResponse::getSuccessCount)
+ .containsExactly(0, 2, 1),
+ () -> assertThat(responses)
+ .map(ChallengeOfMineResponse::getChallengeId)
+ .containsExactly(스모디_방문하기_ID, 미라클_모닝_ID, 오늘의_운동_ID)
+ );
+ }
+
+ @DisplayName("1페이지의 2개만 조회")
+ @Test
+ void searchOfMine_pagePartialSize() {
+ // when
+ List responses = challengeApiService.findAllByMeAndFilter(
+ tokenPayload, new PagingParams(null, 2, 미라클_모닝_ID, null));
+
+ // then
+ assertAll(
+ () -> assertThat(responses).hasSize(2),
+ () -> assertThat(responses)
+ .map(ChallengeOfMineResponse::getSuccessCount)
+ .containsExactly(1, 3),
+ () -> assertThat(responses)
+ .map(ChallengeOfMineResponse::getChallengeId)
+ .containsExactly(오늘의_운동_ID, JPA_공부_ID)
+ );
+ }
+
+ @DisplayName("없는 페이지로 조회")
+ @Test
+ void searchOfMine_pageOverMaxPage() {
+ // when
+ List responses = challengeApiService.findAllByMeAndFilter(
+ tokenPayload, new PagingParams(null, 3, 알고리즘_풀기_ID, null));
+
+ // then
+ assertThat(responses).isEmpty();
+ }
+
+ @DisplayName("성공한 챌린지들만 조회")
+ @Test
+ void searchSuccessOfMine() {
+ // when
+ List responses = challengeApiService.findAllByMeAndFilter(
+ tokenPayload, new PagingParams(null, null, 0L, "success"));
+
+ // then
+ assertAll(
+ () -> assertThat(responses).hasSize(4),
+ () -> assertThat(responses)
+ .map(ChallengeOfMineResponse::getSuccessCount)
+ .containsExactly(2, 1, 3, 1),
+ () -> assertThat(responses)
+ .map(ChallengeOfMineResponse::getChallengeId)
+ .containsExactly(미라클_모닝_ID, 오늘의_운동_ID, JPA_공부_ID, 알고리즘_풀기_ID)
+ );
+ }
+
+ @DisplayName("내가 성공한 챌린지들의 0페이지의 3개만 조회")
+ @Test
+ void searchSuccessOfMine_pageFullSize() {
+ // when
+ List responses = challengeApiService.findAllByMeAndFilter(
+ tokenPayload, new PagingParams(null, 3, 0L, "success"));
+
+ // then
+ assertAll(
+ () -> assertThat(responses).hasSize(3),
+ () -> assertThat(responses)
+ .map(ChallengeOfMineResponse::getSuccessCount)
+ .containsExactly(2, 1, 3),
+ () -> assertThat(responses)
+ .map(ChallengeOfMineResponse::getChallengeId)
+ .containsExactly(미라클_모닝_ID, 오늘의_운동_ID, JPA_공부_ID)
+ );
+ }
+
+ @DisplayName("내가 성공한 챌린지들의 1페이지의 2개만 조회")
+ @Test
+ void searchSuccessOfMine_pagePartialSize() {
+ // when
+ List responses = challengeApiService.findAllByMeAndFilter(
+ tokenPayload, new PagingParams(null, 2, 오늘의_운동_ID, "success"));
+
+ // then
+ assertAll(
+ () -> assertThat(responses).hasSize(2),
+ () -> assertThat(responses)
+ .map(ChallengeOfMineResponse::getSuccessCount)
+ .containsExactly(3, 1),
+ () -> assertThat(responses)
+ .map(ChallengeOfMineResponse::getChallengeId)
+ .containsExactly(JPA_공부_ID, 알고리즘_풀기_ID)
+ );
+ }
+
+ @DisplayName("내가 성공한 챌린지들의 없는 페이지로 조회")
+ @Test
+ void searchSuccessOfMine_pageOverMaxPage() {
+ // when
+ List responses = challengeApiService.findAllByMeAndFilter(
+ tokenPayload, new PagingParams(null, 3, 알고리즘_풀기_ID, "success"));
+
+ // then
+ assertThat(responses).isEmpty();
+ }
+
+ @DisplayName("같은 인증 시간이라면 id순으로 정렬")
+ @Test
+ void searchOfMine_sameTime() {
+ // given
+ TokenPayload tokenPayload = new TokenPayload(조조그린_ID);
+
+ // when
+ List responses = challengeApiService.findAllByMeAndFilter(
+ tokenPayload, new PagingParams(null, null, 0L, null));
+
+ // then
+ assertAll(
+ () -> assertThat(responses).hasSize(5),
+ () -> assertThat(responses)
+ .map(ChallengeOfMineResponse::getSuccessCount)
+ .containsExactly(0, 2, 1, 3, 1),
+ () -> assertThat(responses)
+ .map(ChallengeOfMineResponse::getChallengeId)
+ .containsExactly(스모디_방문하기_ID, 미라클_모닝_ID, 오늘의_운동_ID, JPA_공부_ID, 알고리즘_풀기_ID)
+ );
+ }
+
+ @DisplayName("미래에 시작하는 사이클도 조회한다.")
+ @Test
+ void searchOfMine_retry() {
+ // given
+ fixture.사이클_생성(조조그린_ID, JPA_공부_ID, Progress.NOTHING, now.plusDays(1L));
+ TokenPayload tokenPayload = new TokenPayload(조조그린_ID);
+
+ // when
+ List responses = challengeApiService.findAllByMeAndFilter(
+ tokenPayload, new PagingParams(null, null, 0L, null));
+
+ // then
+ assertAll(
+ () -> assertThat(responses).hasSize(5),
+ () -> assertThat(responses)
+ .map(ChallengeOfMineResponse::getSuccessCount)
+ .containsExactly(3, 0, 2, 1, 1),
+ () -> assertThat(responses)
+ .map(ChallengeOfMineResponse::getChallengeId)
+ .containsExactly(JPA_공부_ID, 스모디_방문하기_ID, 미라클_모닝_ID, 오늘의_운동_ID, 알고리즘_풀기_ID)
+ );
+ }
+ }
+
+ @DisplayName("하나의 챌린지를 상세 조회할 때")
+ @Nested
+ class FindWithChallengerCount {
+
+ @DisplayName("비회원이 조회")
+ @Test
+ void findOneWithChallengerCount() {
+ // given
+ fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.NOTHING, now);
+ fixture.사이클_생성(더즈_ID, 미라클_모닝_ID, Progress.FIRST, now.minusDays(1L));
+ fixture.사이클_생성(토닉_ID, 미라클_모닝_ID, Progress.SUCCESS, now.minusDays(3L));
+
+ // when
+ ChallengeResponse challengeResponse = challengeApiService.findWithChallengerCount(now, 미라클_모닝_ID);
+
+ // then
+ assertAll(
+ () -> assertThat(challengeResponse.getChallengerCount()).isEqualTo(2),
+ () -> assertThat(challengeResponse.getChallengeId()).isEqualTo(미라클_모닝_ID),
+ () -> assertThat(challengeResponse.getIsInProgress()).isFalse()
+ );
+ }
+
+ @DisplayName("회원이 조회")
+ @Test
+ void findOneWithChallengerCount_auth() {
+ // given
+ TokenPayload tokenPayload = new TokenPayload(조조그린_ID);
+ fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.NOTHING, now);
+ fixture.사이클_생성(더즈_ID, 미라클_모닝_ID, Progress.FIRST, now.minusDays(1L));
+ fixture.사이클_생성(토닉_ID, 미라클_모닝_ID, Progress.SUCCESS, now.minusDays(3L));
+
+ // when
+ ChallengeResponse challengeResponse = challengeApiService
+ .findWithChallengerCount(tokenPayload, now, 미라클_모닝_ID);
+
+ // then
+ assertAll(
+ () -> assertThat(challengeResponse.getChallengerCount()).isEqualTo(2),
+ () -> assertThat(challengeResponse.getChallengeId()).isEqualTo(미라클_모닝_ID),
+ () -> assertThat(challengeResponse.getIsInProgress()).isTrue()
+ );
+ }
+ }
+
+ @DisplayName("챌린지의 참여자를 조회할 때")
+ @Nested
+ class FindAllChallengers {
+
+ @DisplayName("챌린지의 참여자를 조회")
+ @Test
+ void findAllChallenger() {
+ // given
+ Member member1 = fixture.회원_조회(조조그린_ID);
+ Member member2 = fixture.회원_조회(더즈_ID);
+ fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.NOTHING, now);
+ fixture.사이클_생성(더즈_ID, 미라클_모닝_ID, Progress.FIRST, now.minusDays(1L));
+ fixture.사이클_생성(토닉_ID, 오늘의_운동_ID, Progress.SUCCESS, now.minusDays(1L));
+
+ // when
+ List challengersResponse = challengeApiService
+ .findAllChallengers(미라클_모닝_ID);
+
+ // then
+ assertAll(
+ () -> assertThat(challengersResponse).hasSize(2),
+ () -> assertThat(challengersResponse.stream().map(ChallengersResponse::getMemberId))
+ .containsExactly(member1.getId(), member2.getId()),
+ () -> assertThat(challengersResponse.stream().map(ChallengersResponse::getNickname))
+ .containsExactly(member1.getNickname(), member2.getNickname()),
+ () -> assertThat(challengersResponse.stream().map(ChallengersResponse::getProgressCount))
+ .containsExactly(0, 1),
+ () -> assertThat(challengersResponse.stream().map(ChallengersResponse::getPicture))
+ .containsExactly(member1.getPicture(), member2.getPicture()),
+ () -> assertThat(challengersResponse.stream().map(ChallengersResponse::getIntroduction))
+ .containsExactly(member1.getIntroduction(), member2.getIntroduction())
+ );
+ }
+
+ @DisplayName("아무도 진행하지 않는 챌린지의 참여자를 조회할 때")
+ @Test
+ void findAllChallenger_noInprogress() {
+ // given
+ fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.NOTHING, now);
+ fixture.사이클_생성(더즈_ID, 미라클_모닝_ID, Progress.FIRST, now.minusDays(1L));
+ fixture.사이클_생성(토닉_ID, 오늘의_운동_ID, Progress.SUCCESS, now.minusDays(1L));
+
+ // when
+ List challengersResponse = challengeApiService
+ .findAllChallengers(알고리즘_풀기_ID);
+
+ // then
+ assertThat(challengersResponse).isEmpty();
+ }
+
+ @DisplayName("존재하지 않는 챌린지의 참여자를 조회할 때")
+ @Test
+ void findAllChallenger_notExists() {
+ // given
+ fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.NOTHING, now);
+ fixture.사이클_생성(더즈_ID, 미라클_모닝_ID, Progress.FIRST, now.minusDays(1L));
+ fixture.사이클_생성(토닉_ID, 오늘의_운동_ID, Progress.SUCCESS, now.minusDays(1L));
+
+ // when then
+ assertThatThrownBy(() -> challengeApiService.findAllChallengers(100L))
+ .isInstanceOf(BusinessException.class)
+ .extracting("exceptionData")
+ .isEqualTo(ExceptionData.NOT_FOUND_CHALLENGE);
+ }
}
}
diff --git a/backend/smody/src/test/java/com/woowacourse/smody/challenge/service/ChallengeServiceTest.java b/backend/smody/src/test/java/com/woowacourse/smody/challenge/service/ChallengeServiceTest.java
index 6120c3b2e..2f6c5930b 100644
--- a/backend/smody/src/test/java/com/woowacourse/smody/challenge/service/ChallengeServiceTest.java
+++ b/backend/smody/src/test/java/com/woowacourse/smody/challenge/service/ChallengeServiceTest.java
@@ -5,7 +5,6 @@
import static com.woowacourse.smody.support.ResourceFixture.미라클_모닝_ID;
import static com.woowacourse.smody.support.ResourceFixture.스모디_방문하기_ID;
import static com.woowacourse.smody.support.ResourceFixture.알고리즘_풀기_ID;
-import static com.woowacourse.smody.support.ResourceFixture.알파_ID;
import static com.woowacourse.smody.support.ResourceFixture.오늘의_운동_ID;
import static com.woowacourse.smody.support.ResourceFixture.조조그린_ID;
import static com.woowacourse.smody.support.ResourceFixture.토닉_ID;
@@ -13,20 +12,15 @@
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static org.junit.jupiter.api.Assertions.assertAll;
-import com.woowacourse.smody.auth.dto.TokenPayload;
-import com.woowacourse.smody.challenge.dto.ChallengeHistoryResponse;
-import com.woowacourse.smody.challenge.dto.ChallengeOfMineResponse;
-import com.woowacourse.smody.challenge.dto.ChallengeResponse;
-import com.woowacourse.smody.challenge.dto.ChallengeTabResponse;
-import com.woowacourse.smody.challenge.dto.ChallengersResponse;
+import com.woowacourse.smody.challenge.domain.Challenge;
import com.woowacourse.smody.cycle.domain.Progress;
import com.woowacourse.smody.db_support.PagingParams;
import com.woowacourse.smody.exception.BusinessException;
import com.woowacourse.smody.exception.ExceptionData;
-import com.woowacourse.smody.member.domain.Member;
import com.woowacourse.smody.support.IntegrationTest;
import java.time.LocalDateTime;
import java.util.List;
+import java.util.Optional;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.Nested;
@@ -37,233 +31,64 @@
class ChallengeServiceTest extends IntegrationTest {
- @Autowired
- private ChallengeApiService challengeApiService;
-
@Autowired
private ChallengeService challengeService;
private final LocalDateTime now = LocalDateTime.now();
- @DisplayName("나의 모든 챌린지 중 가장 최근에 성공한 순으로 챌린지를")
+ @DisplayName("id로 챌린지를 조회할 때")
@Nested
- class SearchSuccessOfMineTest {
-
- private TokenPayload tokenPayload;
-
- @BeforeEach
- void setUp() {
- tokenPayload = new TokenPayload(조조그린_ID);
-
- fixture.사이클_생성(조조그린_ID, 스모디_방문하기_ID, Progress.NOTHING, now);
- fixture.사이클_생성(조조그린_ID, 스모디_방문하기_ID, Progress.FIRST, now.minusDays(3L));
-
- fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.NOTHING, now.minusSeconds(1L));
- fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.FIRST, now.minusDays(1L));
- fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.SUCCESS, now.minusDays(3L));
- fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.SUCCESS, now.minusDays(6L));
-
- fixture.사이클_생성(조조그린_ID, 오늘의_운동_ID, Progress.SUCCESS, now.minusDays(9L));
-
- fixture.사이클_생성(조조그린_ID, JPA_공부_ID, Progress.SUCCESS, now.minusDays(20L));
- fixture.사이클_생성(조조그린_ID, JPA_공부_ID, Progress.SUCCESS, now.minusDays(23L));
- fixture.사이클_생성(조조그린_ID, JPA_공부_ID, Progress.SUCCESS, now.minusDays(26L));
-
- fixture.사이클_생성(조조그린_ID, 알고리즘_풀기_ID, Progress.SUCCESS, now.minusDays(30L));
- }
-
- @DisplayName("조회")
- @Test
- void searchOfMine() {
- // when
- List responses = challengeApiService.findAllByMeAndFilter(
- tokenPayload, new PagingParams(null, null, 0L, null));
-
- // then
- assertAll(
- () -> assertThat(responses).hasSize(5),
- () -> assertThat(responses)
- .map(ChallengeOfMineResponse::getSuccessCount)
- .containsExactly(0, 2, 1, 3, 1),
- () -> assertThat(responses)
- .map(ChallengeOfMineResponse::getChallengeId)
- .containsExactly(스모디_방문하기_ID, 미라클_모닝_ID, 오늘의_운동_ID, JPA_공부_ID, 알고리즘_풀기_ID)
- );
- }
+ class Search {
- @DisplayName("0페이지의 3개만 조회")
+ @DisplayName("성공")
@Test
- void searchOfMine_pageFullSize() {
+ void success() {
// when
- List responses = challengeApiService.findAllByMeAndFilter(
- tokenPayload, new PagingParams(null, 3, 0L, null));
+ Challenge actual = challengeService.search(스모디_방문하기_ID);
// then
- assertAll(
- () -> assertThat(responses).hasSize(3),
- () -> assertThat(responses)
- .map(ChallengeOfMineResponse::getSuccessCount)
- .containsExactly(0, 2, 1),
- () -> assertThat(responses)
- .map(ChallengeOfMineResponse::getChallengeId)
- .containsExactly(스모디_방문하기_ID, 미라클_모닝_ID, 오늘의_운동_ID)
- );
+ assertThat(actual.getName()).isEqualTo("스모디 방문하기");
}
- @DisplayName("1페이지의 2개만 조회")
+ @DisplayName("없는 경우 예외를 발생시킨다.")
@Test
- void searchOfMine_pagePartialSize() {
- // when
- List responses = challengeApiService.findAllByMeAndFilter(
- tokenPayload, new PagingParams(null, 2, 미라클_모닝_ID, null));
-
+ void notFound_exception() {
// then
- assertAll(
- () -> assertThat(responses).hasSize(2),
- () -> assertThat(responses)
- .map(ChallengeOfMineResponse::getSuccessCount)
- .containsExactly(1, 3),
- () -> assertThat(responses)
- .map(ChallengeOfMineResponse::getChallengeId)
- .containsExactly(오늘의_운동_ID, JPA_공부_ID)
- );
+ assertThatThrownBy(() -> challengeService.search(0L))
+ .isInstanceOf(BusinessException.class)
+ .extracting("ExceptionData")
+ .isEqualTo(ExceptionData.NOT_FOUND_CHALLENGE);
}
+ }
- @DisplayName("없는 페이지로 조회")
- @Test
- void searchOfMine_pageOverMaxPage() {
- // when
- List responses = challengeApiService.findAllByMeAndFilter(
- tokenPayload, new PagingParams(null, 3, 알고리즘_풀기_ID, null));
-
- // then
- assertThat(responses).isEmpty();
- }
-
- @DisplayName("성공한 챌린지들만 조회")
- @Test
- void searchSuccessOfMine() {
- // when
- List responses = challengeApiService.findAllByMeAndFilter(
- tokenPayload, new PagingParams(null, null, 0L, "success"));
-
- // then
- assertAll(
- () -> assertThat(responses).hasSize(4),
- () -> assertThat(responses)
- .map(ChallengeOfMineResponse::getSuccessCount)
- .containsExactly(2, 1, 3, 1),
- () -> assertThat(responses)
- .map(ChallengeOfMineResponse::getChallengeId)
- .containsExactly(미라클_모닝_ID, 오늘의_운동_ID, JPA_공부_ID, 알고리즘_풀기_ID)
- );
- }
-
- @DisplayName("내가 성공한 챌린지들의 0페이지의 3개만 조회")
- @Test
- void searchSuccessOfMine_pageFullSize() {
- // when
- List responses = challengeApiService.findAllByMeAndFilter(
- tokenPayload, new PagingParams(null, 3, 0L, "success"));
-
- // then
- assertAll(
- () -> assertThat(responses).hasSize(3),
- () -> assertThat(responses)
- .map(ChallengeOfMineResponse::getSuccessCount)
- .containsExactly(2, 1, 3),
- () -> assertThat(responses)
- .map(ChallengeOfMineResponse::getChallengeId)
- .containsExactly(미라클_모닝_ID, 오늘의_운동_ID, JPA_공부_ID)
- );
- }
+ @DisplayName("id로 챌린지를 조회할 때")
+ @Nested
+ class FindById {
- @DisplayName("내가 성공한 챌린지들의 1페이지의 2개만 조회")
+ @DisplayName("찾으면 Optional에 담아서 반환한다.")
@Test
- void searchSuccessOfMine_pagePartialSize() {
+ void success() {
// when
- List responses = challengeApiService.findAllByMeAndFilter(
- tokenPayload, new PagingParams(null, 2, 오늘의_운동_ID, "success"));
+ Optional actual = challengeService.findById(스모디_방문하기_ID);
// then
- assertAll(
- () -> assertThat(responses).hasSize(2),
- () -> assertThat(responses)
- .map(ChallengeOfMineResponse::getSuccessCount)
- .containsExactly(3, 1),
- () -> assertThat(responses)
- .map(ChallengeOfMineResponse::getChallengeId)
- .containsExactly(JPA_공부_ID, 알고리즘_풀기_ID)
- );
+ assertThat(actual).isPresent();
}
- @DisplayName("내가 성공한 챌린지들의 없는 페이지로 조회")
+ @DisplayName("못찾으면 Option.empty()를 반환한다.")
@Test
- void searchSuccessOfMine_pageOverMaxPage() {
+ void notFound() {
// when
- List responses = challengeApiService.findAllByMeAndFilter(
- tokenPayload, new PagingParams(null, 3, 알고리즘_풀기_ID, "success"));
+ Optional actual = challengeService.findById(0L);
// then
- assertThat(responses).isEmpty();
+ assertThat(actual).isEmpty();
}
}
- @DisplayName("내 전체 참여 챌린지 조회에서 같은 인증 시간이라면 id순으로 정렬")
- @Test
- void searchOfMine_sameTime() {
- // given
- fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.NOTHING, now.minusDays(2L));
- fixture.사이클_생성(조조그린_ID, 오늘의_운동_ID, Progress.NOTHING, now.minusDays(2L));
- fixture.사이클_생성(조조그린_ID, 스모디_방문하기_ID, Progress.SECOND, now.minusDays(2L));
- fixture.사이클_생성(조조그린_ID, 스모디_방문하기_ID, Progress.SUCCESS, now.minusDays(7L));
- TokenPayload tokenPayload = new TokenPayload(조조그린_ID);
-
- List responses = challengeApiService.findAllByMeAndFilter(
- tokenPayload, new PagingParams(null, null, 0L, null));
-
- // then
- assertAll(
- () -> assertThat(responses).hasSize(3),
- () -> assertThat(responses)
- .map(ChallengeOfMineResponse::getSuccessCount)
- .containsExactly(1, 0, 0),
- () -> assertThat(responses)
- .map(ChallengeOfMineResponse::getChallengeId)
- .containsExactly(스모디_방문하기_ID, 미라클_모닝_ID, 오늘의_운동_ID)
- );
-
- }
-
- @DisplayName("내 전체 참여 챌린지 조회에서 미래에 시작하는 사이클도 조회한다.")
- @Test
- void searchOfMine_retry() {
- // given
- fixture.사이클_생성(조조그린_ID, 미라클_모닝_ID, Progress.NOTHING, now.minusDays(2L));
- fixture.사이클_생성(조조그린_ID, 오늘의_운동_ID, Progress.NOTHING, now.minusDays(2L));
- fixture.사이클_생성(조조그린_ID, 스모디_방문하기_ID, Progress.SECOND, now.minusDays(2L));
- fixture.사이클_생성(조조그린_ID, 스모디_방문하기_ID, Progress.SUCCESS, now.minusDays(7L));
- fixture.사이클_생성(조조그린_ID, JPA_공부_ID, Progress.NOTHING, now.plusDays(1L));
- TokenPayload tokenPayload = new TokenPayload(조조그린_ID);
-
- List responses = challengeApiService.findAllByMeAndFilter(
- tokenPayload, new PagingParams(null, null, 0L, null));
-
- // then
- assertAll(
- () -> assertThat(responses).hasSize(4),
- () -> assertThat(responses)
- .map(ChallengeOfMineResponse::getSuccessCount)
- .containsExactly(0, 1, 0, 0),
- () -> assertThat(responses)
- .map(ChallengeOfMineResponse::getChallengeId)
- .containsExactly(JPA_공부_ID, 스모디_방문하기_ID, 미라클_모닝_ID, 오늘의_운동_ID)
- );
- }
-
- @DisplayName("모든 챌린지를 ")
+ @DisplayName("챌린지를 조회할 때")
@Nested
- class FindAllWithChallengerCountSortTest {
+ class FindAllByFilter {
@BeforeEach
void setUp() {
@@ -280,16 +105,13 @@ void setUp() {
@Test
void findAllWithChallengerCount_sort() {
// when
- List challengeResponses = challengeApiService.findAllWithChallengerCountByFilter(
- now, new PagingParams(null, null, 0L, null));
+ List challenges = challengeService.findAllByFilter(new PagingParams(null, null, 0L, null));
// then
assertAll(
- () -> assertThat(challengeResponses).hasSize(5),
- () -> assertThat(challengeResponses.stream().mapToLong(ChallengeTabResponse::getChallengeId))
- .containsExactly(스모디_방문하기_ID, 미라클_모닝_ID, 오늘의_운동_ID, 알고리즘_풀기_ID, JPA_공부_ID),
- () -> assertThat(challengeResponses.stream().mapToInt(ChallengeTabResponse::getChallengerCount))
- .containsExactly(2, 1, 0, 0, 0)
+ () -> assertThat(challenges).hasSize(5),
+ () -> assertThat(challenges.stream().mapToLong(Challenge::getId))
+ .containsExactly(스모디_방문하기_ID, 미라클_모닝_ID, 오늘의_운동_ID, 알고리즘_풀기_ID, JPA_공부_ID)
);
}
@@ -297,16 +119,14 @@ void findAllWithChallengerCount_sort() {
@Test
void findAllWithChallengerCount_pageFullSize() {
// when
- List challengeResponses = challengeApiService.findAllWithChallengerCountByFilter(now,
+ List challenges = challengeService.findAllByFilter(
new PagingParams(null, 2, 0L, null));
// then
assertAll(
- () -> assertThat(challengeResponses).hasSize(2),
- () -> assertThat(challengeResponses.stream().map(ChallengeTabResponse::getChallengeId))
- .containsExactly(스모디_방문하기_ID, 미라클_모닝_ID),
- () -> assertThat(challengeResponses.stream().mapToInt(ChallengeTabResponse::getChallengerCount))
- .containsExactly(2, 1)
+ () -> assertThat(challenges).hasSize(2),
+ () -> assertThat(challenges.stream().map(Challenge::getId))
+ .containsExactly(스모디_방문하기_ID, 미라클_모닝_ID)
);
}
@@ -314,16 +134,15 @@ void findAllWithChallengerCount_pageFullSize() {
@Test
void findAllWithChallengerCount_pagePartialSize() {
// when
- List challengeResponses = challengeApiService.findAllWithChallengerCountByFilter(
- now, new PagingParams(null, 2, 미라클_모닝_ID, null));
+ List challenges = challengeService.findAllByFilter(
+ new PagingParams(null, 2, 미라클_모닝_ID, null)
+ );
// then
assertAll(
- () -> assertThat(challengeResponses).hasSize(2),
- () -> assertThat(challengeResponses.stream().mapToLong(ChallengeTabResponse::getChallengeId))
- .containsExactly(오늘의_운동_ID, 알고리즘_풀기_ID),
- () -> assertThat(challengeResponses.stream().mapToInt(ChallengeTabResponse::getChallengerCount))
- .containsExactly(0, 0)
+ () -> assertThat(challenges).hasSize(2),
+ () -> assertThat(challenges.stream().mapToLong(Challenge::getId))
+ .containsExactly(오늘의_운동_ID, 알고리즘_풀기_ID)
);
}
@@ -331,455 +150,134 @@ void findAllWithChallengerCount_pagePartialSize() {
@Test
void findAllWithChallengerCount_pageOverMaxPage() {
// when
- List challengeResponses = challengeApiService.findAllWithChallengerCountByFilter(
- now, new PagingParams(null, 3, JPA_공부_ID, null));
+ List challenges = challengeService.findAllByFilter(
+ new PagingParams(null, 3, JPA_공부_ID, null));
// then
- assertThat(challengeResponses).isEmpty();
+ assertThat(challenges).isEmpty();
}
- @DisplayName("회원이 조회")
- @Test
- void findAllWithChallengerCount_sortAuth() {
- // when
- TokenPayload tokenPayload = new TokenPayload(조조그린_ID);
- List