1. 지원서 불러오는 api (getApplication)의 로직을, stepId와 applicantId로 불러오는 것에서, applicantIdList(List<String>)으로 불러오는 것으로 수정

  1. stepId를 옵션으로 건 이유는, 다른 동아리, 혹은 같은 동아리라도, 다른 전형들의 지원서 전형들과 구분하기 위해 stepId를 생성하였는데, 생각해보니까 applicantId는 해당 step에 지원서를 작성할 때마다 생성되는 값이기 때문에 applicantId로만 지원서를 조회해도, 다른 전형의 지원서를 불러올 일이 발생하지 않는다. 그렇기 때문에,stepId로 조회하는 과정은 불필요하다고 느껴졌고, applicantId로만 조회하는 것으로 수정하기로 하였다.
    1. 그런데 문제가 발생한게, 기존 로직은 해당 api를 사용하고 있는 stepId를 받아서 해당 club을 찾고, 사용자가 회장인지 동아리원인지 확인한다. (stepId로 동아리의 정보를 찾는다는것이 간접적으로 찾는 것이라 이부분도 수정하자.)
    2. 이 부분은 추후에, 회장,동아리원 인증로직을 별도의 필터로 빼고 싶기 때문에 그때 다시 수정하는 것으로 남겨두었다. 그래서 현재 stepId는 남겨두기로 하였다.
  2. applicantId 한개로만 조회가능하다. 현재 params가 String applicantId로 되어 있기 때문에, 여러명의 지원자의 지원서를 한번에 불러오려면, n번 api를 호출해야하므로 비효율적이다. 이전에는 여러 지원자의 지원서를 동시에 불러와야하는 경우가 없었기 때문에 이와같이 설계하였지만, 면접 평가시와 면접 평가결과 조회시, 해당 타임에 소속된 지원자의 지원서들을 한번에 불러와야 하는데, 이 과정에서 params에 applicantId 리스트로 보내어 조회하도록 수정하기로 하였다.
// controller

@GetMapping("/")
    public ResponseEntity<?> getApplication(@RequestParam(required = true) String stepId,
                                            @RequestParam(required = true) String applicantId) {
        try {
            GetApplicationResponse response = applicationService.findApplicationByApplicantId(stepId, applicantId);
            return ResponseEntity.status(HttpStatus.OK).body(response);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
        }
    }
// service
@Override
    @Transactional
    public GetApplicationResponse findApplicationByApplicantId(String stepId, String applicantId) {
        //0. 해당 접근자가, 회장인지 동아리원인지 판단.
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        CustomUserDetail userDetails = (CustomUserDetail) authentication.getPrincipal();

        User user = userRepository.findById(userDetails.getId())
                .orElseThrow(() -> new NoSuchElementException("User not found"));

        Club club = stepRepository.findById(stepId)
                .orElseThrow(() -> new NoSuchElementException("Step not found"))
                .getRecruitment().getClub();

        UserClubRole userClubRole = userClubRoleRepository.findByClubAndUser(club, user)
                .orElseThrow(() -> new NoSuchElementException("User is not in Club"));

        //1. stepId(해당 전형 하위 step)와 applicantId(지원자id)로 지원서 찾기
        Applicant applicant = applicantRepository.findById(applicantId)
                .orElseThrow(() -> new NoSuchElementException("Applicant not found"));
        Step step = stepRepository.findById(stepId)
                .orElseThrow(() -> new NoSuchElementException("Step not found"));

				//굳이 step과 applicant로 찾을 필요없이, applicant하나로 조회가능하다. 이부분 수정함
        Application application = applicationRepository.findByApplicantAndStep(applicant, step)
                .orElseThrow(() -> new NoSuchElementException("Application not found"));

        //2-1. 회장에게만, 필수 응답값(지원자정보) DTO생성
        RequiredFieldDto requiredFieldAnswerDto = applicant.toRequiredFieldDto();

        //2-2. 질문 및 답변값 DTO 생성
        List<GetApplicationResponse.QuestionAnswerDto> questionAnswerDtos = new ArrayList<>();
        List<Answer> answers = answerRepository.findAllByApplication(application);
        if (answers.isEmpty())
            throw new IllegalArgumentException("Answers cannot be empty");

        for (Answer answer : answers) {
            Question question = questionRepository.findById(answer.getQuestion().getId())
                    .orElseThrow(() -> new NoSuchElementException("Question not found"));

            //동아리원 접근 제한 응답 확인
            if (userClubRole.getClubRole() == ClubRole.MEMBER && !question.isAccessible())
                continue;

            String answerText = getAnswerText(question, answer);
            GetApplicationResponse.QuestionAnswerDto questionAnswerDto = GetApplicationResponse.QuestionAnswerDto.builder()
                    .questionId(question.getId())
                    .questionOrder(question.getQuestionOrder())
                    .questionText(question.getQuestionText())
                    .questionType(question.getQuestionType())
                    .answer(answerText)
                    .build();

            questionAnswerDtos.add(questionAnswerDto);
        }
        //2-1. 질문 순서에 맞게 정렬
        SortUtils.sortList(questionAnswerDtos, Comparator.comparing(GetApplicationResponse.QuestionAnswerDto::questionOrder));

        //3. 전체 지원서 반환값 생성
        return GetApplicationResponse.builder()
                .applicantId(applicant.getId())
                .requiredFieldAnswerDto(userClubRole.getClubRole() == ClubRole.PRESIDENT ? requiredFieldAnswerDto : null)
                .questionAnswerDtos(questionAnswerDtos)
                .build();
    }
// controller
@GetMapping("/")
    public ResponseEntity<?> getApplication(@RequestParam(required = true) String stepId,
                                            @RequestParam(required = true) List<String> applicantIdList) {
        try {
            List<GetApplicationResponse> response = applicationService.findApplicationByApplicantId(stepId, applicantIdList);
            return ResponseEntity.status(HttpStatus.OK).body(response);
        } catch (Exception e) {
            return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body(e.getMessage());
        }
    }
@Override
    @Transactional
    public List<GetApplicationResponse> findApplicationByApplicantId(String stepId, List<String> applicantId) {
        //0. 해당 접근자가, 회장인지 동아리원인지 판단.
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        CustomUserDetail userDetails = (CustomUserDetail) authentication.getPrincipal();

        User user = userRepository.findById(userDetails.getId())
                .orElseThrow(() -> new NoSuchElementException("User not found"));

        Club club = stepRepository.findById(stepId)
                .orElseThrow(() -> new NoSuchElementException("Step not found"))
                .getRecruitment().getClub();

        UserClubRole userClubRole = userClubRoleRepository.findByClubAndUser(club, user)
                .orElseThrow(() -> new NoSuchElementException("User is not in Club"));

        //1. stepId(해당 전형 하위 step)와 applicantId(지원자id)로 지원서 찾기
        List<Applicant> applicants = applicantRepository.findAllById(applicantId);
        if (applicants.isEmpty())
            throw new IllegalArgumentException("Applicant not found");

        List<Application> applications = applicationRepository.findAllByApplicantIn(applicants);
        if (applications.isEmpty())
            throw new IllegalArgumentException("application not found");

        List<GetApplicationResponse> getApplicationResponses = new ArrayList<>();
        for (Application application : applications) {
            //2. 질문 및 답변값 DTO 생성
            List<GetApplicationResponse.QuestionAnswerDto> questionAnswerDtos = new ArrayList<>();
            List<Answer> answers = answerRepository.findAllByApplication(application);
            if (answers.isEmpty())
                throw new IllegalArgumentException("Answers cannot be empty");

            for (Answer answer : answers) {
                Question question = questionRepository.findById(answer.getQuestion().getId())
                        .orElseThrow(() -> new NoSuchElementException("Question not found"));

                //동아리원 접근 제한 응답 확인
                if (userClubRole.getClubRole() == ClubRole.MEMBER && !question.isAccessible())
                    continue;

                String answerText = getAnswerText(question, answer);
                GetApplicationResponse.QuestionAnswerDto questionAnswerDto = GetApplicationResponse.QuestionAnswerDto.builder()
                        .questionId(question.getId())
                        .questionOrder(question.getQuestionOrder())
                        .questionText(question.getQuestionText())
                        .questionType(question.getQuestionType())
                        .answer(answerText)
                        .build();

                questionAnswerDtos.add(questionAnswerDto);
            }
            //2-1. 질문 순서에 맞게 정렬
            SortUtils.sortList(questionAnswerDtos, Comparator.comparing(GetApplicationResponse.QuestionAnswerDto::questionOrder));

            //2-2. 회장에게만, 필수 응답값(지원자정보) DTO생성
            RequiredFieldDto requiredFieldAnswerDto;
            if (userClubRole.getClubRole() == ClubRole.PRESIDENT)
                requiredFieldAnswerDto = application.getApplicant().toFullRequiredFieldDto();
            else
                requiredFieldAnswerDto = application.getApplicant().toNameOnlyRequiredFieldDto();

            //3. 전체 지원서 반환값 생성
            GetApplicationResponse getApplicationResponse = GetApplicationResponse.builder()
                    .applicantId(application.getApplicant().getId())
                    .requiredFieldAnswerDto(requiredFieldAnswerDto)
                    .questionAnswerDtos(questionAnswerDtos)
                    .build();

            getApplicationResponses.add(getApplicationResponse);
        }
        return getApplicationResponses;
    }