diff --git a/src/main/java/com/gongjakso/server/domain/contest/controller/ContestController.java b/src/main/java/com/gongjakso/server/domain/contest/controller/ContestController.java index e3c2fb07..32c994c6 100644 --- a/src/main/java/com/gongjakso/server/domain/contest/controller/ContestController.java +++ b/src/main/java/com/gongjakso/server/domain/contest/controller/ContestController.java @@ -9,6 +9,8 @@ import com.gongjakso.server.global.security.PrincipalDetails; import io.lettuce.core.dynamic.annotation.Param; import io.swagger.v3.oas.annotations.Operation; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Pageable; @@ -31,19 +33,22 @@ public ApplicationResponse create(@AuthenticationPrincipal PrincipalDetail contestService.save(principalDetails.getMember(),image,contestReq); return ApplicationResponse.created(); } + @Operation(description = "공모전 정보 API") @GetMapping("/{contest_id}") - public ApplicationResponse find(@PathVariable Long contest_id){ - return ApplicationResponse.ok(contestService.find(contest_id)); + public ApplicationResponse find(@PathVariable(name = "contest_id") Long contest_id, HttpServletRequest request, HttpServletResponse response){ + return ApplicationResponse.ok(contestService.find(contest_id,request,response)); } + @Operation(description = "공모전 검색 API") @GetMapping("/search") public ApplicationResponse search( @RequestParam(name = "word", defaultValue = "공모전") String word, - @RequestParam(name = "arrange", defaultValue = "createdAt") String arrange, + @RequestParam(name = "sortAt", defaultValue = "createdAt") String sortAt, @PageableDefault(size = 12,page = 0) Pageable pageable){ - return ApplicationResponse.ok(contestService.search(word,arrange,pageable)); + return ApplicationResponse.ok(contestService.search(word,sortAt,pageable)); } + @Operation(description = "공모전 수정 API - 관리자만") @PatchMapping("/{contest_id}") public ApplicationResponse update(@AuthenticationPrincipal PrincipalDetails principalDetails,@PathVariable Long contest_id,@RequestPart(required = false) MultipartFile image,@Valid @RequestPart UpdateContestDto contestReq){ diff --git a/src/main/java/com/gongjakso/server/domain/contest/dto/response/ContestRes.java b/src/main/java/com/gongjakso/server/domain/contest/dto/response/ContestRes.java index a5273c1b..33c52922 100644 --- a/src/main/java/com/gongjakso/server/domain/contest/dto/response/ContestRes.java +++ b/src/main/java/com/gongjakso/server/domain/contest/dto/response/ContestRes.java @@ -16,7 +16,8 @@ public record ContestRes( LocalDate startedAt, LocalDate finishedAt, String dayState, - String imgUrl + String imgUrl, + int viewCount ) { public static String text(LocalDate finishedAt){ long remainDay = ChronoUnit.DAYS.between(LocalDate.now(),finishedAt); @@ -38,6 +39,7 @@ public static ContestRes of(Contest contest){ .finishedAt(contest.getFinishedAt()) .imgUrl(contest.getImgUrl()) .dayState(text(contest.getFinishedAt())) + .viewCount(contest.getView()) .build(); } } diff --git a/src/main/java/com/gongjakso/server/domain/contest/entity/Contest.java b/src/main/java/com/gongjakso/server/domain/contest/entity/Contest.java index 16ce6090..0d323dab 100644 --- a/src/main/java/com/gongjakso/server/domain/contest/entity/Contest.java +++ b/src/main/java/com/gongjakso/server/domain/contest/entity/Contest.java @@ -37,6 +37,8 @@ public class Contest extends BaseTimeEntity { private LocalDate finishedAt; @Column(name = "img_url",columnDefinition = "text") private String imgUrl; + @Column(name = "view",columnDefinition = "bigint") + private int view; public void update(UpdateContestDto contest,String imgUrl){ this.title= (contest.title()==null) ? this.title : contest.title(); @@ -48,8 +50,12 @@ public void update(UpdateContestDto contest,String imgUrl){ this.imgUrl= (imgUrl==null) ? this.imgUrl : imgUrl; } + public void updateView(Contest contest){ + this.view = contest.getView() + 1; + } + @Builder - public Contest(String title,String body,String contestLink,String institution,LocalDate startedAt,LocalDate finishedAt,String imgUrl){ + public Contest(String title,String body,String contestLink,String institution,LocalDate startedAt,LocalDate finishedAt,String imgUrl,int view){ this.title=title; this.body=body; this.contestLink=contestLink; @@ -57,5 +63,6 @@ public Contest(String title,String body,String contestLink,String institution,Lo this.startedAt=startedAt; this.finishedAt=finishedAt; this.imgUrl=imgUrl; + this.view=view; } } diff --git a/src/main/java/com/gongjakso/server/domain/contest/repository/ContestRepository.java b/src/main/java/com/gongjakso/server/domain/contest/repository/ContestRepository.java index 94dac622..10c3ed6b 100644 --- a/src/main/java/com/gongjakso/server/domain/contest/repository/ContestRepository.java +++ b/src/main/java/com/gongjakso/server/domain/contest/repository/ContestRepository.java @@ -1,6 +1,7 @@ package com.gongjakso.server.domain.contest.repository; import com.gongjakso.server.domain.contest.entity.Contest; + import org.springframework.data.jpa.repository.JpaRepository; public interface ContestRepository extends JpaRepository, ContestRepositoryCustom { diff --git a/src/main/java/com/gongjakso/server/domain/contest/repository/ContestRepositoryImpl.java b/src/main/java/com/gongjakso/server/domain/contest/repository/ContestRepositoryImpl.java index 063b88f9..12d72885 100644 --- a/src/main/java/com/gongjakso/server/domain/contest/repository/ContestRepositoryImpl.java +++ b/src/main/java/com/gongjakso/server/domain/contest/repository/ContestRepositoryImpl.java @@ -24,12 +24,12 @@ public ContestRepositoryImpl(EntityManager em) { //최신순으로 정렬 //제목,본문 기준으로 검색 @Override - public Page searchList(String word, String arrange, Pageable pageable) { + public Page searchList(String word, String sortAt, Pageable pageable) { List contestList = queryFactory .selectDistinct(contest) .from(contest) .where(wordEq(word)) - .orderBy(arg(arrange)) + .orderBy(arg(sortAt)) .offset(pageable.getOffset()) .limit(pageable.getPageSize()) .fetch(); @@ -42,10 +42,10 @@ public Page searchList(String word, String arrange, Pageable pageable) return new PageImpl<>(contestList,pageable,total); } - private OrderSpecifier arg(String arrange){ -// if("A".equals(arrange)){ -// return //조회순 -// } + private OrderSpecifier arg(String sortAt){ + if("VIEW".equals(sortAt)){ + return contest.view.asc();//조회순 + } return contest.createdAt.desc(); //최신순 } diff --git a/src/main/java/com/gongjakso/server/domain/contest/service/ContestService.java b/src/main/java/com/gongjakso/server/domain/contest/service/ContestService.java index dda0584c..31802d09 100644 --- a/src/main/java/com/gongjakso/server/domain/contest/service/ContestService.java +++ b/src/main/java/com/gongjakso/server/domain/contest/service/ContestService.java @@ -12,6 +12,9 @@ import com.gongjakso.server.global.exception.ApplicationException; import com.gongjakso.server.global.exception.ErrorCode; import com.gongjakso.server.global.util.s3.S3Client; +import jakarta.servlet.http.Cookie; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -29,7 +32,7 @@ public class ContestService { private final ContestRepository contestRepository; private final S3Client s3Client; - private final String S3_CONTEST_DIR_NAME = "contest"; + private final String S3_CONTEST_DIR_NAME = "contest"; @Transactional @@ -53,14 +56,18 @@ public void save(Member member, MultipartFile image,ContestReq contestReq){ .startedAt(contestReq.startedAt()) .finishedAt(contestReq.finishedAt()) .imgUrl(s3Url) + .view(0) .build(); contestRepository.save(contest); } @Transactional - public ContestRes find(Long id){ + public ContestRes find(Long id, HttpServletRequest request, HttpServletResponse response){ //Vaildation Contest contest = contestRepository.findById(id).orElseThrow(()-> new ApplicationException(ErrorCode.NOT_FOUND_EXCEPTION)); + //Business + //조회수 업데이트 + updateView(contest,request,response); //Response return ContestRes.of(contest); } @@ -97,12 +104,36 @@ public void delete(Member member,Long id){ } @Transactional - public ContestListRes search(String word, String arrange, Pageable pageable){ + public ContestListRes search(String word, String sortAt, Pageable pageable){ //Business - Page contestPage = contestRepository.searchList(word, arrange, pageable); + Page contestPage = contestRepository.searchList(word, sortAt, pageable); List list = new ArrayList<>(); contestPage.getContent().forEach(contest-> list.add(ContestCard.of(contest))); //Response return ContestListRes.of(list,contestPage.getNumber(),contestPage.getTotalElements(), contestPage.getTotalPages()); } + + public void updateView(Contest contest, HttpServletRequest request, HttpServletResponse response) { + boolean hasViewed = false; + Cookie[] cookies = request.getCookies(); + + String COOKIE_NAME = "contest_view"; + if (cookies != null) { + for (Cookie cookie : cookies) { + if (cookie.getName().equals(COOKIE_NAME)) { + hasViewed = true; + break; + } + } + } + + if (!hasViewed) { + contest.updateView(contest); + // 세션 쿠키 설정 + Cookie newCookie = new Cookie(COOKIE_NAME, "viewed"); + newCookie.setMaxAge(-1); // 브라우저 세션이 끝날 때까지 유효 + newCookie.setPath("/"); + response.addCookie(newCookie); + } + } } diff --git a/src/main/java/com/gongjakso/server/global/config/CacheConfig.java b/src/main/java/com/gongjakso/server/global/config/CacheConfig.java index 962dbf15..beabd39f 100644 --- a/src/main/java/com/gongjakso/server/global/config/CacheConfig.java +++ b/src/main/java/com/gongjakso/server/global/config/CacheConfig.java @@ -6,6 +6,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; + @EnableCaching @Configuration public class CacheConfig { @@ -19,4 +20,5 @@ public CacheManager cacheManager() { return cacheManager; } + } diff --git a/src/main/java/com/gongjakso/server/global/util/redis/RedisClient.java b/src/main/java/com/gongjakso/server/global/util/redis/RedisClient.java index 5a3498a3..2ed602b5 100644 --- a/src/main/java/com/gongjakso/server/global/util/redis/RedisClient.java +++ b/src/main/java/com/gongjakso/server/global/util/redis/RedisClient.java @@ -6,7 +6,7 @@ import org.springframework.stereotype.Component; import java.time.Duration; -import java.util.Objects; + @Component @RequiredArgsConstructor @@ -47,4 +47,5 @@ public String getValue(String key) { public void deleteValue(String key) { redisTemplate.delete(key); } + }