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] 홈 참가중인 챌린지 정보 조회 #147

Merged
merged 7 commits into from
Nov 9, 2022
Original file line number Diff line number Diff line change
Expand Up @@ -33,4 +33,9 @@ public ApiResponse<ParticipationCheckResponse> checkParticipation() {
public ApiResponse<InsightProgressResponse> getMyParticipationProgress() {
return ApiResponse.ok(challengeApiService.getMyParticipationProgress());
}

@GetMapping("/participation/my-week-progress")
public ApiResponse<WeekProgressResponse> getMyThisWeekProgress() {
return ApiResponse.ok(challengeApiService.getWeekProgress());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,11 @@
import ccc.keewedomain.dto.challenge.ChallengeParticipateDto;
import org.springframework.stereotype.Component;

import java.time.LocalDate;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;

@Component
public class ChallengeAssembler {
public ChallengeCreateResponse toChallengeCreateResponse(Challenge challenge, ChallengeParticipation participation) {
Expand Down Expand Up @@ -63,4 +68,29 @@ public InsightProgressResponse toParticipationProgressResponse(ChallengeParticip
participation.getTotalInsightNumber()
);
}

public WeekProgressResponse toWeekProgressResponse(
List<String> dates,
Map<String, Long> recordCountPerDate,
ChallengeParticipation participation,
LocalDate startDate) {

List<DayProgressResponse> dayProgresses = dates.stream()
.map(recordCountPerDate::containsKey)
.map(DayProgressResponse::of)
.collect(Collectors.toList());

long recorded = recordCountPerDate.values().stream()
.mapToLong(v -> v)
.sum();

Challenge challenge = participation.getChallenge();

return WeekProgressResponse.of(challenge.getId(),
participation.getInsightPerWeek() - recorded,
challenge.getName(),
startDate.toString(),
dayProgresses
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package ccc.keeweapi.dto.challenge;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor(staticName = "of")
public class DayProgressResponse {

private boolean check;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package ccc.keeweapi.dto.challenge;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.time.LocalDate;
import java.util.List;


@Getter
@AllArgsConstructor(staticName = "of")
public class WeekProgressResponse {

private Long challengeId;
private Long remain;
private String challengeName;
private String startDate;
private List<DayProgressResponse> dayProgresses;
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,17 @@
import ccc.keeweapi.utils.SecurityUtil;
import ccc.keewedomain.persistence.domain.challenge.Challenge;
import ccc.keewedomain.persistence.domain.challenge.ChallengeParticipation;
import ccc.keewedomain.persistence.domain.user.User;
import ccc.keewedomain.service.challenge.ChallengeDomainService;
import ccc.keewedomain.service.insight.InsightDomainService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.time.LocalDate;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;


Expand Down Expand Up @@ -54,4 +59,25 @@ public InsightProgressResponse getMyParticipationProgress() {
})
.orElse(null);
}

@Transactional(readOnly = true)
public WeekProgressResponse getWeekProgress() {
return challengeDomainService.findCurrentChallengeParticipation(SecurityUtil.getUser())
.map(participation -> {
Map<String, Long> recordCountPerDate = challengeDomainService.getRecordCountPerDate(participation);
LocalDate startDateOfWeek = participation.getStartDateOfThisWeek();
List<String> dates = datesOfWeek(startDateOfWeek);
return challengeAssembler.toWeekProgressResponse(dates, recordCountPerDate, participation, startDateOfWeek);
})
.orElse(null);
}

private List<String> datesOfWeek(LocalDate startDate) {
List<String> dates = new ArrayList<>(7);
for (int i = 0; i < 7; i++) {
dates.add(startDate.plusDays(i).toString());
}

return dates;
}
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,7 @@
package ccc.keeweapi.api.challenge;

import ccc.keeweapi.document.utils.ApiDocumentationTest;
import ccc.keeweapi.dto.challenge.ChallengeCreateResponse;
import ccc.keeweapi.dto.challenge.ChallengeParticipationResponse;
import ccc.keeweapi.dto.challenge.InsightProgressResponse;
import ccc.keeweapi.dto.challenge.ParticipationCheckResponse;
import ccc.keeweapi.dto.challenge.*;
import ccc.keeweapi.service.challenge.ChallengeApiService;
import com.epages.restdocs.apispec.ResourceSnippetParameters;
import org.json.JSONObject;
Expand All @@ -19,6 +16,7 @@
import org.springframework.test.web.servlet.ResultActions;

import java.time.LocalDate;
import java.util.List;

import static com.epages.restdocs.apispec.ResourceDocumentation.headerWithName;
import static com.epages.restdocs.apispec.ResourceDocumentation.resource;
Expand Down Expand Up @@ -211,4 +209,51 @@ void insight_create_progress() throws Exception {
.build()
)));
}

@Test
@DisplayName("홈 나의 챌린지 참가 현황 조회 API")
void home_my_week_progress() throws Exception {
Long challengeId = 1L;
Long remain = 2L;
String challengeName = "챌린지 이름";
LocalDate startDate = LocalDate.now();
List<DayProgressResponse> dayProgresses = List.of(
DayProgressResponse.of(true),
DayProgressResponse.of(true),
DayProgressResponse.of(false),
DayProgressResponse.of(true),
DayProgressResponse.of(false),
DayProgressResponse.of(true),
DayProgressResponse.of(false)
);

when(challengeApiService.getWeekProgress())
.thenReturn(WeekProgressResponse.of(challengeId, remain, challengeName, startDate.toString(), dayProgresses));

ResultActions resultActions = mockMvc.perform(get("/api/v1/challenge/participation/my-week-progress")
.header(HttpHeaders.AUTHORIZATION, "Bearer " + JWT)
.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"),
fieldWithPath("data.challengeId").description("참가중인 챌린지의 ID"),
fieldWithPath("data.challengeName").description("챌린지의 이름"),
fieldWithPath("data.remain").description("이번 주의 남은 인사이트의 수"),
fieldWithPath("data.startDate").description("이번 주의 챌린지 시작일"),
fieldWithPath("data.dayProgresses[].check").description("해당 날짜에 인사이트를 작성했는지 true or false")
)
.tag("Challenge")
.build()
)));
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import lombok.NoArgsConstructor;

import javax.persistence.*;

import java.time.LocalDate;
import java.time.Period;

Expand Down Expand Up @@ -83,6 +82,10 @@ public long getCurrentWeek() {
return between.getDays() / 7 + 1; // 1주차부터 시작
}

public LocalDate getStartDateOfThisWeek() {
return getCreatedAt().plusWeeks(getCurrentWeek() - 1).toLocalDate();
}

public Long getTotalInsightNumber() {
return (long) (insightPerWeek * duration);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,61 @@

import ccc.keewedomain.persistence.domain.challenge.ChallengeParticipation;
import ccc.keewedomain.persistence.domain.challenge.enums.ChallengeParticipationStatus;
import com.querydsl.core.group.GroupBy;
import com.querydsl.core.types.dsl.DateTemplate;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Repository;

import java.time.LocalDateTime;
import java.util.Map;
import java.util.Optional;

public interface ChallengeParticipationQueryRepository {
import static ccc.keewedomain.persistence.domain.challenge.QChallenge.challenge;
import static ccc.keewedomain.persistence.domain.challenge.QChallengeParticipation.challengeParticipation;
import static ccc.keewedomain.persistence.domain.insight.QInsight.insight;
import static ccc.keewedomain.persistence.domain.user.QUser.user;

boolean existsByChallengerIdAndStatus(Long challengerId, ChallengeParticipationStatus status);
Optional<ChallengeParticipation> findByChallengerIdAndStatusWithChallenge(Long challengerId, ChallengeParticipationStatus status);
@Repository
@RequiredArgsConstructor
public class ChallengeParticipationQueryRepository {

private final JPAQueryFactory queryFactory;

private final DateTemplate<String> insightCreatedDate = Expressions.dateTemplate(
String.class,
"DATE_FORMAT({0}, {1})",
insight.createdAt,
"%Y-%m-%d");

public boolean existsByChallengerIdAndStatus(Long challengerId, ChallengeParticipationStatus status) {
Integer fetchFirst = queryFactory
.selectOne()
.from(challengeParticipation)
.where(user.id.eq(challengerId).and(challengeParticipation.status.eq(status)))
.fetchFirst();

return fetchFirst != null;
}

public Optional<ChallengeParticipation> findByChallengerIdAndStatusWithChallenge(Long challengerId, ChallengeParticipationStatus status) {
return Optional.ofNullable(queryFactory
.select(challengeParticipation)
.from(challengeParticipation)
.innerJoin(challengeParticipation.challenge, challenge)
.fetchJoin()
.where(challengeParticipation.challenger.id.eq(challengerId).and(challengeParticipation.status.eq(status)))
.fetchOne());
}

public Map<String, Long> getRecordCountPerDate(ChallengeParticipation participation, LocalDateTime startDateTime, LocalDateTime endDateTime) {
return queryFactory
.from(insight)
.where(insight.challengeParticipation.eq(participation)
.and(insight.valid.isTrue())
.and(insight.createdAt.goe(startDateTime).and(insight.createdAt.lt(endDateTime))))
.groupBy(insightCreatedDate)
.transform(GroupBy.groupBy(insightCreatedDate).as(insight.count()));
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,7 @@

import java.util.Optional;

public interface ChallengeParticipationRepository extends
JpaRepository<ChallengeParticipation, Long>,
ChallengeParticipationQueryRepository {
public interface ChallengeParticipationRepository extends JpaRepository<ChallengeParticipation, Long> {

Optional<ChallengeParticipation> findByChallengerAndStatusAndDeletedFalse(User challenger, ChallengeParticipationStatus status);
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,17 @@
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.ChallengeParticipationQueryRepository;
import ccc.keewedomain.persistence.repository.challenge.ChallengeParticipationRepository;
import ccc.keewedomain.persistence.repository.challenge.ChallengeRepository;
import ccc.keewedomain.persistence.repository.insight.InsightQueryRepository;
import ccc.keewedomain.service.user.UserDomainService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Map;
import java.util.Optional;

import static ccc.keewedomain.persistence.domain.challenge.enums.ChallengeParticipationStatus.CHALLENGING;
Expand All @@ -23,7 +27,7 @@
public class ChallengeDomainService {
private final ChallengeRepository challengeRepository;
private final ChallengeParticipationRepository challengeParticipationRepository;
private final InsightQueryRepository insightQueryRepository;
private final ChallengeParticipationQueryRepository challengeParticipationQueryRepository;

private final UserDomainService userDomainService;

Expand All @@ -45,15 +49,15 @@ public Challenge getByIdOrElseThrow(Long id) {
}

public boolean checkParticipation(Long userId) {
return challengeParticipationRepository.existsByChallengerIdAndStatus(userId, CHALLENGING);
return challengeParticipationQueryRepository.existsByChallengerIdAndStatus(userId, CHALLENGING);
}

public ChallengeParticipation getCurrentChallengeParticipation(User challenger) {
return findCurrentChallengeParticipation(challenger).orElseThrow(() -> new KeeweException(KeeweRtnConsts.ERR432));
}

public Optional<ChallengeParticipation> findCurrentParticipationWithChallenge(Long challengerId) {
return challengeParticipationRepository.findByChallengerIdAndStatusWithChallenge(challengerId, CHALLENGING);
return challengeParticipationQueryRepository.findByChallengerIdAndStatusWithChallenge(challengerId, CHALLENGING);
}

private void exitCurrentChallengeIfExist(User challenger) {
Expand All @@ -63,4 +67,10 @@ private void exitCurrentChallengeIfExist(User challenger) {
public Optional<ChallengeParticipation> findCurrentChallengeParticipation(User challenger) {
return challengeParticipationRepository.findByChallengerAndStatusAndDeletedFalse(challenger, CHALLENGING);
}

public Map<String, Long> getRecordCountPerDate(ChallengeParticipation participation) {
LocalDate startDate = participation.getStartDateOfThisWeek();
LocalDateTime startDateTime = LocalDateTime.of(startDate, LocalTime.MIN);
return challengeParticipationQueryRepository.getRecordCountPerDate(participation, startDateTime, startDateTime.plusDays(7L));
}
}