Skip to content

Commit

Permalink
Merge pull request #46 from team-crews/fix/#45-get-application
Browse files Browse the repository at this point in the history
fix: 지원서 상세 정보 조회 api 권한에 따라 분리
  • Loading branch information
jongmee authored Aug 27, 2024
2 parents d2d2db1 + e444238 commit dad2dbd
Show file tree
Hide file tree
Showing 64 changed files with 493 additions and 524 deletions.
Original file line number Diff line number Diff line change
@@ -1,8 +1,5 @@
package com.server.crews.applicant.application;

import static java.util.function.Function.identity;
import static java.util.stream.Collectors.groupingBy;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;

import com.server.crews.applicant.domain.Application;
Expand All @@ -13,24 +10,25 @@
import com.server.crews.applicant.dto.request.EvaluationRequest;
import com.server.crews.applicant.dto.response.ApplicationDetailsResponse;
import com.server.crews.applicant.dto.response.ApplicationsResponse;
import com.server.crews.applicant.mapper.ApplicationMapper;
import com.server.crews.applicant.repository.ApplicationRepository;
import com.server.crews.applicant.repository.NarrativeAnswerRepository;
import com.server.crews.applicant.repository.SelectiveAnswerRepository;
import com.server.crews.auth.domain.Applicant;
import com.server.crews.auth.dto.LoginUser;
import com.server.crews.auth.repository.ApplicantRepository;
import com.server.crews.global.exception.CrewsException;
import com.server.crews.global.exception.ErrorCode;
import com.server.crews.recruitment.domain.Choice;
import com.server.crews.recruitment.domain.NarrativeQuestion;
import com.server.crews.recruitment.domain.Recruitment;
import com.server.crews.recruitment.domain.SelectiveQuestion;
import com.server.crews.recruitment.dto.request.QuestionType;
import com.server.crews.recruitment.repository.ChoiceRepository;
import com.server.crews.recruitment.repository.NarrativeQuestionRepository;
import com.server.crews.recruitment.repository.RecruitmentRepository;
import com.server.crews.recruitment.repository.SelectiveQuestionRepository;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
Expand All @@ -41,6 +39,7 @@
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class ApplicationService {
private final RecruitmentRepository recruitmentRepository;
private final ApplicationRepository applicationRepository;
private final ApplicantRepository applicantRepository;
private final SelectiveQuestionRepository selectiveQuestionRepository;
Expand All @@ -51,43 +50,29 @@ public class ApplicationService {

@Transactional
public ApplicationDetailsResponse saveApplication(Long applicantId, ApplicationSaveRequest request) {
Recruitment recruitment = recruitmentRepository.findByCode(request.recruitmentCode())
.orElseThrow(() -> new CrewsException(ErrorCode.RECRUITMENT_NOT_FOUND));
Applicant applicant = applicantRepository.findById(applicantId)
.orElseThrow(() -> new CrewsException(ErrorCode.USER_NOT_FOUND));

List<NarrativeAnswer> narrativeAnswers = extractNarrativeAnswers(request);
List<SelectiveAnswer> selectiveAnswers = extractSelectiveAnswers(request);

Application application = new Application(request.id(), applicant, request.studentNumber(), request.major(),
request.name(),
narrativeAnswers, selectiveAnswers);
validateNarrativeQuestions(request);
validateSelectiveQuestions(request);

Application application = ApplicationMapper.applicationSaveRequestToApplication(request, recruitment,
applicant);
Application savedApplication = applicationRepository.save(application);

return ApplicationDetailsResponse.of(savedApplication, narrativeAnswers,
collectSelectiveAnswersByQuestion(selectiveAnswers));
return ApplicationMapper.applicationToApplicationDetailsResponse(savedApplication);
}

private List<NarrativeAnswer> extractNarrativeAnswers(ApplicationSaveRequest request) {
private void validateNarrativeQuestions(ApplicationSaveRequest request) {
List<AnswerSaveRequest> narrativeAnswerSaveRequests = filterByQuestionType(QuestionType.NARRATIVE, request);
Set<Long> narrativeQuestionIds = extractQuestionIds(narrativeAnswerSaveRequests);
if (narrativeQuestionIds.size() != narrativeAnswerSaveRequests.size()) {
throw new CrewsException(ErrorCode.DUPLICATE_NARRATIVE_ANSWERS);
}
List<NarrativeQuestion> savedNarrativeQuestions = narrativeQuestionRepository.findAllByIdIn(
narrativeQuestionIds);
validateQuestionIds(savedNarrativeQuestions, narrativeQuestionIds);

Map<Long, NarrativeQuestion> savedNarrativeQuestionsById = savedNarrativeQuestions.stream()
.collect(toMap(NarrativeQuestion::getId, identity()));

return narrativeAnswerSaveRequests.stream()
.map(narrativeQuestionAnswerRequest -> new NarrativeAnswer(narrativeQuestionAnswerRequest.answerId(),
savedNarrativeQuestionsById.get(narrativeQuestionAnswerRequest.questionId()),
narrativeQuestionAnswerRequest.content()))
.toList();
}

private List<SelectiveAnswer> extractSelectiveAnswers(ApplicationSaveRequest request) {
private void validateSelectiveQuestions(ApplicationSaveRequest request) {
List<AnswerSaveRequest> selectiveAnswerSaveRequests = filterByQuestionType(QuestionType.SELECTIVE, request);
Set<Long> selectiveQuestionIds = extractQuestionIds(selectiveAnswerSaveRequests);
List<SelectiveQuestion> savedSelectiveQuestions = selectiveQuestionRepository.findAllByIdIn(
Expand All @@ -101,16 +86,6 @@ private List<SelectiveAnswer> extractSelectiveAnswers(ApplicationSaveRequest req
if (savedChoices.size() != choiceIds.size()) {
throw new CrewsException(ErrorCode.CHOICE_NOT_FOUND);
}

Map<Long, Choice> savedChoicesById = savedChoices.stream()
.collect(toMap(Choice::getId, identity()));
Map<Long, SelectiveQuestion> savedSelectiveQuestionsById = savedSelectiveQuestions.stream()
.collect(toMap(SelectiveQuestion::getId, identity()));

return selectiveAnswerSaveRequests.stream()
.map(selectiveQuestionAnswerRequest -> new SelectiveAnswer(selectiveQuestionAnswerRequest.answerId(),
savedChoicesById.get(selectiveQuestionAnswerRequest.choiceId()),
savedSelectiveQuestionsById.get(selectiveQuestionAnswerRequest.questionId()))).toList();
}

private List<AnswerSaveRequest> filterByQuestionType(QuestionType questionType,
Expand All @@ -135,29 +110,35 @@ private void validateQuestionIds(List<?> savedQuestions, Set<Long> questionIds)
public List<ApplicationsResponse> findAllApplicationsByRecruitment(Long publisherId) {
List<Application> applications = applicationRepository.findAllWithApplicantByPublisherId(publisherId);
return applications.stream()
.map(ApplicationsResponse::from)
.map(ApplicationMapper::applicationToApplicationsResponse)
.toList();
}

public ApplicationDetailsResponse findApplicationDetails(Long applicationId, LoginUser loginUser) {
Application application = applicationRepository.findById(applicationId)
public ApplicationDetailsResponse findApplicationDetails(Long applicationId, Long publisherId) {
Application application = applicationRepository.findByIdWithRecruitmentAndPublisher(applicationId)
.orElseThrow(() -> new CrewsException(ErrorCode.APPLICATION_NOT_FOUND));
checkPermission(application, loginUser);
checkPermission(application, publisherId);
List<NarrativeAnswer> narrativeAnswers = narrativeAnswerRepository.findAllByApplication(application);
Map<Long, List<SelectiveAnswer>> selectiveAnswers = collectSelectiveAnswersByQuestion(
selectiveAnswerRepository.findAllByApplication(application));
return ApplicationDetailsResponse.of(application, narrativeAnswers, selectiveAnswers);
List<SelectiveAnswer> selectiveAnswers = selectiveAnswerRepository.findAllByApplication(application);
application.replaceNarrativeAnswers(narrativeAnswers);
application.replaceSelectiveAnswers(selectiveAnswers);
return ApplicationMapper.applicationToApplicationDetailsResponse(application);
}

private void checkPermission(Application application, LoginUser loginUser) {
if (!application.canBeAccessedBy(loginUser.userId(), loginUser.role())) {
private void checkPermission(Application application, Long publisherId) {
if (!application.canBeAccessedBy(publisherId)) {
throw new CrewsException(ErrorCode.UNAUTHORIZED_USER);
}
}

private Map<Long, List<SelectiveAnswer>> collectSelectiveAnswersByQuestion(List<SelectiveAnswer> selectiveAnswers) {
return selectiveAnswers.stream()
.collect(groupingBy(selectiveAnswer -> selectiveAnswer.getSelectiveQuestion().getId()));
public ApplicationDetailsResponse findMyApplicationDetails(Long applicantId, String code) {
Application application = applicationRepository.findByApplicantIdAndRecruitmentCode(applicantId, code)
.orElseThrow(() -> new CrewsException(ErrorCode.APPLICATION_NOT_FOUND));
List<NarrativeAnswer> narrativeAnswers = narrativeAnswerRepository.findAllByApplication(application);
List<SelectiveAnswer> selectiveAnswers = selectiveAnswerRepository.findAllByApplication(application);
application.replaceNarrativeAnswers(narrativeAnswers);
application.replaceSelectiveAnswers(selectiveAnswers);
return ApplicationMapper.applicationToApplicationDetailsResponse(application);
}

@Transactional
Expand Down
45 changes: 27 additions & 18 deletions src/main/java/com/server/crews/applicant/domain/Application.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

import com.server.crews.auth.domain.Applicant;
import com.server.crews.auth.domain.Role;
import com.server.crews.recruitment.domain.Recruitment;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.ConstraintMode;
Expand All @@ -12,8 +13,8 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import java.util.ArrayList;
import java.util.List;
Expand All @@ -34,7 +35,7 @@ public class Application {
@Column(name = "outcome", nullable = false)
private Outcome outcome;

@OneToOne(fetch = FetchType.LAZY)
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "applicant_id", nullable = false, foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Applicant applicant;

Expand All @@ -48,31 +49,42 @@ public class Application {
private String name;

@OneToMany(mappedBy = "application", cascade = CascadeType.ALL, orphanRemoval = true)
private List<NarrativeAnswer> narrativeAnswers = new ArrayList<>();
private List<NarrativeAnswer> narrativeAnswers;

@OneToMany(mappedBy = "application", cascade = CascadeType.ALL, orphanRemoval = true)
private List<SelectiveAnswer> selectiveAnswers = new ArrayList<>();

public Application(Long id, Applicant applicant, String studentNumber, String major, String name,
List<NarrativeAnswer> narrativeAnswers, List<SelectiveAnswer> selectiveAnswers) {
private List<SelectiveAnswer> selectiveAnswers;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(nullable = false, name = "recruitment_id", foreignKey = @ForeignKey(ConstraintMode.NO_CONSTRAINT))
private Recruitment recruitment;

public Application(Long id,
Recruitment recruitment,
Applicant applicant,
String studentNumber,
String major,
String name,
List<NarrativeAnswer> narrativeAnswers,
List<SelectiveAnswer> selectiveAnswers) {
this.id = id;
this.recruitment = recruitment;
this.applicant = applicant;
this.studentNumber = studentNumber;
this.major = major;
this.name = name;
updateNarrativeAnswers(narrativeAnswers);
updateSelectiveAnswers(selectiveAnswers);
this.outcome = Outcome.PENDING;
replaceNarrativeAnswers(narrativeAnswers);
replaceSelectiveAnswers(selectiveAnswers);
}

public void updateNarrativeAnswers(List<NarrativeAnswer> narrativeAnswers) {
this.narrativeAnswers.addAll(narrativeAnswers);
public void replaceNarrativeAnswers(List<NarrativeAnswer> narrativeAnswers) {
narrativeAnswers.forEach(narrativeAnswer -> narrativeAnswer.updateApplication(this));
this.narrativeAnswers = new ArrayList<>(narrativeAnswers);
}

public void updateSelectiveAnswers(List<SelectiveAnswer> selectiveAnswers) {
this.selectiveAnswers.addAll(selectiveAnswers);
public void replaceSelectiveAnswers(List<SelectiveAnswer> selectiveAnswers) {
selectiveAnswers.forEach(selectiveAnswer -> selectiveAnswer.updateApplication(this));
this.selectiveAnswers = new ArrayList<>(selectiveAnswers);
}

public void pass() {
Expand All @@ -87,10 +99,7 @@ public boolean isNotDetermined() {
return outcome.equals(Outcome.PENDING);
}

public boolean canBeAccessedBy(Long userId, Role role) {
if (role == Role.ADMIN) {
return true;
}
return applicant.getId().equals(userId);
public boolean canBeAccessedBy(Long publisherId) {
return this.recruitment.isPublishedBy(publisherId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,8 @@ public record ApplicationSaveRequest(
String major,
@NotBlank(message = "이름은 공백일 수 없습니다.")
String name,
List<AnswerSaveRequest> answers
List<AnswerSaveRequest> answers,
@NotBlank(message = "모집 공고 코드는 공백일 수 없습니다.")
String recruitmentCode
) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.server.crews.applicant.dto.response;

import com.server.crews.recruitment.dto.request.QuestionType;
import lombok.Builder;

@Builder
public record AnswerResponse(
Long answerId,
Long questionId,
String content,
Long choiceId,
QuestionType type
) {
}
Original file line number Diff line number Diff line change
@@ -1,40 +1,14 @@
package com.server.crews.applicant.dto.response;

import com.server.crews.applicant.domain.Application;
import com.server.crews.applicant.domain.NarrativeAnswer;
import com.server.crews.applicant.domain.SelectiveAnswer;
import lombok.Builder;

import java.util.List;
import java.util.Map;
import lombok.Builder;

@Builder
public record ApplicationDetailsResponse(Long id, String studentNumber, String major, String name,
List<SelectiveAnswerResponse> selectiveAnswers,
List<NarrativeAnswerResponse> narrativeAnswers) {

public static ApplicationDetailsResponse of(Application application, List<NarrativeAnswer> narrativeAnswers,
Map<Long, List<SelectiveAnswer>> selectiveAnswers) {
return ApplicationDetailsResponse.builder()
.id(application.getId())
.studentNumber(application.getStudentNumber())
.major(application.getMajor())
.name(application.getName())
.narrativeAnswers(from(narrativeAnswers))
.selectiveAnswers(from(selectiveAnswers))
.build();
}

private static List<NarrativeAnswerResponse> from(List<NarrativeAnswer> narrativeAnswers) {
return narrativeAnswers.stream()
.map(NarrativeAnswerResponse::from)
.toList();
}

private static List<SelectiveAnswerResponse> from(Map<Long, List<SelectiveAnswer>> selectiveAnswers) {
return selectiveAnswers.entrySet()
.stream()
.map(entry -> SelectiveAnswerResponse.from(entry.getKey(), entry.getValue()))
.toList();
}
public record ApplicationDetailsResponse(
Long id,
String studentNumber,
String major,
String name,
List<AnswerResponse> answers
) {
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
package com.server.crews.applicant.dto.response;

import com.server.crews.applicant.domain.Application;
import com.server.crews.applicant.domain.Outcome;
import lombok.Builder;

@Builder
public record ApplicationsResponse(Long id, String studentNumber, String name, String major, Outcome outcome) {
public static ApplicationsResponse from(final Application application) {
return ApplicationsResponse.builder()
.id(application.getId())
.studentNumber(application.getStudentNumber())
.name(application.getName())
.major(application.getMajor())
.outcome(application.getOutcome())
.build();
}
public record ApplicationsResponse(
Long id,
String studentNumber,
String name,
String major,
Outcome outcome
) {
}

This file was deleted.

This file was deleted.

This file was deleted.

Loading

0 comments on commit dad2dbd

Please sign in to comment.