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
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,15 @@ public void updateRecruit(@PathVariable Long recruitId,
Recruit recruit = recruitRepository.findById(recruitId)
.orElseThrow(() -> new RecruitException(RecruitErrorCode.NOT_FOUND_RECRUIT));
JobFamily previousJobFamily = recruit.getJobFamily();
recruit.update(request.jobFamily(), request.startDate(), request.endDate());
recruit.update(
request.jobFamily(),
request.startDate(),
request.endDate(),
request.summary(),
request.recruitInformation(),
request.notice(),
request.getFaqsOrNull()
);

eventPublisher.publishEvent(new RecruitUpdatedEvent(
recruit.getId(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,17 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.ject.support.domain.recruit.dto.ActiveRecruitmentResponses;
import org.ject.support.domain.recruit.dto.RecruitResponse;
import org.springframework.web.bind.annotation.PathVariable;

@Tag(name = "Recruit", description = "모집 공고 API")
public interface RecruitApiSpec {

@Operation(
summary = "모집공고 단건 조회",
description = "모집공고의 상세정보와 안내사항 및 FAQ를 조회합니다.")
RecruitResponse getRecruit(@PathVariable Long recruitId);

@Operation(
summary = "활성 모집 공고 목록 조회",
description = "현재 지원 가능한 모집 공고 목록을 조회합니다.")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,10 @@

import lombok.RequiredArgsConstructor;
import org.ject.support.domain.recruit.dto.ActiveRecruitmentResponses;
import org.ject.support.domain.recruit.dto.RecruitResponse;
import org.ject.support.domain.recruit.service.RecruitUsecase;
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.RestController;

Expand All @@ -14,6 +16,14 @@ public class RecruitController implements RecruitApiSpec {

private final RecruitUsecase recruitUsecase;

// 모집공고 단건 조회
@Override
@GetMapping("/{recruitId}")
public RecruitResponse getRecruit(@PathVariable Long recruitId) {
return recruitUsecase.getRecruit(recruitId);
}

// 활성 모집공고 목록 조회
@Override
@GetMapping("/active")
public ActiveRecruitmentResponses findActiveRecruitments() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.DynamicUpdate;
import org.hibernate.annotations.JdbcTypeCode;
import org.hibernate.type.SqlTypes;
import org.ject.support.domain.base.BaseTimeEntity;
import org.ject.support.domain.member.JobFamily;
import org.ject.support.domain.recruit.exception.RecruitErrorCode;
Expand Down Expand Up @@ -71,6 +73,20 @@ public class Recruit extends BaseTimeEntity {
@Builder.Default
private List<Question> questions = new ArrayList<>();

@Column(columnDefinition = "MEDIUMTEXT")
private String recruitInformation;

@Column(length = 500)
private String summary;

@Column(columnDefinition = "MEDIUMTEXT")
private String notice;

@JdbcTypeCode(SqlTypes.JSON)
@Column(columnDefinition = "json", nullable = false)
@Builder.Default
private List<RecruitFaq> faqs = new ArrayList<>();

public void addQuestion(Question question) {
this.questions.add(question);
question.setRecruit(this);
Expand All @@ -96,10 +112,24 @@ void validateRecruitTypeDetail() {
}
}

public void update(JobFamily jobFamily, LocalDateTime startDate, LocalDateTime endDate) {
// 모집공고 정보 변경
public void update(JobFamily jobFamily, LocalDateTime startDate, LocalDateTime endDate,
String summary, String recruitInformation, String notice, List<RecruitFaq> faqs) {
this.jobFamily = jobFamily;
this.startDate = startDate;
this.endDate = endDate;
if (summary != null && !summary.isBlank()) {
this.summary = summary;
}
if (recruitInformation != null && !recruitInformation.isBlank()) {
this.recruitInformation = recruitInformation;
}
if (notice != null && !notice.isBlank()) {
this.notice = notice;
}
if (faqs != null && !faqs.isEmpty()) {
this.faqs = new ArrayList<>(faqs);
}
}

public boolean isClosed() {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.ject.support.domain.recruit.domain;

public record RecruitFaq(
String title,
String content
) {

// 모집공고 FAQ 생성
public static RecruitFaq create(String title, String content) {
return new RecruitFaq(title, content);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ public record ActiveRecruitmentResponse(Long recruitId,
JobFamily jobFamily,
String jobFamilyDescription,
LocalDateTime startDate,
LocalDateTime endDate) {
LocalDateTime endDate,
String summary) {

public static ActiveRecruitmentResponse from(Recruit recruit) {
RecruitType recruitType = toPublicRecruitType(recruit.getRecruitType());
Expand All @@ -33,7 +34,8 @@ public static ActiveRecruitmentResponse from(Recruit recruit) {
recruit.getJobFamily(),
recruit.getJobFamily().getDescription(),
recruit.getStartDate(),
recruit.getEndDate()
recruit.getEndDate(),
recruit.getSummary()
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package org.ject.support.domain.recruit.dto;

import java.util.List;
import org.ject.support.domain.recruit.domain.RecruitFaq;

import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record RecruitFaqRequest(
@NotBlank(message = "FAQ 제목을 입력해주세요.")
@Size(max = 200, message = "FAQ 제목은 200자 이하여야 합니다.")
String title,

@NotBlank(message = "FAQ 내용을 입력해주세요.")
@Size(max = 100000, message = "FAQ 내용은 100,000자 이하여야 합니다.")
String content
) {

// FAQ 요청 목록을 도메인 값으로 변환
public static List<RecruitFaq> toDomains(List<RecruitFaqRequest> requests) {
if (requests == null) {
return List.of();
}
return requests.stream()
.map(request -> RecruitFaq.create(request.title(), request.content()))
.toList();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package org.ject.support.domain.recruit.dto;

import org.ject.support.domain.recruit.domain.RecruitFaq;

public record RecruitFaqResponse(
String title,
String content
) {

// FAQ 응답 변환
public static RecruitFaqResponse from(RecruitFaq faq) {
return new RecruitFaqResponse(faq.title(), faq.content());
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,11 +2,14 @@

import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
import org.ject.support.domain.member.JobFamily;
import org.ject.support.domain.recruit.domain.Recruit;
import org.ject.support.domain.recruit.domain.Semester;

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

public record RecruitRegisterRequest(
@NotNull(message = "직군은 필수입니다.")
Expand All @@ -17,14 +20,33 @@ public record RecruitRegisterRequest(

@NotNull(message = "모집 종료일은 필수입니다.")
@Future(message = "모집 종료일은 현재 시각보다 이후여야 합니다.")
LocalDateTime endDate
LocalDateTime endDate,

@Size(max = 500, message = "모집공고 요약은 500자 이하로 입력해주세요.")
String summary,

@Size(max = 100000, message = "모집 정보는 100,000자 이하여야 합니다.")
String recruitInformation,

@Size(max = 100000, message = "안내사항은 100,000자 이하여야 합니다.")
String notice,

List<@Valid RecruitFaqRequest> faqs
) {
public RecruitRegisterRequest(JobFamily jobFamily, LocalDateTime startDate, LocalDateTime endDate) {
this(jobFamily, startDate, endDate, null, null, null, List.of());
}

public Recruit toEntity(Semester semester) {
return Recruit.builder()
.semester(semester)
.jobFamily(this.jobFamily)
.startDate(this.startDate)
.endDate(this.endDate)
.summary(this.summary)
.recruitInformation(this.recruitInformation)
.notice(this.notice)
.faqs(RecruitFaqRequest.toDomains(this.faqs))
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package org.ject.support.domain.recruit.dto;

import java.time.LocalDateTime;
import java.util.List;
import org.ject.support.domain.member.JobFamily;
import org.ject.support.domain.recruit.domain.Recruit;
import org.ject.support.domain.recruit.domain.RecruitType;
import org.ject.support.domain.recruit.domain.RecruitTypeDetail;

public record RecruitResponse(
Long recruitId,
Long semesterId,
String semesterName,
RecruitType recruitType,
String recruitTypeDescription,
RecruitTypeDetail recruitTypeDetail,
String recruitTypeDetailDescription,
JobFamily jobFamily,
String jobFamilyDescription,
LocalDateTime startDate,
LocalDateTime endDate,
String recruitInformation,
String notice,
List<RecruitFaqResponse> faqs
) {

// 모집공고 상세 응답 변환
public static RecruitResponse from(Recruit recruit) {
RecruitType recruitType = toPublicRecruitType(recruit.getRecruitType());
RecruitTypeDetail recruitTypeDetail = toPublicRecruitTypeDetail(recruit);
return new RecruitResponse(
recruit.getId(),
recruit.getSemester().getId(),
recruit.getSemester().getName(),
recruitType,
recruitType.getDescription(),
recruitTypeDetail,
recruitTypeDetail.getDescription(),
recruit.getJobFamily(),
recruit.getJobFamily().getDescription(),
recruit.getStartDate(),
recruit.getEndDate(),
recruit.getRecruitInformation(),
recruit.getNotice(),
recruit.getFaqs().stream().map(RecruitFaqResponse::from).toList()
);
}

private static RecruitType toPublicRecruitType(RecruitType recruitType) {
return switch (recruitType) {
case REGULAR, REGULAR_WAITLIST, BACKFILL, MANUAL -> RecruitType.SEMESTER;
case SEMESTER, MAKERS, SUPPORTERS -> recruitType;
};
}

private static RecruitTypeDetail toPublicRecruitTypeDetail(Recruit recruit) {
if (recruit.getRecruitType() == RecruitType.BACKFILL) {
return RecruitTypeDetail.REFILL;
}
return recruit.getRecruitTypeDetail();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,13 @@

import jakarta.validation.constraints.Future;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.Valid;
import jakarta.validation.constraints.Size;
import org.ject.support.domain.member.JobFamily;
import org.ject.support.domain.recruit.domain.RecruitFaq;

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

public record RecruitUpdateRequest(
@NotNull(message = "직군은 필수입니다.")
Expand All @@ -15,6 +19,25 @@ public record RecruitUpdateRequest(

@NotNull(message = "모집 종료일은 필수입니다.")
@Future(message = "모집 종료일은 현재 시각보다 이후여야 합니다.")
LocalDateTime endDate
LocalDateTime endDate,

@Size(max = 500, message = "모집공고 요약은 500자 이하로 입력해주세요.")
String summary,

@Size(max = 100000, message = "모집 정보는 100,000자 이하여야 합니다.")
String recruitInformation,

@Size(max = 100000, message = "안내사항은 100,000자 이하여야 합니다.")
String notice,

List<@Valid RecruitFaqRequest> faqs
) {
public RecruitUpdateRequest(JobFamily jobFamily, LocalDateTime startDate, LocalDateTime endDate) {
this(jobFamily, startDate, endDate, null, null, null, List.of());
}

// FAQ 요청 목록을 도메인 값으로 변환
public List<RecruitFaq> getFaqsOrNull() {
return faqs == null ? null : RecruitFaqRequest.toDomains(faqs);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,12 @@

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

public interface RecruitRepository extends JpaRepository<Recruit, Long> {
@Query("SELECT r FROM Recruit r JOIN FETCH r.semester WHERE r.id = :recruitId")
Optional<Recruit> findByIdWithSemester(@Param("recruitId") Long recruitId);

@Query("SELECT r FROM Recruit r LEFT JOIN FETCH r.questions "
+ "WHERE r.startDate <= :now AND r.endDate >= :now")
List<Recruit> findActiveRecruits(@Param("now") LocalDateTime now);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import lombok.RequiredArgsConstructor;
import org.ject.support.domain.recruit.dto.RecruitCanceledEvent;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.stereotype.Service;
import org.springframework.transaction.event.TransactionPhase;
import org.springframework.transaction.event.TransactionalEventListener;
Expand All @@ -15,6 +16,7 @@ public class RecruitCanceledEventHandler {
/**
* 모집 취소 시 호출됨
*/
@CacheEvict(value = "recruit-detail", key = "#event.recruitId")
@TransactionalEventListener(phase = TransactionPhase.AFTER_COMMIT)
public void handleRecruitCanceled(RecruitCanceledEvent event) {
recruitFlagService.deleteRecruitFlag(event.recruitId(), event.jobFamily());
Expand Down
Loading
Loading