Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feat] 챌린지 참가 정보 수정 API, 참가중인 챌린지 조회 API 수정 #270

Merged
merged 6 commits into from
Apr 1, 2023
Merged
Show file tree
Hide file tree
Changes from 4 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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(),
Comment on lines +129 to +132
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이미 존재하는 API같은데, 왜 변경되는거에요?
이 API를 여러곳에서 쓰나요?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

챌린지 홈에서 쓰고 있어요. 참가중인 챌린지 정보를 조회하는데 홈이냐 상세이냐에 따라 API를 구분하는게 이상해서 변경으로 진행했어요.

participation.getCreatedAt().toLocalDate().toString()
);
}
Expand Down Expand Up @@ -200,4 +205,8 @@ public ChallengeStatisticsResponse toChallengeStatisticsResponse(
) {
return ChallengeStatisticsResponse.of(viewCount, reactionCount, commentCount, bookmarkCount, shareCount);
}

public ParticipationUpdateDto toParticipationUpdateDto(ParticipationUpdateRequest request) {
return ParticipationUpdateDto.of(request.getMyTopic(), request.getInsightPerWeek(), request.getDuration());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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.query.ChallengeParticipationQueryApiService;
Expand All @@ -22,6 +23,7 @@
import org.springframework.data.web.PageableDefault;
import org.springframework.validation.annotation.Validated;
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;
Expand All @@ -44,7 +46,13 @@ public ApiResponse<ChallengeParticipationResponse> participate(@RequestBody @Val

@GetMapping("/participating")
public ApiResponse<ParticipatingChallengeResponse> getParticipatingChallenge() {
return ApiResponse.ok(challengeApiService.getParticipatingChallenege());
return ApiResponse.ok(challengeApiService.getParticipatingChallenge());
}

@PatchMapping("/participating")
public ApiResponse<Void> updateParticipation(@RequestBody ParticipationUpdateRequest request) {
challengeApiService.updateParticipation(request);
return ApiResponse.ok();
}

@GetMapping(value = "/participation/check")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

duration이 화면의 뭘 의미하는건지 잘 모르겠어요.

Copy link
Collaborator Author

@heoboseong7 heoboseong7 Mar 26, 2023

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

화면에서는 참가 기간이에요. 매주 3번 기록 x 4주 여기서 {4}주 에 해당돼요

private String endDate;
private String startDate;
}
Original file line number Diff line number Diff line change
@@ -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;
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
}

Expand Down Expand Up @@ -164,4 +163,11 @@ public ChallengeInsightNumberResponse countInsightOfChallenge(Long writerId) {
.orElseThrow(() -> new KeeweException(KeeweRtnConsts.ERR432));
return challengeAssembler.toChallengeInsightNumberResponse(insightNumber);
}

@Transactional
public void updateParticipation(ParticipationUpdateRequest request) {
ChallengeParticipation participation = challengeParticipateQueryDomainService.getCurrentChallengeParticipation(SecurityUtil.getUser());
ParticipationUpdateDto dto = challengeAssembler.toParticipationUpdateDto(request);
challengeCommandDomainService.updateParticipation(participation, dto);
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Dto를 따로 만든김에 participation 정보를 포함해서 뭉치는게 좋지않을까요?

혹은 domain service에서 조회하는 것도 고려할 수 있을거같아요~

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

userId를 포함시키는 방법으로 생각해볼게요

}
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
import static org.mockito.ArgumentMatchers.anyLong;
import static org.mockito.Mockito.when;
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;
Expand Down Expand Up @@ -206,12 +207,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)
Expand All @@ -231,9 +235,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()
Expand Down Expand Up @@ -389,4 +396,35 @@ void count_completed_challenges() throws Exception {
.build()
)));
}

@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()
)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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, "이미 등록된 서랍 이름이에요"),
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package ccc.keewedomain.dto.challenge;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor(staticName = "of")
public class ParticipationUpdateDto {
private String myTopic;
private int insightPerWeek;
private int duration;
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
Expand All @@ -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);
}
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,26 @@
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 java.time.LocalDateTime;
import java.time.Period;

@RequiredArgsConstructor
@Service
public class ChallengeCommandDomainService {
Expand All @@ -22,6 +29,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) {
Expand All @@ -39,6 +47,25 @@ public ChallengeParticipation participate(ChallengeParticipateDto dto) {
});
}

public void updateParticipation(ChallengeParticipation participation, ParticipationUpdateDto dto) {
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);
}
}

private void exitCurrentChallengeIfExist(User challenger) {
challengeParticipateQueryDomainService.findCurrentChallengeParticipation(challenger).ifPresent(ChallengeParticipation::cancel);
}
Expand Down