From abdf59183309251e4d343f592ab1381818c451bc Mon Sep 17 00:00:00 2001 From: shinae1023 Date: Fri, 22 May 2026 23:25:09 +0900 Subject: [PATCH 1/3] =?UTF-8?q?[Feat]=20=ED=95=98=EB=82=98=EC=9D=98=20?= =?UTF-8?q?=EA=B3=B5=EA=B3=A0=EC=97=90=20=EB=8C=80=ED=95=9C=20=EB=AA=A8?= =?UTF-8?q?=EC=9D=98=EC=A7=80=EC=9B=90=20=EC=88=9C=EB=B2=88=20api?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/MockApplyController.java | 16 +++++++++ .../response/MockApplySequenceResponse.java | 9 +++++ .../repository/MockApplyRepository.java | 1 + .../mockapply/service/MockApplyService.java | 34 +++++++++++++++++++ .../service/MockApplyServiceTest.java | 25 ++++++++++++++ 5 files changed, 85 insertions(+) create mode 100644 src/main/java/com/jobdri/jobdri_api/domain/mockapply/dto/response/MockApplySequenceResponse.java diff --git a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/controller/MockApplyController.java b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/controller/MockApplyController.java index 90c5666..c1cb3fe 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/controller/MockApplyController.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/controller/MockApplyController.java @@ -4,6 +4,7 @@ import com.jobdri.jobdri_api.domain.mockapply.dto.request.MockApplyCreateMockFromJobPostingRequest; import com.jobdri.jobdri_api.domain.mockapply.dto.request.MockApplyCreateMockRequest; import com.jobdri.jobdri_api.domain.mockapply.dto.response.MockApplyCreateResponse; +import com.jobdri.jobdri_api.domain.mockapply.dto.response.MockApplySequenceResponse; import com.jobdri.jobdri_api.domain.jobposting.dto.response.JobPostingResponse; import com.jobdri.jobdri_api.domain.mockapply.service.MockApplyService; import com.jobdri.jobdri_api.global.apiPayload.ApiResponse; @@ -191,4 +192,19 @@ public ApiResponse getMockApplyJobPosting( mockApplyService.getMockApplyJobPosting(userDetails.getUser(), mockApplyId) ); } + + @Operation( + summary = "같은 공고 기준 자소서 개수 및 순번 조회", + description = "현재 mockApply가 연결된 채용 공고 기준으로, 로그인 사용자가 생성한 자소서 총 개수와 현재 자소서 순번을 조회합니다." + ) + @GetMapping("/{mockApplyId}/sequence") + public ApiResponse getMockApplySequence( + @AuthenticationPrincipal UserDetailsImpl userDetails, + @PathVariable Long mockApplyId + ) { + return ApiResponse.onSuccess( + "자소서 순번 조회에 성공했습니다.", + mockApplyService.getMockApplySequence(userDetails.getUser(), mockApplyId) + ); + } } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/dto/response/MockApplySequenceResponse.java b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/dto/response/MockApplySequenceResponse.java new file mode 100644 index 0000000..d6a1ed8 --- /dev/null +++ b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/dto/response/MockApplySequenceResponse.java @@ -0,0 +1,9 @@ +package com.jobdri.jobdri_api.domain.mockapply.dto.response; + +public record MockApplySequenceResponse( + Long jobPostingId, + Long mockApplyId, + int totalCount, + int sequence +) { +} diff --git a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java index 1a9ade8..239459e 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java @@ -8,4 +8,5 @@ public interface MockApplyRepository extends JpaRepository { List findAllByUserId(Long userId); List findAllByJobPostingId(Long jobPostingId); + List findAllByUserIdAndJobPostingIdOrderByCreatedAtAscIdAsc(Long userId, Long jobPostingId); } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java index 065a6c0..0b64516 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java @@ -12,6 +12,7 @@ import com.jobdri.jobdri_api.domain.mockapply.dto.request.MockApplyCreateMockFromJobPostingRequest; import com.jobdri.jobdri_api.domain.mockapply.dto.request.MockApplyCreateMockRequest; import com.jobdri.jobdri_api.domain.mockapply.dto.response.MockApplyCreateResponse; +import com.jobdri.jobdri_api.domain.mockapply.dto.response.MockApplySequenceResponse; import com.jobdri.jobdri_api.domain.mockapply.entity.ApplyType; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; import com.jobdri.jobdri_api.domain.mockapply.repository.MockApplyRepository; @@ -89,6 +90,39 @@ public JobPostingResponse getMockApplyJobPosting(User user, Long mockApplyId) { return JobPostingResponse.from(mockApply.getJobPosting()); } + public MockApplySequenceResponse getMockApplySequence(User user, Long mockApplyId) { + User validatedUser = userService.validateUser(user); + MockApply mockApply = getOwnedMockApply(validatedUser, mockApplyId); + + java.util.List mockApplies = mockApplyRepository + .findAllByUserIdAndJobPostingIdOrderByCreatedAtAscIdAsc( + validatedUser.getId(), + mockApply.getJobPosting().getId() + ); + + int sequence = -1; + for (int i = 0; i < mockApplies.size(); i++) { + if (mockApplies.get(i).getId().equals(mockApply.getId())) { + sequence = i + 1; + break; + } + } + + if (sequence < 0) { + throw new GeneralException( + GeneralErrorCode.MOCK_APPLY_NOT_FOUND, + "해당 공고에 연결된 모의 서류 지원 순서를 찾을 수 없습니다. mockApplyId=" + mockApplyId + ); + } + + return new MockApplySequenceResponse( + mockApply.getJobPosting().getId(), + mockApply.getId(), + mockApplies.size(), + sequence + ); + } + private MockApply getOwnedMockApply(User user, Long mockApplyId) { MockApply mockApply = mockApplyRepository.findById(mockApplyId) .orElseThrow(() -> new GeneralException( diff --git a/src/test/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyServiceTest.java b/src/test/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyServiceTest.java index 3d280ef..4ecd87e 100644 --- a/src/test/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyServiceTest.java +++ b/src/test/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyServiceTest.java @@ -14,6 +14,7 @@ import com.jobdri.jobdri_api.domain.jobposting.service.MockJobPostingGenerationService; import com.jobdri.jobdri_api.domain.mockapply.dto.request.MockApplyCreateMockRequest; import com.jobdri.jobdri_api.domain.mockapply.dto.response.MockApplyCreateResponse; +import com.jobdri.jobdri_api.domain.mockapply.dto.response.MockApplySequenceResponse; import com.jobdri.jobdri_api.domain.mockapply.entity.ApplyType; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; import com.jobdri.jobdri_api.domain.mockapply.entity.MockApplyStatus; @@ -28,6 +29,7 @@ import org.springframework.boot.test.context.SpringBootTest; import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.test.context.ActiveProfiles; +import org.springframework.test.util.ReflectionTestUtils; import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -152,6 +154,29 @@ void getMockApplyJobPosting() { assertThat(response.getCompanyName()).isEqualTo(jobPosting.getCompany().getName()); } + @Test + @DisplayName("같은 공고 기준 자소서 총 개수와 현재 순번을 조회한다") + void getMockApplySequence() { + User user = saveUser("sequence@example.com"); + JobPosting jobPosting = saveJobPosting(user, "백엔드 개발"); + MockApply first = mockApplyRepository.save(MockApply.create(user, jobPosting, ApplyType.MOCK)); + MockApply second = mockApplyRepository.save(MockApply.create(user, jobPosting, ApplyType.MOCK)); + MockApply third = mockApplyRepository.save(MockApply.create(user, jobPosting, ApplyType.ACTUAL)); + + ReflectionTestUtils.setField(first, "createdAt", first.getCreatedAt().minusMinutes(2)); + ReflectionTestUtils.setField(second, "createdAt", second.getCreatedAt().minusMinutes(1)); + mockApplyRepository.save(first); + mockApplyRepository.save(second); + mockApplyRepository.save(third); + + MockApplySequenceResponse response = mockApplyService.getMockApplySequence(user, second.getId()); + + assertThat(response.jobPostingId()).isEqualTo(jobPosting.getId()); + assertThat(response.mockApplyId()).isEqualTo(second.getId()); + assertThat(response.totalCount()).isEqualTo(3); + assertThat(response.sequence()).isEqualTo(2); + } + @Test @DisplayName("존재하지 않는 공고 ID로 ACTUAL 타입 지원 생성 시 예외를 던진다") void createActualApplyThrowsWhenJobPostingNotFound() { From d70ae4a4a4cc443f7680180a780e213acb68e1d7 Mon Sep 17 00:00:00 2001 From: shinae1023 Date: Fri, 22 May 2026 23:32:42 +0900 Subject: [PATCH 2/3] [Refactor] optimize mock apply sequence lookup with count queries (#27) --- .../repository/MockApplyRepository.java | 21 +++++++++++++- .../mockapply/service/MockApplyService.java | 29 +++++++++---------- 2 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java index 239459e..3ddd7a0 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/repository/MockApplyRepository.java @@ -2,11 +2,30 @@ import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; public interface MockApplyRepository extends JpaRepository { List findAllByUserId(Long userId); List findAllByJobPostingId(Long jobPostingId); - List findAllByUserIdAndJobPostingIdOrderByCreatedAtAscIdAsc(Long userId, Long jobPostingId); + long countByUserIdAndJobPostingId(Long userId, Long jobPostingId); + + @Query(""" + select count(ma) + from MockApply ma + where ma.user.id = :userId + and ma.jobPosting.id = :jobPostingId + and ( + ma.createdAt < :createdAt + or (ma.createdAt = :createdAt and ma.id <= :mockApplyId) + ) + """) + long countSequenceByUserIdAndJobPostingId( + @Param("userId") Long userId, + @Param("jobPostingId") Long jobPostingId, + @Param("createdAt") java.time.LocalDateTime createdAt, + @Param("mockApplyId") Long mockApplyId + ); } diff --git a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java index 0b64516..5b64b31 100644 --- a/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java +++ b/src/main/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyService.java @@ -93,22 +93,21 @@ public JobPostingResponse getMockApplyJobPosting(User user, Long mockApplyId) { public MockApplySequenceResponse getMockApplySequence(User user, Long mockApplyId) { User validatedUser = userService.validateUser(user); MockApply mockApply = getOwnedMockApply(validatedUser, mockApplyId); + Long jobPostingId = mockApply.getJobPosting().getId(); - java.util.List mockApplies = mockApplyRepository - .findAllByUserIdAndJobPostingIdOrderByCreatedAtAscIdAsc( + int totalCount = Math.toIntExact( + mockApplyRepository.countByUserIdAndJobPostingId(validatedUser.getId(), jobPostingId) + ); + int sequence = Math.toIntExact( + mockApplyRepository.countSequenceByUserIdAndJobPostingId( validatedUser.getId(), - mockApply.getJobPosting().getId() - ); - - int sequence = -1; - for (int i = 0; i < mockApplies.size(); i++) { - if (mockApplies.get(i).getId().equals(mockApply.getId())) { - sequence = i + 1; - break; - } - } + jobPostingId, + mockApply.getCreatedAt(), + mockApply.getId() + ) + ); - if (sequence < 0) { + if (sequence < 1 || sequence > totalCount) { throw new GeneralException( GeneralErrorCode.MOCK_APPLY_NOT_FOUND, "해당 공고에 연결된 모의 서류 지원 순서를 찾을 수 없습니다. mockApplyId=" + mockApplyId @@ -116,9 +115,9 @@ public MockApplySequenceResponse getMockApplySequence(User user, Long mockApplyI } return new MockApplySequenceResponse( - mockApply.getJobPosting().getId(), + jobPostingId, mockApply.getId(), - mockApplies.size(), + totalCount, sequence ); } From 71df03053aca30660795625a59577581120912a2 Mon Sep 17 00:00:00 2001 From: shinae1023 Date: Fri, 22 May 2026 23:35:50 +0900 Subject: [PATCH 3/3] [Test] stabilize mock apply sequence ordering assertions (#27) --- .../mockapply/service/MockApplyServiceTest.java | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/src/test/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyServiceTest.java b/src/test/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyServiceTest.java index 4ecd87e..388bc1d 100644 --- a/src/test/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyServiceTest.java +++ b/src/test/java/com/jobdri/jobdri_api/domain/mockapply/service/MockApplyServiceTest.java @@ -32,6 +32,7 @@ import org.springframework.test.util.ReflectionTestUtils; import org.springframework.transaction.annotation.Transactional; +import java.time.LocalDateTime; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -163,11 +164,13 @@ void getMockApplySequence() { MockApply second = mockApplyRepository.save(MockApply.create(user, jobPosting, ApplyType.MOCK)); MockApply third = mockApplyRepository.save(MockApply.create(user, jobPosting, ApplyType.ACTUAL)); - ReflectionTestUtils.setField(first, "createdAt", first.getCreatedAt().minusMinutes(2)); - ReflectionTestUtils.setField(second, "createdAt", second.getCreatedAt().minusMinutes(1)); - mockApplyRepository.save(first); - mockApplyRepository.save(second); - mockApplyRepository.save(third); + LocalDateTime baseTime = LocalDateTime.of(2026, 1, 1, 12, 0); + ReflectionTestUtils.setField(first, "createdAt", baseTime); + ReflectionTestUtils.setField(second, "createdAt", baseTime.plusMinutes(1)); + ReflectionTestUtils.setField(third, "createdAt", baseTime.plusMinutes(2)); + mockApplyRepository.saveAndFlush(first); + mockApplyRepository.saveAndFlush(second); + mockApplyRepository.saveAndFlush(third); MockApplySequenceResponse response = mockApplyService.getMockApplySequence(user, second.getId());