Skip to content
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
1 change: 0 additions & 1 deletion src/main/java/UMC/career_mate/domain/recruit/Recruit.java
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ public class Recruit {
// 채용 공고 목록 조회 시 아래 4개만 반환
private String companyName; // 회사명
private String title; // 공고 제목
private String imageUrl; // 공고 이미지
private LocalDateTime deadLine; // 마감일

// 요약 페이지 조회 시 아래 데이터 추가 반환
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,22 +29,34 @@ public class RecruitController {
private final RecruitCommandService recruitCommandService;
private final RecruitQueryService recruitQueryService;

/**
* TODO: MemberEducationLevel, EducationStatus 멤버 완성 삭제 후 서비스에서 처리
*/
@Operation(summary = "추천 채용 공고 조회 API", description = "추천 채용 공고를 조회하는 API입니다.")
@Operation(
summary = "추천 채용 공고 조회 API",
description = """
추천 채용 공고를 조회하는 API입니다.\n\n
page의 값은 1부터 시작이고, 기본 값은 1입니다.\n\n
size의 기본값은 6입니다.\n\n
정렬의 경우\n\n
전체 (기본 값) -> POSTING_DESC\n\n
마감 빠른 순 -> DEADLINE_ASC\n\n
마감 늦은 순 -> DEADLINE_DESC
""")
@GetMapping
public ApiResponse<PageResponseDTO<List<RecommendRecruitDTO>>> getRecommendRecruitList(
@RequestParam(defaultValue = "1", required = false) int page,
@RequestParam(defaultValue = "6", required = false) int size,
@RequestParam RecruitSortType recruitSortType,
@RequestParam(defaultValue = "POSTING_DESC", required = false) RecruitSortType recruitSortType,
@LoginMember Member member
) {
return ApiResponse.onSuccess(
recruitQueryService.getRecommendRecruitList(page, size, recruitSortType, member));
}

@Operation(summary = "채용 공고 요약 페이지 조회 API", description = "채용 공고 요약 페이지를 조회하는 API입니다.")
@Operation(
summary = "채용 공고 요약 페이지 조회 API",
description = """
채용 공고 요약 페이지를 조회하는 API입니다.\n\n
recruitId : 조회하려는 채용 공고 pk 값
""")
@GetMapping("/{recruitId}")
public ResponseEntity<ApiResponse<RecruitInfoDTO>> getRecruitInfo(@PathVariable Long recruitId, @LoginMember Member member) {
return ResponseEntity.ok(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,10 @@
import UMC.career_mate.domain.recruit.dto.response.RecruitInfoDTO;
import UMC.career_mate.domain.recruit.enums.EducationLevel;
import java.time.Instant;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.Arrays;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringEscapeUtils;
Expand All @@ -22,7 +24,6 @@ public static Recruit toRecruit(Job job, String recruitUrl, String companyInfoUr
return Recruit.builder()
.companyName(job.company().detail().name())
.title(job.position().title())
.imageUrl(null)
.deadLine(LocalDateTime.ofInstant(Instant.ofEpochSecond(job.expirationTimestamp()),
ZoneId.systemDefault()))
.companyInfoUrl(companyInfoUrl)
Expand All @@ -46,20 +47,19 @@ public static Recruit toRecruit(Job job, String recruitUrl, String companyInfoUr
.build();
}

public static RecommendRecruitDTO toRecommendRecruitDTO(Recruit recruit) {
public static RecommendRecruitDTO toRecommendRecruitDTO(Recruit recruit, boolean isScraped) {
return RecommendRecruitDTO.builder()
.recruitId(recruit.getId())
.companyName(recruit.getCompanyName())
.imageUrl(recruit.getImageUrl())
.title(recruit.getTitle())
.deadLine(recruit.getDeadLine())
.deadLine(formatDeadLine(recruit))
.isScraped(isScraped)
.experienceLevelCode(recruit.getExperienceLevelCode())
.experienceLevelMin(recruit.getExperienceLevelMin())
.experienceLevelMax(recruit.getExperienceLevelMax())
.experienceLevelName(recruit.getExperienceLevelName())
.educationLevelCode(recruit.getEducationLevelCode())
.educationLevelName(recruit.getEducationLevelName())
.openingDate(recruit.getOpeningDate())
.postingDate(recruit.getPostingDate())
.build();
}
Expand All @@ -68,17 +68,25 @@ public static RecruitInfoDTO toRecruitInfoDTO(String comment, Recruit recruit) {
return RecruitInfoDTO.builder()
.comment(comment)
.companyName(recruit.getCompanyName())
.title(recruit.getTitle())
.industryName(recruit.getIndustryName())
.region(recruit.getRegion())
.employmentName(recruit.getEmploymentName())
.experienceLevelName(recruit.getExperienceLevelName())
.educationLevelName(recruit.getEducationLevelName())
.salaryName(recruit.getSalaryName())
.deadLine(recruit.getDeadLine())
.jobNames(recruit.getJobNames())
.region(recruit.getRegion())
.companyInfoUrl(recruit.getCompanyInfoUrl())
.recruitUrl(recruit.getRecruitUrl())
.build();
}

private static String formatDeadLine(Recruit recruit) {
Copy link
Contributor

Choose a reason for hiding this comment

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

여기에서는 기한이 지난 공고는 다루지 않아도 괜찮나요?

LocalDate today = LocalDate.now();
LocalDate targetDate = recruit.getDeadLine().toLocalDate();
long daysBetween = ChronoUnit.DAYS.between(today, targetDate);

if (daysBetween == 0) {
return "오늘 마감";
}

return "D-" + daysBetween;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
public record RecommendRecruitDTO(
Long recruitId,
String companyName,
String imageUrl,
String title,
LocalDateTime deadLine,
String deadLine,
boolean isScraped,

/**
* TODO: 아래로는 (필터링, 정렬) 잘 되는지 확인용 데이터, 나중에 삭제 예정
Expand All @@ -20,7 +20,6 @@ public record RecommendRecruitDTO(
String experienceLevelName,
Integer educationLevelCode, // 학력 코드 0(학력무관), 1(고등학교졸업), 2(대학졸업(2,3년)), 3(대학졸업(4년)), 4(석사졸업), 5(박사졸업) 등
String educationLevelName,
LocalDateTime openingDate,
LocalDateTime postingDate
) {

Expand Down
Original file line number Diff line number Diff line change
@@ -1,22 +1,16 @@
package UMC.career_mate.domain.recruit.dto.response;

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

@Builder
public record RecruitInfoDTO(
String comment,
String companyName,
String title,
String industryName,
String region,
String employmentName,
String experienceLevelName,
String educationLevelName,
String salaryName,
LocalDateTime deadLine,
List<String> jobNames,
String region,
String companyInfoUrl,
String recruitUrl
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,7 @@ public enum RecruitSortType {

DEADLINE_ASC("deadLine", "asc"), // 마감일 빠른 순
DEADLINE_DESC("deadLine", "desc"), // 마감일 늦은 순
OPENING_ASC("openingDate", "asc"), // 원서 접수 시작일 빠른 순
OPENING_DESC("openingDate", "desc"), // 원서 접수 시작일 늦은 순
POSTING_ASC("postingDate", "asc"), // 사람인에 채용 공고 작성일 빠른 순
POSTING_DESC("postingDate", "desc"); // 사람인에 채용 공고 작성일 늦은 순
POSTING_DESC("postingDate", "desc"); // 사람인에 채용 공고 작성일 늦은 순 (전체)

private final String field;
private final String direction;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -117,10 +117,7 @@ private boolean isEnglish(String keyword) {
private OrderSpecifier createOrderSpecifier(RecruitSortType recruitSortType) {
return switch (recruitSortType) {
case DEADLINE_ASC -> new OrderSpecifier(Order.ASC, recruit.deadLine);
case OPENING_ASC -> new OrderSpecifier(Order.ASC, recruit.openingDate);
case POSTING_ASC -> new OrderSpecifier(Order.ASC, recruit.postingDate);
case DEADLINE_DESC -> new OrderSpecifier(Order.DESC, recruit.deadLine);
case OPENING_DESC -> new OrderSpecifier(Order.DESC, recruit.openingDate);
case POSTING_DESC -> new OrderSpecifier(Order.DESC, recruit.postingDate);
};
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,11 @@
import UMC.career_mate.domain.recruit.enums.EducationLevel;
import UMC.career_mate.domain.recruit.enums.RecruitSortType;
import UMC.career_mate.domain.recruit.repository.RecruitRepository;
import UMC.career_mate.domain.recruitScrap.repository.RecruitScrapRepository;
import UMC.career_mate.global.common.PageResponseDTO;
import UMC.career_mate.global.response.exception.GeneralException;
import UMC.career_mate.global.response.exception.code.CommonErrorCode;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
Expand All @@ -29,6 +31,7 @@ public class RecruitQueryService {

private final RecruitRepository recruitRepository;
private final ChatGptService chatGptService;
private final RecruitScrapRepository recruitScrapRepository;

public PageResponseDTO<List<RecommendRecruitDTO>> getRecommendRecruitList(int page, int size,
RecruitSortType recruitSortType, Member member) {
Expand All @@ -48,7 +51,7 @@ public PageResponseDTO<List<RecommendRecruitDTO>> getRecommendRecruitList(int pa
filterCondition.recruitKeyword(), educationLevel, filterCondition.careerYear(),
recruitSortType, pageRequest);

return createPageResponseDTO(page, size, findRecruitPage);
return createPageResponseDTO(page, size, findRecruitPage, member);
}

public RecruitInfoDTO findRecruitInfo(Member member, Long recruitId) {
Expand All @@ -64,12 +67,15 @@ public RecruitInfoDTO findRecruitInfo(Member member, Long recruitId) {
}

private PageResponseDTO<List<RecommendRecruitDTO>> createPageResponseDTO(int page,
int size, Page<Recruit> findRecruitPage) {
int size, Page<Recruit> findRecruitPage, Member member) {
Set<Long> scrapedRecruitIds = recruitScrapRepository.findRecruitIdsByMember(member);

boolean hasNext = findRecruitPage.getSize() == size + 1;

List<RecommendRecruitDTO> recommendRecruitDTOList = findRecruitPage.stream()
.limit(size)
.map(recruit -> RecruitConverter.toRecommendRecruitDTO(recruit))
.map(recruit -> RecruitConverter.toRecommendRecruitDTO(recruit,
scrapedRecruitIds.contains(recruit.getId())))
.toList();

return PageResponseDTO.<List<RecommendRecruitDTO>>builder()
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package UMC.career_mate.domain.recruitScrap.controller;

import UMC.career_mate.domain.member.Member;
import UMC.career_mate.domain.recruitScrap.dto.response.RecruitScrapResponseDTO;
import UMC.career_mate.domain.recruitScrap.service.RecruitScrapCommandService;
import UMC.career_mate.domain.recruitScrap.service.RecruitScrapQueryService;
import UMC.career_mate.global.annotation.LoginMember;
import UMC.career_mate.global.response.ApiResponse;
import io.swagger.v3.oas.annotations.Operation;
import java.util.List;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequiredArgsConstructor
@RequestMapping("/scrap/recruits")
public class RecruitScrapController {

private final RecruitScrapCommandService recruitScrapCommandService;
private final RecruitScrapQueryService recruitScrapQueryService;

@Operation(
summary = "채용 공고 스크랩 API",
description = """
채용 공고를 스크랩하는 API입니다.\n\n
recruitId : 스크랩하려는 채용 공고 pk 값
""")
@PostMapping("/{recruitId}")
public ApiResponse<String> createRecruitScrap(@LoginMember Member member,
@PathVariable Long recruitId) {
recruitScrapCommandService.saveRecruitScrap(member, recruitId);
return ApiResponse.onSuccess("스크랩 완료");
}

@Operation(
summary = "채용 공고 스크랩 삭제 API",
description = """
스크랩을 삭제하는 API입니다.\n\n
recruitId : 삭제하려는 스크랩의 채용 공고 pk 값
""")
@DeleteMapping("/{recruitId}")
public ApiResponse<String> deleteRecruitScrap(@LoginMember Member member,
@PathVariable Long recruitId) {
recruitScrapCommandService.deleteRecruitScrap(member, recruitId);
return ApiResponse.onSuccess("스크랩 삭제 완료");
}

@Operation(
summary = "채용 공고 스크랩 목록 조회 API",
description = """
스크랩한 채용 공고 목록을 조회하는 API입니다.\n\n
로그인 인증만을 요구합니다.
""")
@GetMapping
public ApiResponse<List<RecruitScrapResponseDTO>> getRecruitScrapList(
@LoginMember Member member) {
return ApiResponse.onSuccess(recruitScrapQueryService.findRecruitScrapList(member));
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
package UMC.career_mate.domain.recruitScrap.converter;

import UMC.career_mate.domain.member.Member;
import UMC.career_mate.domain.recruit.Recruit;
import UMC.career_mate.domain.recruitScrap.RecruitScrap;
import UMC.career_mate.domain.recruitScrap.dto.response.RecruitScrapResponseDTO;
import java.time.LocalDate;
import java.time.temporal.ChronoUnit;

public class RecruitScrapConverter {

public static RecruitScrap toEntity(Member member, Recruit recruit) {
return RecruitScrap.builder()
.member(member)
.recruit(recruit)
.build();
}

public static RecruitScrapResponseDTO toRecruitScrapResponseDTO(Recruit recruit) {
return RecruitScrapResponseDTO.builder()
.recruitId(recruit.getId())
.companyName(recruit.getCompanyName())
.title(recruit.getTitle())
.deadLine(formatDeadLine(recruit))
.isScraped(true)
.build();
}

private static String formatDeadLine(Recruit recruit) {
LocalDate today = LocalDate.now();
LocalDate targetDate = recruit.getDeadLine().toLocalDate();
long daysBetween = ChronoUnit.DAYS.between(today, targetDate);

if (daysBetween == 0) {
return "오늘 마감";
}

if (daysBetween > 0) {
return "D-" + daysBetween;
}

return "마감";
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package UMC.career_mate.domain.recruitScrap.dto.response;

import java.time.LocalDateTime;
import lombok.Builder;

@Builder
public record RecruitScrapResponseDTO(
Long recruitId,
String companyName,
String title,
String deadLine,
boolean isScraped
) {

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package UMC.career_mate.domain.recruitScrap.repository;

import UMC.career_mate.domain.member.Member;
import UMC.career_mate.domain.recruit.Recruit;
import UMC.career_mate.domain.recruitScrap.RecruitScrap;
import java.util.List;
import java.util.Optional;
import java.util.Set;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;

public interface RecruitScrapRepository extends JpaRepository<RecruitScrap, Long> {

boolean existsByMemberAndRecruit(Member member, Recruit recruit);

Optional<RecruitScrap> findByMemberAndRecruitId(Member member, Long recruitId);

@Query("select rs from RecruitScrap rs join fetch rs.recruit where rs.member = :member")
List<RecruitScrap> findByMember(@Param("member") Member member);

@Query("select rs.recruit.id from RecruitScrap rs where rs.member = :member")
Set<Long> findRecruitIdsByMember(@Param("member") Member member);
}
Loading
Loading