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: 마이프로필 및 리크루트 데이터포맷 수정 #248

Merged
merged 16 commits into from
Sep 20, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
16 commits
Select commit Hold shift + click to select a range
5d720fa
rename: match status 네이밍 수정
khs960616 Sep 7, 2023
cd83093
rename: 상태 리네이밍 테스트 코드 반영
khs960616 Sep 7, 2023
0a18a07
feat: dynamic query
khs960616 Sep 7, 2023
119c74b
refactor: 리크루트 목록 조회 query dsl로 리팩토링 및 사용자 참여 리크루트 조회 쿼리 추가
khs960616 Sep 9, 2023
04c0233
refactor: 사용자 참여중 리크루트 목록 조회 구현에 따른 서비스 및 컨트롤러 수정
khs960616 Sep 9, 2023
98e776a
test: dto 및 테스트 코드 추가 사항 반영
khs960616 Sep 9, 2023
209822d
feat: 사용자 스크랩 리크루트 조회 api 추가
khs960616 Sep 9, 2023
581f7ad
test: 스크랩 테스트 케이스 추가
khs960616 Sep 9, 2023
0ba7abd
fix: 리크투르 등록 시 카테고리 정보 upper case 추가
khs960616 Sep 20, 2023
3d593ca
fix: 리크루트 목록 조회 시 카테고리 정보 추가
khs960616 Sep 20, 2023
e76c221
fix: 리크루트 목록 조회 mine 추가
khs960616 Sep 20, 2023
de7e250
fix: 리크루트 카테고리가 study인 경우 모집타입 무시하도록 수정
khs960616 Sep 20, 2023
02a634a
fix: 리크루트 상세 조회시 로그인 유저의 매칭 상태 추가
khs960616 Sep 20, 2023
7c0cc82
fix: 리크루트 참여자 조회 시 참여신청서 조회 및 최종 수정일 정보 추가
khs960616 Sep 20, 2023
ed520f2
fix: 리크루트 참여신청 조회 시 생성일 수정일 추가
khs960616 Sep 20, 2023
0a66929
docs: api 문서 수정사항 반영
khs960616 Sep 20, 2023
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
Expand Up @@ -57,9 +57,16 @@ public EnvelopeResponse<Void> deleteRecruit(@PathVariable Long recruitId, @Authe
}

@GetMapping
public EnvelopeResponse<GetRecruitsResDto> getRecruits(GetRecruitsReqDto getRecruitsReqDto, Pageable pageable) {
public EnvelopeResponse<GetRecruitsResDto> getRecruits(GetRecruitsReqDto getRecruitsReqDto, Pageable pageable, @Authentication AuthenticatedMember memberInfo) {
return EnvelopeResponse.<GetRecruitsResDto>builder()
.data(recruitService.getRecruits(getRecruitsReqDto, pageable))
.data(recruitService.getRecruits(getRecruitsReqDto, pageable, memberInfo == null ? null : memberInfo.getMemberId()))
.build();
}

@GetMapping("/scraped")
public EnvelopeResponse<GetRecruitsResDto> getScrapedRecruits(Long cursor, Pageable pageable, @Authentication AuthenticatedMember memberInfo) {
return EnvelopeResponse.<GetRecruitsResDto>builder()
.data(recruitService.getScrapedRecruits(memberInfo == null ? null : memberInfo.getMemberId(), cursor, pageable))
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,8 @@
import com.ssafy.ssafsound.domain.recruit.domain.RecruitQuestion;
import com.ssafy.ssafsound.domain.recruit.exception.RecruitErrorInfo;
import com.ssafy.ssafsound.domain.recruit.exception.RecruitException;
import com.ssafy.ssafsound.domain.recruitapplication.domain.MatchStatus;
import com.ssafy.ssafsound.domain.recruitapplication.domain.RecruitApplication;
import lombok.Builder;
import lombok.Getter;

Expand Down Expand Up @@ -33,8 +35,9 @@ public class GetRecruitDetailResDto {
private AuthorElement author;
private long scrapCount;
private Boolean scraped;
private String matchStatus;

public static GetRecruitDetailResDto of(Recruit recruit, long scrapCount, Boolean scraped) {
public static GetRecruitDetailResDto of(Recruit recruit, long scrapCount, Boolean scraped, RecruitApplication recruitApplication) {

if(recruit.getDeletedRecruit()) throw new RecruitException(RecruitErrorInfo.IS_DELETED);

Expand All @@ -52,6 +55,10 @@ public static GetRecruitDetailResDto of(Recruit recruit, long scrapCount, Boolea
addRegisterRecruitType(limits, registerRecruitType);
List<String> questions = recruit.getQuestions().stream().map(RecruitQuestion::getContent).collect(Collectors.toList());

String matchStatus = (recruitApplication != null) ? recruitApplication.getMatchStatus().name() : MatchStatus.INITIAL.name();
if(matchStatus.equals(MatchStatus.CANCEL.name())) {
matchStatus = MatchStatus.INITIAL.name();
}
return GetRecruitDetailResDto.builder()
.category(recruit.getCategory().name())
.recruitId(recruit.getId())
Expand All @@ -68,6 +75,7 @@ public static GetRecruitDetailResDto of(Recruit recruit, long scrapCount, Boolea
.author(new AuthorElement(register, false))
.scrapCount(scrapCount)
.scraped(scraped)
.matchStatus(matchStatus)
.build();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@
@Builder
public class GetRecruitsReqDto {
private Long cursor;
private Long memberId;

@NotBlank
private String category;
private String keyword;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,10 @@ public Map<Long, Map<String, RecruitParticipant>> getRecruitParticipantMapByRecr
return result;
}

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

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public class PostRecruitReqDto {
public Recruit to() {
Recruit recruit = Recruit.builder()
.view(0L)
.category(Category.valueOf(category))
.category(Category.valueOf(category.toUpperCase()))
.title(title)
.content(content)
.startDateTime(LocalDateTime.now())
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,12 +16,14 @@
@Builder
public class RecruitElement {
private Long recruitId;
private String category;
private String title;
private boolean finishedRecruit;
private String recruitEnd;
private String content;
private List<RecruitSkillElement> skills;
private List<RecruitParticipant> participants;
private Boolean mine;

@JsonIgnore
public Map<String, RecruitParticipant> getRecruitParticipantMap() {
Expand All @@ -33,7 +35,7 @@ public Map<String, RecruitParticipant> getRecruitParticipantMap() {
return recruitTypeMaps;
}

public static RecruitElement from(Recruit recruit) {
public static RecruitElement fromRecruitAndLoginMemberId(Recruit recruit, Long memberId) {
List<RecruitSkillElement> skills = recruit.getSkills()
.stream()
.map(RecruitSkillElement::from).collect(Collectors.toList());
Expand All @@ -49,12 +51,14 @@ public static RecruitElement from(Recruit recruit) {

return RecruitElement.builder()
.recruitId(recruit.getId())
.category(recruit.getCategory().name())
.title(recruit.getTitle())
.finishedRecruit(recruit.getFinishedRecruit())
.recruitEnd(recruit.getEndDateTime().format(DateTimeFormatter.ofPattern("yyyy-MM-dd")))
.content(content)
.participants(recruitParticipants)
.skills(skills)
.mine(recruit.getMember().getId().equals(memberId))
.build();
}

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

public interface RecruitDynamicQueryRepository {
Slice<Recruit> findRecruitByGetRecruitsReqDto(GetRecruitsReqDto dto, Pageable pageable);
Slice<Recruit> findMemberJoinRecruitWithCursorAndPageable(Long memberId, Long cursorId, Pageable pageable);
Slice<Recruit> findMemberScrapRecruits(Long memberId, Long cursorId, Pageable pageable);
}
Original file line number Diff line number Diff line change
@@ -1,78 +1,139 @@
package com.ssafy.ssafsound.domain.recruit.repository;

import com.querydsl.core.types.dsl.BooleanExpression;
import com.querydsl.jpa.JPAExpressions;
import com.querydsl.jpa.JPQLQuery;
import com.querydsl.jpa.impl.JPAQuery;
import com.querydsl.jpa.impl.JPAQueryFactory;
import com.ssafy.ssafsound.domain.meta.domain.MetaData;
import com.ssafy.ssafsound.domain.meta.domain.MetaDataType;
import com.ssafy.ssafsound.domain.meta.service.MetaDataConsumer;
import com.ssafy.ssafsound.domain.recruit.domain.Category;
import com.ssafy.ssafsound.domain.recruit.domain.Recruit;
import com.ssafy.ssafsound.domain.recruit.dto.GetRecruitsReqDto;
import org.springframework.beans.factory.annotation.Autowired;
import com.ssafy.ssafsound.domain.recruitapplication.domain.MatchStatus;
import lombok.RequiredArgsConstructor;

import lombok.extern.slf4j.Slf4j;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Slice;
import org.springframework.data.domain.SliceImpl;
import org.springframework.stereotype.Repository;
import org.springframework.util.StringUtils;

import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;
import javax.persistence.criteria.CriteriaBuilder;
import javax.persistence.criteria.CriteriaQuery;
import javax.persistence.criteria.Predicate;
import javax.persistence.criteria.Root;
import java.util.ArrayList;
import java.util.List;
import java.util.stream.Collectors;

import static com.ssafy.ssafsound.domain.recruit.domain.QRecruit.recruit;
import static com.ssafy.ssafsound.domain.recruit.domain.QRecruitSkill.recruitSkill;
import static com.ssafy.ssafsound.domain.recruit.domain.QRecruitLimitation.recruitLimitation;
import static com.ssafy.ssafsound.domain.recruit.domain.QRecruitScrap.recruitScrap;
import static com.ssafy.ssafsound.domain.recruitapplication.domain.QRecruitApplication.recruitApplication;
import static com.ssafy.ssafsound.domain.member.domain.QMember.member;


@Repository
@RequiredArgsConstructor
@Slf4j
public class RecruitDynamicQueryRepositoryImpl implements RecruitDynamicQueryRepository {

@Autowired
MetaDataConsumer metaDataConsumer;
private final MetaDataConsumer metaDataConsumer;

@PersistenceContext
private EntityManager entityManager;
private final JPAQueryFactory jpaQueryFactory;

@Override
public Slice<Recruit> findRecruitByGetRecruitsReqDto(GetRecruitsReqDto dto, Pageable pageable) {
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Recruit> cq = cb.createQuery(Recruit.class);

Root<Recruit> root = cq.from(Recruit.class);
List<Predicate> predicates = new ArrayList<>();

/* 정적 검색 조건 처리 -> 카테고리 */
Predicate category = cb.equal(root.get("category"), Category.valueOf(dto.getCategory().toUpperCase()));
predicates.add(category);
/* 동적 검색 조건 처리 -> 커서, 검색 키워드, 모집파트, 모집중 여부 */
if(dto.getCursor() != null) {
Predicate cursorInfo = cb.lessThan(root.get("id"), dto.getCursor());
predicates.add(cursorInfo);
}
// cursor base pagination (value -1 or null ignore search condition)
Long cursor = dto.getCursor();

if(StringUtils.hasText(dto.getKeyword())) {
Predicate titleContainKeyword = cb.like(root.get("title"), "%"+dto.getKeyword()+"%");
predicates.add(titleContainKeyword);
}
// recruit category (STUDY | PROJECT)
BooleanExpression categoryEq = recruit.category.eq(Category.valueOf(dto.getCategory().toUpperCase()));

// recruit title contains search keyword
String keyword = dto.getKeyword();
BooleanExpression titleEq = StringUtils.hasText(keyword) ? recruit.title.contains(keyword) : null;

JPAQuery<Recruit> recruitDynamicQuery = jpaQueryFactory.selectFrom(recruit)
.where(recruitIdLtThanCursor(cursor), categoryEq, titleEq);

// recruit skill
List<String> skills = dto.getSkills();
if(skills!=null && !skills.isEmpty()) {
predicates.add(root.get("skills").in(skills));
if(skills!=null && skills.size() > 0) {
String metaDataType = MetaDataType.SKILL.name();
List<MetaData> containSkills = skills.stream()
.map(skillName->metaDataConsumer.getMetaData(metaDataType, skillName))
.collect(Collectors.toList());

JPQLQuery<Long> recruitSkillContainRecruitIds = JPAExpressions
.select(recruitSkill.recruit.id)
.from(recruitSkill)
.innerJoin(recruitSkill.recruit, recruit)
.where(recruitSkill.skill.in(containSkills));

recruitDynamicQuery.where(recruit.id.in(recruitSkillContainRecruitIds));
}

// recruit types limitation
List<String> recruitTypes = dto.getRecruitTypes();
if(recruitTypes!=null && !recruitTypes.isEmpty()) {
List<MetaData> limitTypes = recruitTypes.stream().map(
type->metaDataConsumer.getMetaData(MetaDataType.RECRUIT_TYPE.name(), type)
).collect(Collectors.toList());
predicates.add(root.get("limitations").get("type").in(limitTypes));
if(dto.getCategory().toUpperCase().equals(Category.PROJECT.name()) && recruitTypes!=null && !recruitTypes.isEmpty()) {
String metaDataType = MetaDataType.RECRUIT_TYPE.name();
List<MetaData> containRecruitTypes = recruitTypes.stream()
.map(recruitType->metaDataConsumer.getMetaData(metaDataType, recruitType))
.collect(Collectors.toList());

JPQLQuery<Long> limitationContainRecruitIds = JPAExpressions
.select(recruitLimitation.recruit.id)
.from(recruitLimitation)
.innerJoin(recruitLimitation.recruit, recruit)
.where(recruitLimitation.type.in(containRecruitTypes));

recruitDynamicQuery.where(recruit.id.in(limitationContainRecruitIds));
}
cq.where(predicates.toArray(new Predicate[0]));
cq.orderBy(cb.desc(root.get("id")));

TypedQuery<Recruit> query = entityManager.createQuery(cq);
List<Recruit> recruits = query.setMaxResults(pageable.getPageSize()+1)
.getResultList();
List<Recruit> recruits = recruitDynamicQuery
.limit(pageable.getPageSize()+1)
.orderBy(recruit.id.desc())
.fetch();
boolean hasNext = pageable.isPaged() && recruits.size() > pageable.getPageSize();
return new SliceImpl<>(hasNext ? recruits.subList(0, pageable.getPageSize()) : recruits, pageable, hasNext);
}

@Override
public Slice<Recruit> findMemberJoinRecruitWithCursorAndPageable(Long memberId, Long cursor, Pageable pageable) {
List<Long> memberJoinRecruitIds = jpaQueryFactory.select(recruitApplication.recruit.id)
.from(recruitApplication)
.innerJoin(recruitApplication.recruit, recruit)
.innerJoin(recruitApplication.member, member)
.where(recruitApplication.member.id.eq(memberId), recruitApplication.matchStatus.eq(MatchStatus.DONE))
.fetch();

List<Recruit> recruits = jpaQueryFactory.selectFrom(recruit)
.innerJoin(recruit.member, member)
.where(recruitIdLtThanCursor(cursor), recruit.id.in(memberJoinRecruitIds), recruit.member.id.eq(memberId))
.limit(pageable.getPageSize()+1)
.orderBy(recruit.id.desc())
.fetch();

boolean hasNext = pageable.isPaged() && recruits.size() > pageable.getPageSize();
return new SliceImpl<>(hasNext ? recruits.subList(0, pageable.getPageSize()) : recruits, pageable, hasNext);
}

@Override
public Slice<Recruit> findMemberScrapRecruits(Long memberId, Long cursor, Pageable pageable) {
List<Recruit> recruits = jpaQueryFactory.select(recruitScrap.recruit)
.from(recruitScrap)
.innerJoin(recruitScrap.recruit, recruit)
.innerJoin(recruitScrap.member, member)
.where(recruitIdLtThanCursor(cursor), recruitScrap.member.id.eq(memberId))
.limit(pageable.getPageSize()+1)
.orderBy(recruit.id.desc())
.fetch();

boolean hasNext = pageable.isPaged() && recruits.size() > pageable.getPageSize();
return new SliceImpl<>(hasNext ? recruits.subList(0, pageable.getPageSize()) : recruits, pageable, hasNext);
}

private BooleanExpression recruitIdLtThanCursor(Long cursor) {
return ((cursor != null) && (cursor != -1)) ? recruit.id.lt(cursor) : null;
}
Copy link
Collaborator

Choose a reason for hiding this comment

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

if (cursor == null) return null;
if (cursor == -1) return null;
return recruit.id.lt(cursor);

가 낫지 않을까요

}
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.ssafy.ssafsound.domain.recruit.service;

import com.ssafy.ssafsound.domain.member.domain.Member;
import com.ssafy.ssafsound.domain.member.exception.MemberErrorInfo;
import com.ssafy.ssafsound.domain.member.exception.MemberException;
import com.ssafy.ssafsound.domain.member.repository.MemberRepository;
import com.ssafy.ssafsound.domain.meta.domain.MetaData;
import com.ssafy.ssafsound.domain.meta.domain.MetaDataType;
Expand Down Expand Up @@ -72,8 +74,10 @@ public GetRecruitDetailResDto getRecruitDetail(Long recruitId, Long memberId) {
.orElseThrow(()->new ResourceNotFoundException(GlobalErrorInfo.NOT_FOUND));
long scrapCount = recruitScrapRepository.countByRecruitId(recruitId);
Boolean scraped = recruitScrapRepository.existsByRecruitIdAndMemberId(recruitId, memberId);

RecruitApplication recruitApplication = recruitApplicationRepository.findTopByRecruitIdAndMemberIdOrderByIdDesc(recruitId, memberId);
recruit.increaseView();
return GetRecruitDetailResDto.of(recruit, scrapCount, scraped);
return GetRecruitDetailResDto.of(recruit, scrapCount, scraped, recruitApplication);
}

@Transactional
Expand Down Expand Up @@ -123,16 +127,37 @@ public void deleteRecruit(Long recruitId, Long memberId) {
}

@Transactional(readOnly = true)
public GetRecruitsResDto getRecruits(GetRecruitsReqDto getRecruitsReqDto, Pageable pageable) {
public GetRecruitsResDto getRecruits(GetRecruitsReqDto getRecruitsReqDto, Pageable pageable, Long loginMemberId) {
// 페이지네이션 조건에 따라 프로젝트/스터디 글 목록을 조회한다.
Slice<Recruit> recruitPages = recruitRepository.findRecruitByGetRecruitsReqDto(getRecruitsReqDto, pageable);
GetRecruitsResDto recruitsResDto = GetRecruitsResDto.fromPage(recruitPages);
Slice<Recruit> recruitPages;
Long memberId = getRecruitsReqDto.getMemberId();

// 사용자 Id 여부로 프로필, 사용자가 참여한 리크루트 목록 조회, 일반 글 목록 조회를 구분한다.
if(memberId != null) {
Member member = memberRepository.findById(memberId).orElseThrow(()->new ResourceNotFoundException(GlobalErrorInfo.NOT_FOUND));
if(!member.getPublicProfile() && !memberId.equals(loginMemberId)) {
throw new MemberException(MemberErrorInfo.MEMBER_PROFILE_SECRET);
}

recruitPages = recruitRepository.findMemberJoinRecruitWithCursorAndPageable(memberId, getRecruitsReqDto.getCursor(), pageable);
} else {
recruitPages = recruitRepository.findRecruitByGetRecruitsReqDto(getRecruitsReqDto, pageable);
Copy link
Collaborator

Choose a reason for hiding this comment

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

recruitRepository.findMemberJoinRecruitWithCursorAndPageable(memberId, getRecruitsReqDto.getCursor(), pageable) 에서는 커서를 꺼내서 파라미터를 넣어주는 여기서는 그렇게 안하네여?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

마이프로필쪽 api라 아직 확정되진 않았습니다. 다음 pr때 프론트랑 연동하면서 작업방향 정해지면 수정될 수 있습니다.

}

GetRecruitsResDto recruitsResDto = GetRecruitsResDto.fromPageAndMemberId(recruitPages, memberId);
if(!recruitsResDto.getRecruits().isEmpty()) {
addRecruitParticipants(recruitsResDto);
}
return recruitsResDto;
}

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

@Transactional
public void expiredRecruit(Long recruitId, Long memberId) {
Recruit recruit = recruitRepository.findByIdUsingFetchJoinRegister(recruitId)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
package com.ssafy.ssafsound.domain.recruitapplication.domain;

public enum MatchStatus {
WAITING_REGISTER_APPROVE, DONE, REJECT, CANCEL
PENDING, DONE, REJECT, CANCEL, INITIAL
}
Loading