diff --git a/keewe-api/src/main/java/ccc/keeweapi/component/ChallengeAssembler.java b/keewe-api/src/main/java/ccc/keeweapi/component/ChallengeAssembler.java index 6a5507fb..59657e36 100644 --- a/keewe-api/src/main/java/ccc/keeweapi/component/ChallengeAssembler.java +++ b/keewe-api/src/main/java/ccc/keeweapi/component/ChallengeAssembler.java @@ -17,10 +17,12 @@ import ccc.keeweapi.dto.challenge.ParticipatingChallengeDetailResponse; import ccc.keeweapi.dto.challenge.ParticipatingChallengeResponse; import ccc.keeweapi.dto.challenge.ParticipationCheckResponse; +import ccc.keeweapi.dto.challenge.ParticipationUpdateRequest; import ccc.keeweapi.dto.challenge.WeekProgressResponse; import ccc.keeweapi.utils.SecurityUtil; import ccc.keewedomain.dto.challenge.ChallengeCreateDto; import ccc.keewedomain.dto.challenge.ChallengeParticipateDto; +import ccc.keewedomain.dto.challenge.ParticipationUpdateDto; import ccc.keewedomain.persistence.domain.challenge.Challenge; import ccc.keewedomain.persistence.domain.challenge.ChallengeParticipation; import ccc.keewedomain.persistence.domain.user.User; @@ -118,13 +120,16 @@ public WeekProgressResponse toWeekProgressResponse( ); } - public ParticipatingChallengeResponse toMyChallengeResponse(ChallengeParticipation participation, Long participatingUser) { + public ParticipatingChallengeResponse toMyChallengeResponse(ChallengeParticipation participation) { Challenge challenge = participation.getChallenge(); return ParticipatingChallengeResponse.of( challenge.getId(), challenge.getName(), - participatingUser, challenge.getInterest().getName(), + participation.getMyTopic(), + participation.getInsightPerWeek(), + participation.getDuration(), + participation.getEndDate().toString(), participation.getCreatedAt().toLocalDate().toString() ); } @@ -200,4 +205,8 @@ public ChallengeStatisticsResponse toChallengeStatisticsResponse( ) { return ChallengeStatisticsResponse.of(viewCount, reactionCount, commentCount, bookmarkCount, shareCount); } + + public ParticipationUpdateDto toParticipationUpdateDto(Long userId, ParticipationUpdateRequest request) { + return ParticipationUpdateDto.of(userId, request.getMyTopic(), request.getInsightPerWeek(), request.getDuration()); + } } diff --git a/keewe-api/src/main/java/ccc/keeweapi/controller/api/challenge/ChallengeParticipationController.java b/keewe-api/src/main/java/ccc/keeweapi/controller/api/challenge/ChallengeParticipationController.java index 328ff93a..278fb050 100644 --- a/keewe-api/src/main/java/ccc/keeweapi/controller/api/challenge/ChallengeParticipationController.java +++ b/keewe-api/src/main/java/ccc/keeweapi/controller/api/challenge/ChallengeParticipationController.java @@ -10,6 +10,7 @@ import ccc.keeweapi.dto.challenge.MyParticipationProgressResponse; import ccc.keeweapi.dto.challenge.ParticipatingChallengeResponse; import ccc.keeweapi.dto.challenge.ParticipationCheckResponse; +import ccc.keeweapi.dto.challenge.ParticipationUpdateRequest; import ccc.keeweapi.dto.challenge.WeekProgressResponse; import ccc.keeweapi.service.challenge.ChallengeApiService; import ccc.keeweapi.service.challenge.command.ChallengeParticipationCommandApiService; @@ -24,6 +25,7 @@ import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; @@ -47,7 +49,13 @@ public ApiResponse participate(@RequestBody @Val @GetMapping("/participating") public ApiResponse getParticipatingChallenge() { - return ApiResponse.ok(challengeApiService.getParticipatingChallenege()); + return ApiResponse.ok(challengeApiService.getParticipatingChallenge()); + } + + @PatchMapping("/participating") + public ApiResponse updateParticipation(@RequestBody ParticipationUpdateRequest request) { + challengeApiService.updateParticipation(request); + return ApiResponse.ok(); } @GetMapping(value = "/participation/check") diff --git a/keewe-api/src/main/java/ccc/keeweapi/dto/challenge/ParticipatingChallengeResponse.java b/keewe-api/src/main/java/ccc/keeweapi/dto/challenge/ParticipatingChallengeResponse.java index 95fa3500..3a36ac44 100644 --- a/keewe-api/src/main/java/ccc/keeweapi/dto/challenge/ParticipatingChallengeResponse.java +++ b/keewe-api/src/main/java/ccc/keeweapi/dto/challenge/ParticipatingChallengeResponse.java @@ -8,7 +8,10 @@ public class ParticipatingChallengeResponse { private Long challengeId; private String name; - private Long participatingUserNumber; private String interest; + private String myTopic; + private int insightPerWeek; + private int duration; + private String endDate; private String startDate; } diff --git a/keewe-api/src/main/java/ccc/keeweapi/dto/challenge/ParticipationUpdateRequest.java b/keewe-api/src/main/java/ccc/keeweapi/dto/challenge/ParticipationUpdateRequest.java new file mode 100644 index 00000000..55253f7b --- /dev/null +++ b/keewe-api/src/main/java/ccc/keeweapi/dto/challenge/ParticipationUpdateRequest.java @@ -0,0 +1,19 @@ +package ccc.keeweapi.dto.challenge; + +import ccc.keeweapi.validator.annotations.GraphemeLength; +import lombok.Getter; + +import javax.validation.constraints.Max; +import javax.validation.constraints.Min; + +@Getter +public class ParticipationUpdateRequest { + @GraphemeLength(max = 150) + private String myTopic; + + @Min(1) @Max(7) + private int insightPerWeek; + + @Min(2) @Max(8) + private int duration; +} diff --git a/keewe-api/src/main/java/ccc/keeweapi/service/challenge/ChallengeApiService.java b/keewe-api/src/main/java/ccc/keeweapi/service/challenge/ChallengeApiService.java index f3ce1fb3..70a4dbdf 100644 --- a/keewe-api/src/main/java/ccc/keeweapi/service/challenge/ChallengeApiService.java +++ b/keewe-api/src/main/java/ccc/keeweapi/service/challenge/ChallengeApiService.java @@ -13,10 +13,12 @@ import ccc.keeweapi.dto.challenge.ParticipatingChallengeDetailResponse; import ccc.keeweapi.dto.challenge.ParticipatingChallengeResponse; import ccc.keeweapi.dto.challenge.ParticipationCheckResponse; +import ccc.keeweapi.dto.challenge.ParticipationUpdateRequest; import ccc.keeweapi.dto.challenge.WeekProgressResponse; import ccc.keeweapi.utils.SecurityUtil; import ccc.keewecore.consts.KeeweRtnConsts; import ccc.keewecore.exception.KeeweException; +import ccc.keewedomain.dto.challenge.ParticipationUpdateDto; import ccc.keewedomain.persistence.domain.challenge.Challenge; import ccc.keewedomain.persistence.domain.challenge.ChallengeParticipation; import ccc.keewedomain.persistence.domain.user.User; @@ -99,12 +101,9 @@ public WeekProgressResponse getWeekProgress() { } @Transactional(readOnly = true) - public ParticipatingChallengeResponse getParticipatingChallenege() { + public ParticipatingChallengeResponse getParticipatingChallenge() { return challengeParticipateQueryDomainService.findCurrentChallengeParticipation(SecurityUtil.getUser()) - .map(participation -> { - Long participatingUser = challengeParticipateQueryDomainService.countParticipatingUser(participation.getChallenge()); - return challengeAssembler.toMyChallengeResponse(participation, participatingUser); - }) + .map(challengeAssembler::toMyChallengeResponse) .orElse(null); } @@ -164,4 +163,10 @@ public ChallengeInsightNumberResponse countInsightOfChallenge(Long writerId) { .orElseThrow(() -> new KeeweException(KeeweRtnConsts.ERR432)); return challengeAssembler.toChallengeInsightNumberResponse(insightNumber); } + + @Transactional + public void updateParticipation(ParticipationUpdateRequest request) { + ParticipationUpdateDto dto = challengeAssembler.toParticipationUpdateDto(SecurityUtil.getUserId(), request); + challengeCommandDomainService.updateParticipation(dto); + } } diff --git a/keewe-api/src/test/java/ccc/keeweapi/controller/api/challenge/ChallengeParticipationControllerTest.java b/keewe-api/src/test/java/ccc/keeweapi/controller/api/challenge/ChallengeParticipationControllerTest.java index 251f5bc1..bbb56439 100644 --- a/keewe-api/src/test/java/ccc/keeweapi/controller/api/challenge/ChallengeParticipationControllerTest.java +++ b/keewe-api/src/test/java/ccc/keeweapi/controller/api/challenge/ChallengeParticipationControllerTest.java @@ -37,6 +37,7 @@ import static org.mockito.Mockito.when; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.delete; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.get; +import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.patch; import static org.springframework.restdocs.mockmvc.RestDocumentationRequestBuilders.post; import static org.springframework.restdocs.payload.PayloadDocumentation.fieldWithPath; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; @@ -224,12 +225,15 @@ void home_my_week_progress() throws Exception { void home_my_challenge() throws Exception { long challengeId = 1L; String name = "챌린지 이름"; - Long participatingUser = 9999L; String interest = "프론트"; + String myTopic = "나만의 주제"; + int insightPerWeek = 2; + int duration = 2; + String endDate = "2023-02-17"; String startDate = "2023-02-04"; - ParticipatingChallengeResponse response = ParticipatingChallengeResponse.of(challengeId, name, participatingUser, interest, startDate); + ParticipatingChallengeResponse response = ParticipatingChallengeResponse.of(challengeId, name, interest, myTopic, insightPerWeek, duration, endDate, startDate); - when(challengeApiService.getParticipatingChallenege()).thenReturn(response); + when(challengeApiService.getParticipatingChallenge()).thenReturn(response); ResultActions resultActions = mockMvc.perform(get("/api/v1/challenge/participating") .header(HttpHeaders.AUTHORIZATION, "Bearer " + JWT) @@ -249,9 +253,12 @@ void home_my_challenge() throws Exception { fieldWithPath("data").description("참가중이지 않은 경우 null"), fieldWithPath("data.challengeId").description("참가중인 챌린지의 ID"), fieldWithPath("data.name").description("참가중인 챌린지의 이름"), - fieldWithPath("data.participatingUserNumber").description("도전(참가)중인 유저의 수"), fieldWithPath("data.interest").description("관심사"), - fieldWithPath("data.startDate").description("챌린지에 참가한 날짜") + fieldWithPath("data.myTopic").description("나만의 주제"), + fieldWithPath("data.insightPerWeek").description("주마다 올릴 인사이트 개수"), + fieldWithPath("data.duration").description("참가 기간 단위: 주"), + fieldWithPath("data.startDate").description("챌린지에 참가한 날짜"), + fieldWithPath("data.endDate").description("챌린지 종료 예정일") ) .tag("ChallengeParticipation") .build() @@ -408,6 +415,37 @@ void count_completed_challenges() throws Exception { ))); } + @Test + @DisplayName("챌린지 참가 정보 수정") + void update_participation() throws Exception { + JSONObject request = new JSONObject() + .put("myTopic", "나만의 토픽") + .put("insightPerWeek", 2) + .put("duration", 4); + + ResultActions resultActions = mockMvc.perform(patch("/api/v1/challenge/participating") + .header(HttpHeaders.AUTHORIZATION, "Bearer " + JWT) + .content(request.toString()) + .contentType(MediaType.APPLICATION_JSON)) + .andExpect(status().isOk()); + + resultActions.andDo(restDocs.document(resource( + ResourceSnippetParameters.builder() + .description("챌린지 참가 정보 수정 API 입니다.") + .summary("챌린지 참가 정보 수정 API") + .requestHeaders( + headerWithName("Authorization").description("유저의 JWT") + ) + .responseFields( + fieldWithPath("message").description("요청 결과 메세지"), + fieldWithPath("code").description("결과 코드"), + fieldWithPath("data").description("성공한 경우 null") + ) + .tag("ChallengeParticipation") + .build() + ))); + } + @Test @DisplayName("참가중인 챌린지 취소") void cancel_current_challenge() throws Exception { diff --git a/keewe-core/src/main/java/ccc/keewecore/consts/KeeweRtnConsts.java b/keewe-core/src/main/java/ccc/keewecore/consts/KeeweRtnConsts.java index e0154fbd..2f4aaf88 100644 --- a/keewe-core/src/main/java/ccc/keewecore/consts/KeeweRtnConsts.java +++ b/keewe-core/src/main/java/ccc/keewecore/consts/KeeweRtnConsts.java @@ -29,7 +29,9 @@ public enum KeeweRtnConsts { ERR430(KeeweRtnGrp.Validation, 430, "챌린지를 찾을 수 없어요."), ERR431(KeeweRtnGrp.Validation, 431, "이미 챌린지에 참여중이에요."), - ERR432(KeeweRtnGrp.Validation, 432, "참가중인 챌린지가 없어요"), + ERR432(KeeweRtnGrp.Validation, 432, "참가중인 챌린지가 없어요."), + ERR433(KeeweRtnGrp.Validation, 433, "종료일을 오늘 이전으로 설정할 수 없어요."), + ERR434(KeeweRtnGrp.Validation, 434, "달성 불가능한 기록 횟수로 변경할 수 없어요."), ERR440(KeeweRtnGrp.Validation, 440, "서랍을 찾을 수 없어요"), ERR441(KeeweRtnGrp.Validation, 441, "이미 등록된 서랍 이름이에요"), diff --git a/keewe-domain/src/main/java/ccc/keewedomain/dto/challenge/ParticipationUpdateDto.java b/keewe-domain/src/main/java/ccc/keewedomain/dto/challenge/ParticipationUpdateDto.java new file mode 100644 index 00000000..68ccb72b --- /dev/null +++ b/keewe-domain/src/main/java/ccc/keewedomain/dto/challenge/ParticipationUpdateDto.java @@ -0,0 +1,13 @@ +package ccc.keewedomain.dto.challenge; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor(staticName = "of") +public class ParticipationUpdateDto { + private Long userId; + private String myTopic; + private int insightPerWeek; + private int duration; +} diff --git a/keewe-domain/src/main/java/ccc/keewedomain/persistence/domain/challenge/ChallengeParticipation.java b/keewe-domain/src/main/java/ccc/keewedomain/persistence/domain/challenge/ChallengeParticipation.java index 0fbf80f4..a30bb8db 100644 --- a/keewe-domain/src/main/java/ccc/keewedomain/persistence/domain/challenge/ChallengeParticipation.java +++ b/keewe-domain/src/main/java/ccc/keewedomain/persistence/domain/challenge/ChallengeParticipation.java @@ -1,5 +1,7 @@ package ccc.keewedomain.persistence.domain.challenge; +import ccc.keewecore.consts.KeeweRtnConsts; +import ccc.keewecore.exception.KeeweException; import ccc.keewedomain.persistence.domain.challenge.enums.ChallengeParticipationStatus; import ccc.keewedomain.persistence.domain.common.BaseTimeEntity; import ccc.keewedomain.persistence.domain.user.User; @@ -64,16 +66,6 @@ public static ChallengeParticipation of(User challenger, Challenge challenge, St return participation; } - public void cancel() { - this.endDate = LocalDate.now(); - this.status = ChallengeParticipationStatus.CANCELED; - } - - private void initEndDate() { - LocalDate createdDate = getCreatedAt().toLocalDate(); - this.endDate = createdDate.minusDays(1).plusWeeks(duration); - } - // 현재가 몇 주차인지 public long getCurrentWeek() { LocalDate createdAt = getCreatedAt().toLocalDate(); @@ -91,12 +83,37 @@ public Long getTotalInsightNumber() { return (long) (insightPerWeek * duration); } + public void update(String myTopic, int insightPerWeek, int duration) { + this.myTopic = myTopic; + this.insightPerWeek = insightPerWeek; + this.duration = duration; + initEndDate(); + } + public void expire(LocalDate endDate) { this.endDate = endDate; this.status = ChallengeParticipationStatus.EXPIRED; } + public void cancel() { + this.endDate = LocalDate.now(); + this.status = ChallengeParticipationStatus.CANCELED; + } + public void complete() { this.status = ChallengeParticipationStatus.COMPLETED; } + + private void initEndDate() { + LocalDate createdDate = getCreatedAt().toLocalDate(); + LocalDate newEndDate = createdDate.minusDays(1).plusWeeks(duration); + validateEndDate(newEndDate); + this.endDate = newEndDate; + } + + private void validateEndDate(LocalDate newEndDate) { + if(newEndDate.isBefore(LocalDate.now())) { + throw new KeeweException(KeeweRtnConsts.ERR433); + } + } } diff --git a/keewe-domain/src/main/java/ccc/keewedomain/service/challenge/command/ChallengeCommandDomainService.java b/keewe-domain/src/main/java/ccc/keewedomain/service/challenge/command/ChallengeCommandDomainService.java index 084ae736..9f5ac710 100644 --- a/keewe-domain/src/main/java/ccc/keewedomain/service/challenge/command/ChallengeCommandDomainService.java +++ b/keewe-domain/src/main/java/ccc/keewedomain/service/challenge/command/ChallengeCommandDomainService.java @@ -1,20 +1,27 @@ package ccc.keewedomain.service.challenge.command; +import ccc.keewecore.consts.KeeweRtnConsts; import ccc.keewecore.consts.LockType; +import ccc.keewecore.exception.KeeweException; import ccc.keewecore.utils.RedisLockUtils; import ccc.keewedomain.dto.challenge.ChallengeCreateDto; import ccc.keewedomain.dto.challenge.ChallengeParticipateDto; +import ccc.keewedomain.dto.challenge.ParticipationUpdateDto; import ccc.keewedomain.persistence.domain.challenge.Challenge; import ccc.keewedomain.persistence.domain.challenge.ChallengeParticipation; import ccc.keewedomain.persistence.domain.user.User; import ccc.keewedomain.persistence.repository.challenge.ChallengeRepository; import ccc.keewedomain.service.challenge.query.ChallengeParticipateQueryDomainService; import ccc.keewedomain.service.challenge.query.ChallengeQueryDomainService; +import ccc.keewedomain.service.insight.query.InsightQueryDomainService; import ccc.keewedomain.service.user.UserDomainService; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; +import java.time.Period; + @RequiredArgsConstructor @Service public class ChallengeCommandDomainService { @@ -23,6 +30,7 @@ public class ChallengeCommandDomainService { private final ChallengeQueryDomainService challengeQueryDomainService; private final ChallengeParticipateQueryDomainService challengeParticipateQueryDomainService; private final UserDomainService userDomainService; + private final InsightQueryDomainService insightQueryDomainService; private final RedisLockUtils redisLockUtils; public Challenge save(ChallengeCreateDto dto) { @@ -41,6 +49,26 @@ public ChallengeParticipation participate(ChallengeParticipateDto dto) { } @Transactional + public void updateParticipation(ParticipationUpdateDto dto) { + ChallengeParticipation participation = challengeParticipateQueryDomainService.getCurrentParticipationByUserId(dto.getUserId()); + validateInsightPerWeek(participation, dto.getInsightPerWeek()); + participation.update(dto.getMyTopic(), dto.getInsightPerWeek(), dto.getDuration()); + } + + // 남은 일자 < insightPerWeek 확인 + private void validateInsightPerWeek(ChallengeParticipation participation, int insightPerWeek) { + LocalDateTime start = participation.getStartDateOfThisWeek().atStartOfDay(); + LocalDateTime end = LocalDateTime.now(); + Long thisWeekRecordCount = insightQueryDomainService.countInsightCreatedAtBetween(participation, start, end); + int remainDays = 7 - Period.between(start.toLocalDate(), end.toLocalDate()).getDays(); + if(insightQueryDomainService.isTodayRecorded(participation.getChallenger())) { + remainDays -= 1; + } + if(insightPerWeek - thisWeekRecordCount > remainDays) { + throw new KeeweException(KeeweRtnConsts.ERR434); + } + } + public void exitCurrentChallengeIfExist(User challenger) { challengeParticipateQueryDomainService.findCurrentChallengeParticipation(challenger).ifPresent(ChallengeParticipation::cancel); } diff --git a/keewe-domain/src/main/java/ccc/keewedomain/service/challenge/query/ChallengeParticipateQueryDomainService.java b/keewe-domain/src/main/java/ccc/keewedomain/service/challenge/query/ChallengeParticipateQueryDomainService.java index 51eebb11..9ecc5177 100644 --- a/keewe-domain/src/main/java/ccc/keewedomain/service/challenge/query/ChallengeParticipateQueryDomainService.java +++ b/keewe-domain/src/main/java/ccc/keewedomain/service/challenge/query/ChallengeParticipateQueryDomainService.java @@ -38,6 +38,10 @@ public ChallengeParticipation getCurrentChallengeParticipation(User user) { return findCurrentChallengeParticipation(user).orElseThrow(() -> new KeeweException(KeeweRtnConsts.ERR432)); } + public ChallengeParticipation getCurrentParticipationByUserId(Long userId) { + return findCurrentParticipationByUserId(userId).orElseThrow(() -> new KeeweException(KeeweRtnConsts.ERR432)); + } + public Optional findCurrentParticipationByUserId(Long userId) { return challengeParticipationQueryRepository.findByUserIdAndStatus(userId, CHALLENGING); }