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 : 공지, 랭킹 목록 조회 페이징 적용 및 잡다한 것들 #186

Merged
merged 6 commits into from
Nov 21, 2024
Merged
Show file tree
Hide file tree
Changes from all 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
@@ -1,11 +1,13 @@
package com.gamzabat.algohub.feature.group.ranking.controller;

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.gamzabat.algohub.common.annotation.AuthedUser;
Expand All @@ -26,8 +28,12 @@ public class RankingController {

@GetMapping(value = "/{groupId}/rankings")
@Operation(summary = "과제 진행도 전체순위 API")
public ResponseEntity<List<GetRankingResponse>> getAllRanking(@AuthedUser User user, @PathVariable Long groupId) {
List<GetRankingResponse> rankingResponse = rankingService.getAllRank(user, groupId);
public ResponseEntity<Page<GetRankingResponse>> getAllRanking(@AuthedUser User user, @PathVariable Long groupId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
Pageable pageable = PageRequest.of(page, size);

Page<GetRankingResponse> rankingResponse = rankingService.getAllRank(user, groupId, pageable);
return ResponseEntity.ok().body(rankingResponse);
}
}
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.gamzabat.algohub.feature.group.ranking.dto;

import com.gamzabat.algohub.feature.group.ranking.domain.Ranking;

import lombok.Getter;
import lombok.Setter;

Expand All @@ -20,4 +22,14 @@ public GetRankingResponse(String userNickname, String profileImage, Integer rank
this.solvedCount = solvedCount;
this.rankDiff = rankDiff;
}

public static GetRankingResponse toDTO(Ranking ranking) {
return new GetRankingResponse(
ranking.getMember().getUser().getNickname(),
ranking.getMember().getUser().getProfileImage(),
ranking.getCurrentRank(),
ranking.getSolvedCount(),
ranking.getRankDiff()
);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,15 @@

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import com.gamzabat.algohub.feature.group.ranking.domain.Ranking;
import com.gamzabat.algohub.feature.group.studygroup.domain.StudyGroup;

public interface CustomRankingRepository {
Page<Ranking> findAllByStudyGroup(StudyGroup studyGroup, Pageable pageable);

List<Ranking> findAllByStudyGroup(StudyGroup studyGroup);

void deleteAllByStudyGroup(StudyGroup studyGroup);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,14 @@

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.support.PageableExecutionUtils;
import org.springframework.stereotype.Repository;

import com.gamzabat.algohub.feature.group.ranking.domain.Ranking;
import com.gamzabat.algohub.feature.group.studygroup.domain.StudyGroup;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.impl.JPAQueryFactory;

Expand All @@ -20,13 +24,32 @@
public class CustomRankingRepositoryImpl implements CustomRankingRepository {
private final JPAQueryFactory queryFactory;

@Override
public Page<Ranking> findAllByStudyGroup(StudyGroup studyGroup, Pageable pageable) {
JPAQuery<Ranking> query = getRankingsQuery(studyGroup)
.orderBy(ranking.currentRank.asc())
.offset(pageable.getOffset())
.limit(pageable.getPageSize());
JPAQuery<Long> countQuery = rankingCountQuery();

return PageableExecutionUtils.getPage(query.fetch(), pageable, countQuery::fetchOne);
}

@Override
Copy link
Contributor

Choose a reason for hiding this comment

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

호옥시 이부분은 어디서 쓰나요..? 위에 페이징으로 받은 리스트가 있는데 다른 곳에서 쓰는게 있나요?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

회원이 탈퇴할 때 AOP를 사용해서 랭킹을 새로 갱신해야할 때가 있었습니다! 그 때 그룹 멤버들에 대해 페이징 없이 모든 리스트를 가져와야 해서 얘도 필요합니담

Copy link
Contributor

Choose a reason for hiding this comment

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

아아 넹! 답변감사합니두!

public List<Ranking> findAllByStudyGroup(StudyGroup studyGroup) {
return getRankingsQuery(studyGroup).fetch();
}

private JPAQuery<Ranking> getRankingsQuery(StudyGroup studyGroup) {
return queryFactory.selectFrom(ranking)
.join(ranking.member, groupMember).fetchJoin()
.join(groupMember.user, user).fetchJoin()
.where(ranking.member.studyGroup.eq(studyGroup))
.fetch();
.where(ranking.member.studyGroup.eq(studyGroup));
}

private JPAQuery<Long> rankingCountQuery() {
return queryFactory.select(ranking.count())
.from(ranking);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,9 @@
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.LocalTime;
import java.util.Comparator;
import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -37,7 +37,7 @@ public class RankingService {
public static final double SCORE_SCALING_FACTOR = 1e-4;

@Transactional(readOnly = true)
public List<GetRankingResponse> getAllRank(User user, Long groupId) {
public Page<GetRankingResponse> getAllRank(User user, Long groupId, Pageable pageable) {

StudyGroup group = groupRepository.findById(groupId)
.orElseThrow(() -> new CannotFoundGroupException("그룹을 찾을 수 없습니다."));
Expand All @@ -46,21 +46,8 @@ public List<GetRankingResponse> getAllRank(User user, Long groupId) {
throw new GroupMemberValidationException(HttpStatus.FORBIDDEN.value(), "랭킹을 확인할 권한이 없습니다.");
}

List<Ranking> ranking = rankingRepository.findAllByStudyGroup(group)
.stream()
.sorted(Comparator.comparing(Ranking::getCurrentRank))
.toList();
return getRankingResponse(ranking);
}

private List<GetRankingResponse> getRankingResponse(List<Ranking> ranking) {
return ranking.stream().map(r -> new GetRankingResponse(
r.getMember().getUser().getNickname(),
r.getMember().getUser().getProfileImage(),
r.getCurrentRank(),
r.getSolvedCount(),
r.getRankDiff()))
.toList();
return rankingRepository.findAllByStudyGroup(group, pageable)
.map(GetRankingResponse::toDTO);
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -49,10 +49,10 @@ public class StudyGroupController {
@Operation(summary = "그룹 생성 API")
public ResponseEntity<GroupCodeResponse> createGroup(@AuthedUser User user,
@Valid @RequestPart CreateGroupRequest request, Errors errors,
@RequestPart(required = false) MultipartFile profileImage) {
@RequestPart(required = false) MultipartFile groupImage) {
if (errors.hasErrors())
throw new RequestException("그룹 생성 요청이 올바르지 않습니다.", errors);
GroupCodeResponse inviteCode = studyGroupService.createGroup(user, request, profileImage);
GroupCodeResponse inviteCode = studyGroupService.createGroup(user, request, groupImage);
Copy link
Contributor

Choose a reason for hiding this comment

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

앗 이부분은 제가 했어야한건데.. 감사함돵 ㅜ.ㅜ

return ResponseEntity.ok().body(inviteCode);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,8 @@
package com.gamzabat.algohub.feature.notice.controller;

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.ResponseEntity;
import org.springframework.validation.Errors;
import org.springframework.web.bind.annotation.DeleteMapping;
Expand All @@ -11,6 +12,7 @@
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.gamzabat.algohub.common.annotation.AuthedUser;
Expand Down Expand Up @@ -55,9 +57,12 @@ public ResponseEntity<GetNoticeResponse> getNotice(@AuthedUser User user, @PathV

@GetMapping(value = "/groups/{groupId}/notices")
@Operation(summary = "공지 목록 조회 API")
public ResponseEntity<List<GetNoticeResponse>> getNoticeList(@AuthedUser User user,
@PathVariable Long groupId) {
List<GetNoticeResponse> response = noticeService.getNoticeList(user, groupId);
public ResponseEntity<Page<GetNoticeResponse>> getNoticeList(@AuthedUser User user,
@PathVariable Long groupId,
@RequestParam(defaultValue = "0") int page,
@RequestParam(defaultValue = "20") int size) {
Pageable pageable = PageRequest.of(page, size);
Page<GetNoticeResponse> response = noticeService.getNoticeList(user, groupId, pageable);
return ResponseEntity.ok().body(response);
}

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

@Builder
public record GetNoticeResponse(String author,
String authorImage,
Long noticeId,
String content,
String title,
Expand All @@ -17,6 +18,7 @@ public record GetNoticeResponse(String author,
public static GetNoticeResponse toDTO(Notice notice, boolean isRead) {
return GetNoticeResponse.builder()
.author(notice.getAuthor().getNickname())
.authorImage(notice.getAuthor().getProfileImage())
.noticeId(notice.getId())
.title(notice.getTitle())
.content(notice.getContent())
Expand Down
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
package com.gamzabat.algohub.feature.notice.repository;

import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;

import com.gamzabat.algohub.feature.group.studygroup.domain.StudyGroup;
import com.gamzabat.algohub.feature.notice.domain.Notice;

public interface NoticeRepository extends JpaRepository<Notice, Long> {
List<Notice> findAllByStudyGroup(StudyGroup studyGroup);
Page<Notice> findAllByStudyGroup(StudyGroup studyGroup, Pageable pageable);

}
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
package com.gamzabat.algohub.feature.notice.service;

import java.time.LocalDateTime;
import java.util.List;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
Expand Down Expand Up @@ -86,18 +87,17 @@ public GetNoticeResponse getNotice(@AuthedUser User user, Long noticeId) {
}

@Transactional(readOnly = true)
public List<GetNoticeResponse> getNoticeList(@AuthedUser User user, Long studyGroupId) {
public Page<GetNoticeResponse> getNoticeList(@AuthedUser User user, Long studyGroupId, Pageable pageable) {
StudyGroup studyGroup = studyGroupRepository.findById(studyGroupId)
.orElseThrow(() -> new StudyGroupValidationException(HttpStatus.BAD_REQUEST.value(), "존재하지 않는 스터디 그룹입니다"));
if (!groupMemberRepository.existsByUserAndStudyGroup(user, studyGroup))
throw new GroupMemberValidationException(HttpStatus.FORBIDDEN.value(), "참여하지 않은 스터디 그룹입니다");

List<Notice> list = noticeRepository.findAllByStudyGroup(studyGroup);
List<GetNoticeResponse> result = list.stream().map(
notice -> GetNoticeResponse.toDTO(notice, noticeReadRepository.existsByNoticeAndUser(notice, user))
).toList();
Page<Notice> list = noticeRepository.findAllByStudyGroup(studyGroup, pageable);
Page<GetNoticeResponse> responses = list.map(
notice -> GetNoticeResponse.toDTO(notice, noticeReadRepository.existsByNoticeAndUser(notice, user)));
log.info("success to get notice list");
return result;
return responses;
}

@Transactional
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Optional;

import org.junit.jupiter.api.BeforeEach;
Expand All @@ -17,6 +16,10 @@
import org.springframework.boot.test.autoconfigure.web.servlet.WebMvcTest;
import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.context.annotation.Import;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.http.HttpStatus;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.web.servlet.MockMvc;
Expand Down Expand Up @@ -60,43 +63,47 @@ void setUp() {
@DisplayName("전체 랭킹 조회 성공")
void getAllRank() throws Exception {
// given
List<GetRankingResponse> response = new ArrayList<>();
when(rankingService.getAllRank(user, groupId)).thenReturn(response);
Page<GetRankingResponse> response = new PageImpl<>(new ArrayList<>());
Pageable pageable = PageRequest.of(0, 20);
when(rankingService.getAllRank(user, groupId, pageable)).thenReturn(response);
// when, then
mockMvc.perform(get("/api/groups/{groupId}/rankings", groupId)
.header("Authorization", token))
.andExpect(status().isOk())
.andExpect(content().json(objectMapper.writeValueAsString(response)));

verify(rankingService, times(1)).getAllRank(any(User.class), anyLong());
verify(rankingService, times(1)).getAllRank(any(User.class), anyLong(), any(Pageable.class));
}

@Test
@DisplayName("전체 랭킹 조회 실패 : 그룹을 못 찾은 경우")
void getAllRankFailed_1() throws Exception {
// given
doThrow(new CannotFoundGroupException("그룹을 찾을 수 없습니다.")).when(rankingService).getAllRank(user, groupId);
Pageable pageable = PageRequest.of(0, 20);
doThrow(new CannotFoundGroupException("그룹을 찾을 수 없습니다.")).when(rankingService)
.getAllRank(user, groupId, pageable);
// when, then
mockMvc.perform(get("/api/groups/{groupId}/rankings", groupId)
.header("Authorization", token))
.andExpect(status().isNotFound())
.andExpect(jsonPath("$.error").value("그룹을 찾을 수 없습니다."));

verify(rankingService, times(1)).getAllRank(any(User.class), anyLong());
verify(rankingService, times(1)).getAllRank(any(User.class), anyLong(), any(Pageable.class));
}

@Test
@DisplayName("전체 랭킹 조회 실패 : 랭킹 확인 권한이 없는 경우")
void getAllRankFailed_2() throws Exception {
// given
Pageable pageable = PageRequest.of(0, 20);
doThrow(new GroupMemberValidationException(HttpStatus.FORBIDDEN.value(), "랭킹을 확인할 권한이 없습니다.")).when(
rankingService).getAllRank(user, groupId);
rankingService).getAllRank(user, groupId, pageable);
// when, then
mockMvc.perform(get("/api/groups/{groupId}/rankings", groupId)
.header("Authorization", token))
.andExpect(status().isForbidden())
.andExpect(jsonPath("$.error").value("랭킹을 확인할 권한이 없습니다."));

verify(rankingService, times(1)).getAllRank(any(User.class), anyLong());
verify(rankingService, times(1)).getAllRank(any(User.class), anyLong(), any(Pageable.class));
}
}
Loading
Loading