Skip to content

Commit

Permalink
feature: offset기반 페이지 네이션 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
khs960616 committed Oct 22, 2023
1 parent eff1d56 commit e318f82
Show file tree
Hide file tree
Showing 5 changed files with 143 additions and 26 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.ssafy.ssafsound.domain.recruit.service.RecruitService;
import com.ssafy.ssafsound.global.common.response.EnvelopeResponse;
import lombok.RequiredArgsConstructor;

import org.springframework.data.domain.Pageable;
import org.springframework.web.bind.annotation.*;

Expand Down Expand Up @@ -57,23 +58,30 @@ public EnvelopeResponse<Void> deleteRecruit(@PathVariable Long recruitId, @Authe
return EnvelopeResponse.<Void>builder().build();
}

@GetMapping
public EnvelopeResponse<GetRecruitsResDto> getRecruits(GetRecruitsReqDto getRecruitsReqDto, Pageable pageable, @Authentication AuthenticatedMember memberInfo) {
return EnvelopeResponse.<GetRecruitsResDto>builder()
.data(recruitService.getRecruits(getRecruitsReqDto, pageable, memberInfo.getMemberId()))
@GetMapping("/cursor")
public EnvelopeResponse<GetRecruitsCursorResDto> getRecruitsByCursor(GetRecruitsReqDto getRecruitsReqDto, Pageable pageable, @Authentication AuthenticatedMember memberInfo) {
return EnvelopeResponse.<GetRecruitsCursorResDto>builder()
.data(recruitService.getRecruitsByCursor(getRecruitsReqDto, pageable, memberInfo.getMemberId()))
.build();
}

@GetMapping("/offset")
public EnvelopeResponse<GetRecruitOffsetResDto> getRecruitsByOffset(GetRecruitsReqDto getRecruitsReqDto, Pageable pageable, @Authentication AuthenticatedMember memberInfo) {
return EnvelopeResponse.<GetRecruitOffsetResDto>builder()
.data(recruitService.getRecruitsByOffset(getRecruitsReqDto, pageable, memberInfo.getMemberId()))
.build();
}

@GetMapping("/my-scrap")
public EnvelopeResponse<GetRecruitsResDto> getScrapedRecruits(Long cursor, Pageable pageable, @Authentication AuthenticatedMember memberInfo) {
return EnvelopeResponse.<GetRecruitsResDto>builder()
public EnvelopeResponse<GetRecruitsCursorResDto> getScrapedRecruits(Long cursor, Pageable pageable, @Authentication AuthenticatedMember memberInfo) {
return EnvelopeResponse.<GetRecruitsCursorResDto>builder()
.data(recruitService.getScrapedRecruits(memberInfo.getMemberId(), cursor, pageable))
.build();
}

@GetMapping("/joined")
public EnvelopeResponse<GetRecruitsResDto> getMemberJoinedRecruits(GetMemberJoinRecruitsReqDto getMemberJoinRecruitsReqDto, @Authentication AuthenticatedMember memberInfo) {
return EnvelopeResponse.<GetRecruitsResDto>builder()
public EnvelopeResponse<GetRecruitsCursorResDto> getMemberJoinedRecruits(GetMemberJoinRecruitsReqDto getMemberJoinRecruitsReqDto, @Authentication AuthenticatedMember memberInfo) {
return EnvelopeResponse.<GetRecruitsCursorResDto>builder()
.data(recruitService.getMemberJoinRecruits(getMemberJoinRecruitsReqDto, memberInfo.getMemberId()))
.build();
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.ssafy.ssafsound.domain.recruit.dto;

import com.fasterxml.jackson.annotation.JsonIgnore;
import com.ssafy.ssafsound.domain.recruit.domain.Recruit;
import lombok.Builder;
import lombok.Getter;
import org.springframework.data.domain.Page;

import java.util.List;
import java.util.Map;
import java.util.TreeMap;
import java.util.stream.Collectors;

@Getter
@Builder
public class GetRecruitOffsetResDto implements AddParticipantDto {
private List<RecruitElement> recruits;
private int currentPage;
private int totalPageCount;
@JsonIgnore
public List<Long> getRecruitsId() {
return recruits.stream().map(RecruitElement::getRecruitId).collect(Collectors.toList());
}

@JsonIgnore
public Map<Long, Map<String, RecruitParticipant>> getRecruitParticipantMapByRecruitIdAndRecruitType() {
Map<Long, Map<String, RecruitParticipant>> result = new TreeMap<>();
for(RecruitElement recruitElement: recruits) {
result.put(recruitElement.getRecruitId(), recruitElement.getRecruitParticipantMap());
}
return result;
}

public static GetRecruitOffsetResDto fromPageAndMemberId(Page<Recruit> recruitPages, Long memberId) {
List<RecruitElement> recruits = recruitPages.getContent()
.stream()
.map((recruit -> RecruitElement.fromRecruitAndLoginMemberId(recruit, memberId)))
.collect(Collectors.toList());

return GetRecruitOffsetResDto.builder()
.recruits(recruits)
.currentPage(recruitPages.getNumber())
.totalPageCount(recruitPages.getTotalPages())
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@

@Getter
@Builder
public class GetRecruitsResDto implements AddParticipantDto {
public class GetRecruitsCursorResDto implements AddParticipantDto {
private List<RecruitElement> recruits;
private Long nextCursor;
private Boolean isLast;
Expand All @@ -32,14 +32,14 @@ public Map<Long, Map<String, RecruitParticipant>> getRecruitParticipantMapByRecr
return result;
}

public static GetRecruitsResDto fromPageAndMemberId(Slice<Recruit> sliceRecruit, Long memberId) {
public static GetRecruitsCursorResDto fromPageAndMemberId(Slice<Recruit> sliceRecruit, Long memberId) {
List<RecruitElement> recruits = sliceRecruit.toList()
.stream()
.map((recruit -> RecruitElement.fromRecruitAndLoginMemberId(recruit, memberId)))
.collect(Collectors.toList());
Long nextCursor = recruits.isEmpty() ? -1L : recruits.get(recruits.size()-1).getRecruitId();

return GetRecruitsResDto.builder()
return GetRecruitsCursorResDto.builder()
.recruits(recruits)
.nextCursor(nextCursor)
.isLast(sliceRecruit.isLast())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
import com.ssafy.ssafsound.global.common.exception.GlobalErrorInfo;
import com.ssafy.ssafsound.global.common.exception.ResourceNotFoundException;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
Expand Down Expand Up @@ -129,20 +130,32 @@ public void deleteRecruit(Long recruitId, Long memberId) {
}

@Transactional(readOnly = true)
public GetRecruitsResDto getRecruits(GetRecruitsReqDto getRecruitsReqDto, Pageable pageable, Long loginMemberId) {
// 페이지네이션 조건에 따라 프로젝트/스터디 글 목록을 조회한다.
public GetRecruitsCursorResDto getRecruitsByCursor(GetRecruitsReqDto getRecruitsReqDto, Pageable pageable, Long loginMemberId) {
Slice<Recruit> recruitPages = recruitRepository.findRecruitSliceByGetRecruitsReqDto(getRecruitsReqDto, pageable);
GetRecruitsResDto recruitsResDto = GetRecruitsResDto.fromPageAndMemberId(recruitPages, loginMemberId);
GetRecruitsCursorResDto recruitsResDto = GetRecruitsCursorResDto.fromPageAndMemberId(recruitPages, loginMemberId);
if(!recruitsResDto.getRecruits().isEmpty()) {
addRecruitParticipants(recruitsResDto);
}
return recruitsResDto;
}

@Transactional(readOnly = true)
public GetRecruitsResDto getScrapedRecruits(Long memberId, Long cursor, Pageable pageable) {
public GetRecruitOffsetResDto getRecruitsByOffset(GetRecruitsReqDto getRecruitsReqDto, Pageable pageable, Long loginMemberId) {
Integer pageOffset = getRecruitsReqDto.getNext();

PageRequest pageRequest = PageRequest.of(pageOffset== null || pageOffset == -1 ? 0 : pageOffset, pageable.getPageSize());
Page<Recruit> recruitPages = recruitRepository.findRecruitPageByGetRecruitsReqDto(getRecruitsReqDto, pageRequest);
GetRecruitOffsetResDto recruitsResDto = GetRecruitOffsetResDto.fromPageAndMemberId(recruitPages, loginMemberId);
if(!recruitsResDto.getRecruits().isEmpty()) {
addRecruitParticipants(recruitsResDto);
}
return recruitsResDto;
}

@Transactional(readOnly = true)
public GetRecruitsCursorResDto getScrapedRecruits(Long memberId, Long cursor, Pageable pageable) {
Slice<Recruit> recruitPages = recruitRepository.findMemberScrapRecruits(memberId, cursor, pageable);
GetRecruitsResDto recruitsResDto = GetRecruitsResDto.fromPageAndMemberId(recruitPages, memberId);
GetRecruitsCursorResDto recruitsResDto = GetRecruitsCursorResDto.fromPageAndMemberId(recruitPages, memberId);
return recruitsResDto;
}

Expand All @@ -158,7 +171,7 @@ public void expiredRecruit(Long recruitId, Long memberId) {
}

@Transactional(readOnly = true)
public GetRecruitsResDto getMemberJoinRecruits(GetMemberJoinRecruitsReqDto recruitsReqDto, Long loginMemberId) {
public GetRecruitsCursorResDto getMemberJoinRecruits(GetMemberJoinRecruitsReqDto recruitsReqDto, Long loginMemberId) {
Long memberId = recruitsReqDto.getMemberId();
Member member = memberRepository.findById(memberId).orElseThrow(()->new ResourceNotFoundException(GlobalErrorInfo.NOT_FOUND));

Expand All @@ -167,7 +180,7 @@ public GetRecruitsResDto getMemberJoinRecruits(GetMemberJoinRecruitsReqDto recru
}

Slice<Recruit> recruitPages = recruitRepository.findMemberJoinRecruitWithCursorAndPageable(memberId, recruitsReqDto.getCategory(), recruitsReqDto.getCursor(), PageRequest.ofSize(recruitsReqDto.getSize()));
GetRecruitsResDto recruitsResDto = GetRecruitsResDto.fromPageAndMemberId(recruitPages, loginMemberId);
GetRecruitsCursorResDto recruitsResDto = GetRecruitsCursorResDto.fromPageAndMemberId(recruitPages, loginMemberId);
if(!recruitsResDto.getRecruits().isEmpty()) {
addRecruitParticipants(recruitsResDto);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -200,23 +200,23 @@ void deleteRecruit() {
getEnvelopPatternWithNoContent()));
}

@DisplayName("리크루트 목록 조회")
@DisplayName("커서 기반 리크루트 목록 조회")
@Test
void getRecruits() {
doReturn(RecruitFixture.GET_RECRUITS_RES_DTO)
void getRecruitsByCursor() {
doReturn(RecruitFixture.GET_RECRUITS_CURSOR_RES_DTO)
.when(recruitService)
.getRecruits(any(), any(), any());
.getRecruitsByCursor(any(), any(), any());

restDocs
.cookie(ACCESS_TOKEN)
.when().get("/recruits?size=20&category=project&keyword=사이드&isFinished=false&recruitTypes=백엔드&recruitTypes=프론트엔드&skills=Spring&skills=React")
.when().get("/recruits/cursor?size=20&category=project&keyword=사이드&isFinished=false&recruitTypes=백엔드&recruitTypes=프론트엔드&skills=Spring&skills=React")
.then().log().all()
.assertThat()
.statusCode(HttpStatus.OK.value())
.apply(document("recruit/recruits",
requestCookieAccessTokenOptional(),
requestParameters(
parameterWithName("cursor").optional().description("다음 조회 커서 default(초기화면)에서는 미포함"),
parameterWithName("next").optional().description("조회할 커서 번호 default(초기화면)에서는 미포함"),
parameterWithName("size").description("페이징 사이즈"),
parameterWithName("category").description("카테고리 project|study"),
parameterWithName("keyword").description("리크루트 게시글 제목 검색 키워드"),
Expand Down Expand Up @@ -250,10 +250,60 @@ void getRecruits() {
);
}

@DisplayName("offset 기반 리크루트 목록 조회")
@Test
void getRecruitsByOffset() {
doReturn(RecruitFixture.GET_RECRUITS_OFFSET_RES_DTO)
.when(recruitService)
.getRecruitsByOffset(any(), any(), any());

restDocs
.cookie(ACCESS_TOKEN)
.when().get("/recruits/offset?size=20&category=project&keyword=사이드&isFinished=false&recruitTypes=백엔드&recruitTypes=프론트엔드&skills=Spring&skills=React")
.then().log().all()
.assertThat()
.statusCode(HttpStatus.OK.value())
.apply(document("recruit/recruits",
requestCookieAccessTokenOptional(),
requestParameters(
parameterWithName("next").optional().description("조회할 page 번호"),
parameterWithName("size").description("페이징 사이즈"),
parameterWithName("category").description("카테고리 project|study"),
parameterWithName("keyword").description("리크루트 게시글 제목 검색 키워드"),
parameterWithName("isFinished").description("리크루트 종료 여부"),
parameterWithName("recruitTypes").description("리크루트 모집파트, 메타데이터-리크루트 목록 조회 참고"),
parameterWithName("skills").description("리크루트와 연관된 기술 스택, 메타데이터-스킬 목록 조회 참고")
),
getEnvelopPatternWithData().andWithPrefix("data.",
fieldWithPath("currentPage").type(JsonFieldType.NUMBER).description("현재 조회한 페이지 번호"),
fieldWithPath("totalPageCount").type(JsonFieldType.NUMBER).description("total 페이지 갯수")
).andWithPrefix("data.recruits[].",
fieldWithPath("recruitId").type(JsonFieldType.NUMBER).description("리크루트 id"),
fieldWithPath("category").type(JsonFieldType.STRING).description("카테고리 project|study"),
fieldWithPath("mine").type(JsonFieldType.BOOLEAN).description("내가 쓴 글 여부(토큰 기준)"),
fieldWithPath("title").type(JsonFieldType.STRING).description("리크루트 모집글 제목, 글자 수 제한 50자"),
fieldWithPath("content").type(JsonFieldType.STRING).description("리크루트 본문 요약본 최대 50자"),
fieldWithPath("recruitEnd").type(JsonFieldType.STRING).description("yyyy-MM-dd 모집 종료 일자"),
fieldWithPath("finishedRecruit").type(JsonFieldType.BOOLEAN).description("리크루트 종료 여부"),
fieldWithPath("participants[].members[]").type(JsonFieldType.ARRAY).description("리크루트 참여 멤버 ").optional()
).andWithPrefix("data.recruits[].skills[].",
fieldWithPath("skillId").type(JsonFieldType.NUMBER).description("스킬 id 미사용"),
fieldWithPath("name").type(JsonFieldType.STRING).description("리크루트와 연관된 기술 스택명, 메타데이터-스킬 목록 조회 참고")
).andWithPrefix("data.recruits[].participants[].",
fieldWithPath("recruitType").type(JsonFieldType.STRING).description("리크루트 모집파트, 메타데이터-리크루트 목록 조회 참고"),
fieldWithPath("limit").type(JsonFieldType.NUMBER).description("리크루트 모집 인원 제한 1명이상 10명 이하")
).andWithPrefix("data.recruits[].participants[].members[].",
fieldWithPath("nickname").type(JsonFieldType.STRING).description("리크루트 참여자 닉네임"),
fieldWithPath("isMajor").type(JsonFieldType.BOOLEAN).description("리크루트 참여자 전공 여부")
)
)
);
}

@DisplayName("스크랩 된 리크루트 목록 조회")
@Test
void getScrapedRecruits() {
doReturn(RecruitFixture.GET_RECRUITS_RES_DTO)
doReturn(RecruitFixture.GET_RECRUITS_CURSOR_RES_DTO)
.when(recruitService)
.getScrapedRecruits(any(), any(), any());

Expand Down Expand Up @@ -298,7 +348,7 @@ void getScrapedRecruits() {
@DisplayName("사용자 참여 확정 리크루트 목록 조회")
@Test
void getMemberJoinedRecruits() {
doReturn(RecruitFixture.GET_RECRUITS_RES_DTO)
doReturn(RecruitFixture.GET_RECRUITS_CURSOR_RES_DTO)
.when(recruitService)
.getMemberJoinRecruits(any(), any());

Expand Down

0 comments on commit e318f82

Please sign in to comment.