Skip to content

Commit

Permalink
feat: 챌린지 홈 조회 api (#8)
Browse files Browse the repository at this point in the history
feat: 챌린지 홈 조회 api
  • Loading branch information
daeunkwak authored Jul 23, 2023
2 parents 03ea4f9 + e297b17 commit b9574a6
Show file tree
Hide file tree
Showing 13 changed files with 392 additions and 5 deletions.
6 changes: 6 additions & 0 deletions cider-api/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,12 @@ dependencies {

// s3
implementation group: 'org.springframework.cloud', name: 'spring-cloud-starter-aws', version: '2.2.6.RELEASE'

// querydsl
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta';
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"
}

jar{
Expand Down
Original file line number Diff line number Diff line change
@@ -1,30 +1,39 @@
package com.cmc.domains.challenge.controller;

import com.cmc.challenge.Challenge;
import com.cmc.challengeLike.ChallengeLike;
import com.cmc.common.exception.BadRequestException;
import com.cmc.common.response.CommonResponse;
import com.cmc.common.response.CreatedResponse;
import com.cmc.domains.challenge.dto.request.ChallengeCreateRequestDto;
import com.cmc.domains.challenge.dto.request.ChallengeParticipateRequestDto;
import com.cmc.domains.challenge.dto.response.ChallengeCreateResponseDto;
import com.cmc.domains.challenge.dto.response.ChallengeHomeResponseDto;
import com.cmc.domains.challenge.dto.response.ChallengeResponseDto;
import com.cmc.domains.challenge.service.ChallengeService;
import com.cmc.domains.challenge.vo.ChallengeResponseVo;
import com.cmc.domains.image.service.ImageService;
import com.cmc.domains.participate.service.ParticipateService;
import com.cmc.global.resolver.RequestMemberId;
import com.cmc.participate.Participate;
import com.cmc.oauth.service.TokenProvider;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.http.HttpStatus;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.IOException;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

@Slf4j
@RestController
Expand Down Expand Up @@ -65,6 +74,67 @@ public ResponseEntity<CommonResponse> createSuccessExampleImages(@Parameter(hidd
return ResponseEntity.ok(CommonResponse.from("인증 예시 이미지가 업로드 되었습니다."));
}

@Tag(name = "challenge")
@Operation(summary = "홈 - 인기 챌린지, 공식 챌린지 조회 api")
@GetMapping("/home")
public ResponseEntity<ChallengeHomeResponseDto> getChallengeHome(HttpServletRequest httpServletRequest) {

final String tokenString = httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION);

// 인기 챌린지
List<ChallengeResponseVo> popularChallengeVos = challengeService.getPopularChallenges();
List<ChallengeResponseDto> popularChallengeResponseDtos = makeChallengeResponseDto(tokenString, popularChallengeVos);

// 공식 챌린지
List<ChallengeResponseVo> officialChallengeVos = challengeService.getOfficialChallenges();
List<ChallengeResponseDto> officialChallengeResponseDtos = makeChallengeResponseDto(tokenString, officialChallengeVos);

return ResponseEntity.ok(ChallengeHomeResponseDto.from(popularChallengeResponseDtos, officialChallengeResponseDtos));
}

@Tag(name = "challenge")
@Operation(summary = "홈 - 카테고리 별 챌린지 조회 api")
@GetMapping("/home/{category}")
public ResponseEntity<List<ChallengeResponseDto>> getChallengeHomeCategory(HttpServletRequest httpServletRequest,
@PathVariable("category") String category) {

final String tokenString = httpServletRequest.getHeader(HttpHeaders.AUTHORIZATION);

List<ChallengeResponseVo> challengeVos = challengeService.getCategoryChallenges(category);
List<ChallengeResponseDto> challengeResponseDtos = makeChallengeResponseDto(tokenString, challengeVos);
return ResponseEntity.ok(challengeResponseDtos);
}

private List<ChallengeResponseDto> makeChallengeResponseDto(String tokenString, List<ChallengeResponseVo> challengeVos){

List<ChallengeResponseDto> challengeResponseDtos = new ArrayList<>();
if (tokenString == null || tokenString.isEmpty()) {
// 로그인 x
challengeResponseDtos = challengeVos.stream().map(vo -> {
return ChallengeResponseDto.from(vo.getChallenge(), vo.getParticipateNum(),
ChronoUnit.DAYS.between(LocalDate.now(), vo.getChallenge().getChallengeStartDate()));
}).toList();
} else{
// 로그인 o
challengeResponseDtos = challengeVos.stream().map(vo -> {
return ChallengeResponseDto.from(vo.getChallenge(), vo.getParticipateNum(),
findIsLike(vo.getChallenge(), TokenProvider.getMemberId(tokenString)), ChronoUnit.DAYS.between(LocalDate.now(), vo.getChallenge().getChallengeStartDate()));
}).toList();
}

return challengeResponseDtos;
}

private Boolean findIsLike(Challenge challenge, Long memberId){

for(ChallengeLike challengeLike : challenge.getChallengeLikes()){
if (challengeLike.getMember().getMemberId().equals(memberId)){
return true;
}
}
return false;
}

@Tag(name = "challenge")
@Operation(summary = "챌린지 참여하기 api")
@PostMapping(value="/participate")
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.cmc.domains.challenge.dto.response;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

import java.util.List;

@Data
@Builder
@AllArgsConstructor
public class ChallengeHomeResponseDto {

@Schema(description = "인기 챌린지 리스트")
private List<ChallengeResponseDto> challengeResponseDto;

@Schema(description = "공식 챌린지 리스트")
private List<ChallengeResponseDto> officialChallengeResponseDto;

public static ChallengeHomeResponseDto from(List<ChallengeResponseDto> challengeResponseDto,
List<ChallengeResponseDto> officialChallengeResponseDto){

return new ChallengeHomeResponseDtoBuilder()
.challengeResponseDto(challengeResponseDto)
.officialChallengeResponseDto(officialChallengeResponseDto)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
package com.cmc.domains.challenge.dto.response;

import com.cmc.challenge.Challenge;
import com.cmc.challenge.constant.Status;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;

@Data
@Builder
@AllArgsConstructor
public class ChallengeResponseDto {

@Schema(description = "챌린지 id", example = "10")
private Long challengeId;

@Schema(description = "챌린지 제목", example = "소비습관 고치기")
private String challengeName;

@Schema(description = "챌린지 상태", example = "RECRUITING: 모집중, POSSIBLE: 참여 가능, IMPOSSIBLE: 참여 불가(종료)")
private Status challengeStatus;

@Schema(description = "챌린지 대기/참여중 인원", example = "5")
private Integer participateNum;

@Schema(description = "모집중인 경우 - 디데이", example = "23")
private Long recruitLeft;

@Schema(description = "챌린지 분야", example = "재태크/돈관리/금융학습/소비절약")
private String interestField;

@Schema(description = "챌린지 진행 기간", example = "4")
private Integer challengePeriod;

@Schema(description = "공식 챌린지 여부", example = "true")
private Boolean isOfficial;

@Schema(description = "리워드 여부", example = "true")
private Boolean isReward;

@Schema(description = "로그인 한 사용자 - 챌린지 좋아요 여부", example = "false")
private Boolean isLike;

public static ChallengeResponseDto from(Challenge challenge, Integer participateNum, Boolean isLike, Long recruitLeft){

return new ChallengeResponseDtoBuilder()
.challengeId(challenge.getChallengeId())
.challengeName(challenge.getChallengeName())
.challengeStatus(challenge.getChallengeStatus())
.participateNum(participateNum)
.recruitLeft(recruitLeft)
.interestField(challenge.getChallengeBranch())
.isOfficial(challenge.getIsOfficial())
.isReward(challenge.getIsReward())
.isLike(isLike)
.build();
}

public static ChallengeResponseDto from(Challenge challenge, Integer participateNum, Long recruitLeft){

return new ChallengeResponseDtoBuilder()
.challengeId(challenge.getChallengeId())
.challengeName(challenge.getChallengeName())
.challengeStatus(challenge.getChallengeStatus())
.participateNum(participateNum)
.recruitLeft(recruitLeft)
.interestField(challenge.getChallengeBranch())
.isOfficial(challenge.getIsOfficial())
.isReward(challenge.getIsReward())
.isLike(false)
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.cmc.domains.challenge.repository;

import com.cmc.domains.challenge.vo.ChallengeResponseVo;

import java.util.List;

public interface ChallengeCustomRepository {

List<ChallengeResponseVo> getPopularChallenges();

List<ChallengeResponseVo> getOfficialChallenges();

List<ChallengeResponseVo> getCategoryChallenges(String category);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
package com.cmc.domains.challenge.repository;

import com.cmc.challenge.QChallenge;
import com.cmc.challenge.constant.InterestField;
import com.cmc.challenge.constant.Status;
import com.cmc.domains.challenge.vo.ChallengeResponseVo;
import com.querydsl.core.types.Projections;
import com.querydsl.core.types.dsl.Expressions;
import com.querydsl.jpa.impl.JPAQueryFactory;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.stereotype.Repository;

import java.time.LocalDate;
import java.time.temporal.ChronoUnit;
import java.time.temporal.Temporal;
import java.util.List;

import static com.cmc.challenge.QChallenge.challenge;
import static com.cmc.participate.QParticipate.participate;

@Repository
@RequiredArgsConstructor
@Slf4j
public class ChallengeCustomRepositoryImpl implements ChallengeCustomRepository{

private final JPAQueryFactory jpaQueryFactory;

@Override
public List<ChallengeResponseVo> getPopularChallenges() {

return jpaQueryFactory.selectDistinct(Projections.fields(ChallengeResponseVo.class,
challenge,
participate.count()))
.from(challenge, participate)
.innerJoin(participate.challenge, challenge)
.where(challenge.challengeStatus.eq(Status.RECRUITING).or(challenge.challengeStatus.eq(Status.POSSIBLE)))
.groupBy(challenge)
.orderBy(participate.count().desc())
.limit(10)
.fetch();
}

@Override
public List<ChallengeResponseVo> getOfficialChallenges() {

return jpaQueryFactory.selectDistinct(Projections.fields(ChallengeResponseVo.class,
challenge,
participate.count()))
.from(challenge, participate)
.innerJoin(participate.challenge, challenge)
.where(challenge.challengeStatus.eq(Status.POSSIBLE).and(challenge.isOfficial.eq(true)))
.groupBy(challenge)
.orderBy(challenge.createdDate.desc())
.limit(10)
.fetch();
}

@Override
public List<ChallengeResponseVo> getCategoryChallenges(String category) {

return jpaQueryFactory.selectDistinct(Projections.fields(ChallengeResponseVo.class,
challenge,
participate.count()))
.from(challenge, participate)
.innerJoin(participate.challenge, challenge)
.where(challenge.challengeStatus.eq(Status.RECRUITING).or(challenge.challengeStatus.eq(Status.POSSIBLE))
.and(challenge.challengeBranch.eq(category)))
.groupBy(challenge)
.fetch();
}

private Long getDateBetween(QChallenge challenge){

return ChronoUnit.DAYS.between((Temporal) challenge.challengeStartDate, LocalDate.now());
}

}
Original file line number Diff line number Diff line change
@@ -1,11 +1,15 @@
package com.cmc.domains.challenge.repository;

import com.cmc.challenge.Challenge;
import com.cmc.domains.challenge.vo.ChallengeResponseVo;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Modifying;
import org.springframework.data.jpa.repository.Query;
import org.springframework.stereotype.Repository;

@Repository
public interface ChallengeRepository extends JpaRepository<Challenge, Long> {
import java.util.List;

@Repository
public interface ChallengeRepository extends JpaRepository<Challenge, Long>, ChallengeCustomRepository {

}
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,13 @@
import com.cmc.challenge.Challenge;
import com.cmc.domains.challenge.dto.request.ChallengeCreateRequestDto;
import com.cmc.domains.challenge.repository.ChallengeRepository;
import com.cmc.domains.challenge.vo.ChallengeResponseVo;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional
@RequiredArgsConstructor
Expand All @@ -20,4 +23,22 @@ public Challenge create(ChallengeCreateRequestDto req, Long memberId) {
Challenge challenge = req.toEntity();
return challengeRepository.save(challenge);
}

// 인기 챌린지 조회
public List<ChallengeResponseVo> getPopularChallenges() {

return challengeRepository.getPopularChallenges();
}

// 공식 챌린지 조회
public List<ChallengeResponseVo> getOfficialChallenges() {

return challengeRepository.getOfficialChallenges();
}

// 카테고리 별 챌린지 조회
public List<ChallengeResponseVo> getCategoryChallenges(String category) {

return challengeRepository.getCategoryChallenges(category);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.cmc.domains.challenge.vo;

import com.cmc.challenge.Challenge;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class ChallengeResponseVo {

private Challenge challenge;

private Integer participateNum;

}
Loading

0 comments on commit b9574a6

Please sign in to comment.