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
@@ -0,0 +1,2 @@
CREATE INDEX IF NOT EXISTS idx_analysis_async_tasks_user_mock_apply_status
ON analysis_async_tasks (user_id, mock_apply_id, status);
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,11 @@ Optional<AnalysisAsyncTask> findFirstByUserIdAndMockApplyIdAndStatusInOrderByCre
Collection<TaskStatus> statuses
);

List<AnalysisAsyncTask> findByUserIdAndMockApplyIdInAndStatusIn(
Long userId,
Collection<Long> mockApplyIds,
Collection<TaskStatus> statuses
);
Comment thread
coderabbitai[bot] marked this conversation as resolved.

List<AnalysisAsyncTask> findByStatusIn(Collection<TaskStatus> statuses);
}
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,10 @@ public record MockApplyHomeItemResponse(
JobPostingProfileColor profileColor,
LocalDateTime createdAt,
ApplyType applyType,
Integer score
Integer score,
boolean analysisInProgress
) {
public static MockApplyHomeItemResponse from(MockApply mockApply) {
public static MockApplyHomeItemResponse from(MockApply mockApply, boolean analysisInProgress) {
JobPosting jobPosting = mockApply.getJobPosting();
String detailClassificationName = jobPosting.getDetailClassification().getDetailName();
Analysis analysis = mockApply.getAnalysis();
Expand All @@ -42,7 +43,8 @@ public static MockApplyHomeItemResponse from(MockApply mockApply) {
jobPosting.getProfileColor(),
mockApply.getCreatedAt(),
mockApply.getApplyType(),
analysis == null ? null : analysis.getScore()
analysis == null ? null : analysis.getScore(),
analysisInProgress
);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
package com.jobdri.jobdri_api.domain.mockapply.service;

import com.jobdri.jobdri_api.domain.analysis.entity.Question;
import com.jobdri.jobdri_api.domain.analysis.entity.AnalysisAsyncTask.TaskStatus;
import com.jobdri.jobdri_api.domain.analysis.repository.AnalysisAsyncTaskRepository;
import com.jobdri.jobdri_api.domain.analysis.repository.AnalysisRepository;
import com.jobdri.jobdri_api.domain.analysis.repository.QuestionAnalysisRepository;
import com.jobdri.jobdri_api.domain.analysis.repository.QuestionRepository;
Expand Down Expand Up @@ -36,6 +38,7 @@
import lombok.RequiredArgsConstructor;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageImpl;
import org.springframework.data.domain.PageRequest;
import org.springframework.data.domain.Pageable;
import org.springframework.data.domain.Sort;
Expand All @@ -46,6 +49,8 @@
import java.sql.SQLException;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
Expand All @@ -72,6 +77,7 @@ public class MockApplyService {
private final UserService userService;
private final MockApplyPersistenceService mockApplyPersistenceService;
private final MockApplySequenceService mockApplySequenceService;
private final AnalysisAsyncTaskRepository analysisAsyncTaskRepository;

@Transactional(propagation = Propagation.NOT_SUPPORTED)
@AuditLogEvent(action = "MOCK_APPLY_CREATE", targetType = "MOCK_APPLY", targetId = "#result.mockApplyId()")
Expand Down Expand Up @@ -222,11 +228,9 @@ public MockApplySequenceResponse getMockApplySequence(User user, Long mockApplyI

public MockApplyHomeResponse getMyMockApplies(User user, int page, int size) {
User validatedUser = userService.validateUser(user);
List<MockApplyHomeItemResponse> inProgressItems = mockApplyRepository
.findAllByUserIdAndStatusNotOrderByCreatedAtDescIdDesc(validatedUser.getId(), MockApplyStatus.COMPLETED)
.stream()
.map(MockApplyHomeItemResponse::from)
.toList();
List<MockApply> inProgressMockApplies = mockApplyRepository
.findAllByUserIdAndStatusNotOrderByCreatedAtDescIdDesc(validatedUser.getId(), MockApplyStatus.COMPLETED);
List<MockApplyHomeItemResponse> inProgressItems = toHomeItems(validatedUser.getId(), inProgressMockApplies);
Pageable pageable = PageRequest.of(
Math.max(page, 0),
Math.min(Math.max(size, 1), MAX_PAGE_SIZE),
Expand All @@ -237,7 +241,7 @@ public MockApplyHomeResponse getMyMockApplies(User user, int page, int size) {
);
Page<MockApplyHomeItemResponse> completedItems = mockApplyRepository
.findAllByUserIdAndStatus(validatedUser.getId(), MockApplyStatus.COMPLETED, pageable)
.map(MockApplyHomeItemResponse::from);
.map(mockApply -> MockApplyHomeItemResponse.from(mockApply, false));

return new MockApplyHomeResponse(
inProgressItems,
Expand All @@ -256,9 +260,8 @@ public List<MockApplyHomeItemResponse> getRecentMockApplies(User user, int limit
)
);

return mockApplyRepository.findAllByUserId(validatedUser.getId(), pageable)
.map(MockApplyHomeItemResponse::from)
.getContent();
List<MockApply> mockApplies = mockApplyRepository.findAllByUserId(validatedUser.getId(), pageable).getContent();
return toHomeItems(validatedUser.getId(), mockApplies);
}

public Page<MockApplyHomeItemResponse> getCompletedMockApplies(
Expand All @@ -285,7 +288,7 @@ public Page<MockApplyHomeItemResponse> getCompletedMockApplies(
maxScoreExclusive(resolvedFilter),
pageable
)
.map(MockApplyHomeItemResponse::from);
.map(mockApply -> MockApplyHomeItemResponse.from(mockApply, false));
}

public Page<MockApplyHomeItemResponse> searchMyMockApplies(User user, String query, int page, int size) {
Expand All @@ -300,8 +303,40 @@ public Page<MockApplyHomeItemResponse> searchMyMockApplies(User user, String que
)
);

return mockApplyRepository.searchByUserId(validatedUser.getId(), normalizedQuery, pageable)
.map(MockApplyHomeItemResponse::from);
Page<MockApply> mockApplyPage = mockApplyRepository.searchByUserId(validatedUser.getId(), normalizedQuery, pageable);
return new PageImpl<>(
toHomeItems(validatedUser.getId(), mockApplyPage.getContent()),
pageable,
mockApplyPage.getTotalElements()
);
}

private List<MockApplyHomeItemResponse> toHomeItems(Long userId, List<MockApply> mockApplies) {
Set<Long> activeAnalysisMockApplyIds = findActiveAnalysisMockApplyIds(userId, mockApplies);
return mockApplies.stream()
.map(mockApply -> MockApplyHomeItemResponse.from(
mockApply,
activeAnalysisMockApplyIds.contains(mockApply.getId())
))
.toList();
}

private Set<Long> findActiveAnalysisMockApplyIds(Long userId, List<MockApply> mockApplies) {
List<Long> mockApplyIds = mockApplies.stream()
.filter(mockApply -> mockApply.getStatus() != MockApplyStatus.COMPLETED)
.map(MockApply::getId)
.toList();
if (mockApplyIds.isEmpty()) {
return Set.of();
}
return analysisAsyncTaskRepository.findByUserIdAndMockApplyIdInAndStatusIn(
userId,
mockApplyIds,
List.of(TaskStatus.PENDING, TaskStatus.RUNNING)
)
.stream()
.map(task -> task.getMockApplyId())
.collect(Collectors.toSet());
}

@Transactional
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/schema.sql
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,9 @@ CREATE INDEX IF NOT EXISTS idx_mock_question_embeddings_hnsw
CREATE INDEX IF NOT EXISTS idx_job_posting_embeddings_hnsw
ON job_posting_embeddings USING hnsw (embedding vector_cosine_ops);

CREATE INDEX IF NOT EXISTS idx_analysis_async_tasks_user_mock_apply_status
ON analysis_async_tasks (user_id, mock_apply_id, status);

ALTER TABLE IF EXISTS analyses
ADD COLUMN IF NOT EXISTS missing_keywords TEXT NOT NULL DEFAULT '[]';

Expand Down
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
package com.jobdri.jobdri_api.domain.mockapply.service;

import com.jobdri.jobdri_api.domain.analysis.entity.Analysis;
import com.jobdri.jobdri_api.domain.analysis.entity.AnalysisAsyncTask;
import com.jobdri.jobdri_api.domain.analysis.entity.Question;
import com.jobdri.jobdri_api.domain.analysis.entity.QuestionAnalysis;
import com.jobdri.jobdri_api.domain.analysis.entity.QuestionAnalysisStatus;
import com.jobdri.jobdri_api.domain.analysis.repository.AnalysisAsyncTaskRepository;
import com.jobdri.jobdri_api.domain.analysis.repository.AnalysisRepository;
import com.jobdri.jobdri_api.domain.analysis.repository.QuestionAnalysisRepository;
import com.jobdri.jobdri_api.domain.analysis.repository.QuestionRepository;
Expand Down Expand Up @@ -49,6 +51,7 @@
import org.springframework.transaction.annotation.Transactional;
import org.springframework.transaction.support.TransactionTemplate;

import java.time.Instant;
import java.time.LocalDateTime;
import java.util.List;
import java.util.UUID;
Expand All @@ -73,6 +76,9 @@ class MockApplyServiceTest {
@Autowired
private AnalysisRepository analysisRepository;

@Autowired
private AnalysisAsyncTaskRepository analysisAsyncTaskRepository;

@Autowired
private QuestionAnalysisRepository questionAnalysisRepository;

Expand Down Expand Up @@ -338,6 +344,12 @@ void getMyMockApplies() {
MockApply inProgress = mockApplyRepository.save(MockApply.create(user, backendPosting, ApplyType.ACTUAL));
inProgress.updateStatus(MockApplyStatus.ANSWER_WRITE);
inProgress.updateDisplayName("카카오 백엔드 지원 연습");
analysisAsyncTaskRepository.save(AnalysisAsyncTask.pending(user.getId(), inProgress.getId(), 3));
MockApply runningInProgress = mockApplyRepository.save(MockApply.create(user, backendPosting, ApplyType.MOCK));
runningInProgress.updateStatus(MockApplyStatus.ANSWER_WRITE);
AnalysisAsyncTask runningTask = AnalysisAsyncTask.pending(user.getId(), runningInProgress.getId(), 3);
runningTask.markRunning("worker-1", 0, Instant.now());
analysisAsyncTaskRepository.save(runningTask);
MockApply completedFirst = mockApplyRepository.save(MockApply.create(user, dataPosting, ApplyType.MOCK));
completedFirst.updateStatus(MockApplyStatus.COMPLETED);
MockApply completedSecond = mockApplyRepository.save(MockApply.create(user, dataPosting, ApplyType.ACTUAL));
Expand All @@ -350,29 +362,36 @@ void getMyMockApplies() {

LocalDateTime baseTime = LocalDateTime.of(2026, 1, 1, 12, 0);
ReflectionTestUtils.setField(inProgress, "createdAt", baseTime);
ReflectionTestUtils.setField(runningInProgress, "createdAt", baseTime.plusSeconds(30));
ReflectionTestUtils.setField(completedFirst, "createdAt", baseTime.plusMinutes(1));
ReflectionTestUtils.setField(completedSecond, "createdAt", baseTime.plusMinutes(2));
mockApplyRepository.saveAndFlush(inProgress);
mockApplyRepository.saveAndFlush(runningInProgress);
mockApplyRepository.saveAndFlush(completedFirst);
mockApplyRepository.saveAndFlush(completedSecond);

MockApplyHomeResponse response = mockApplyService.getMyMockApplies(user, 0, 9);

assertThat(response.inProgress()).hasSize(1);
assertThat(response.inProgress()).hasSize(2);
assertThat(response.completed().getContent()).hasSize(2);
assertThat(response.inProgress().get(0).mockApplyId()).isEqualTo(inProgress.getId());
assertThat(response.inProgress().get(0).jobPostingId()).isEqualTo(backendPosting.getId());
assertThat(response.inProgress().get(0).displayName()).isEqualTo("카카오 백엔드 지원 연습");
assertThat(response.inProgress().get(0).sequence()).isEqualTo(1);
assertThat(response.inProgress().get(0).status()).isEqualTo(MockApplyStatus.ANSWER_WRITE);
assertThat(response.inProgress().get(0).companyName()).isEqualTo("테스트 기업");
assertThat(response.inProgress().get(0).detailClassificationName()).isEqualTo("백엔드 개발");
assertThat(response.inProgress().get(0).jobTitle()).isEqualTo("백엔드 개발");
assertThat(response.inProgress().get(0).profileColor()).isEqualTo(JobPostingProfileColor.BLUE);
assertThat(response.inProgress().get(0).createdAt()).isEqualTo(baseTime);
assertThat(response.inProgress().get(0).applyType()).isEqualTo(ApplyType.ACTUAL);
assertThat(response.inProgress().get(0).score()).isNull();
assertThat(response.inProgress().get(0).resumePath()).isEqualTo("/mock-applies/" + inProgress.getId() + "/answers");
MockApplyHomeItemResponse runningItem = response.inProgress().get(0);
MockApplyHomeItemResponse pendingItem = response.inProgress().get(1);
assertThat(runningItem.mockApplyId()).isEqualTo(runningInProgress.getId());
assertThat(runningItem.analysisInProgress()).isTrue();
assertThat(pendingItem.mockApplyId()).isEqualTo(inProgress.getId());
assertThat(pendingItem.jobPostingId()).isEqualTo(backendPosting.getId());
assertThat(pendingItem.displayName()).isEqualTo("카카오 백엔드 지원 연습");
assertThat(pendingItem.sequence()).isEqualTo(1);
assertThat(pendingItem.status()).isEqualTo(MockApplyStatus.ANSWER_WRITE);
assertThat(pendingItem.companyName()).isEqualTo("테스트 기업");
assertThat(pendingItem.detailClassificationName()).isEqualTo("백엔드 개발");
assertThat(pendingItem.jobTitle()).isEqualTo("백엔드 개발");
assertThat(pendingItem.profileColor()).isEqualTo(JobPostingProfileColor.BLUE);
assertThat(pendingItem.createdAt()).isEqualTo(baseTime);
assertThat(pendingItem.applyType()).isEqualTo(ApplyType.ACTUAL);
assertThat(pendingItem.score()).isNull();
assertThat(pendingItem.analysisInProgress()).isTrue();
assertThat(pendingItem.resumePath()).isEqualTo("/mock-applies/" + inProgress.getId() + "/answers");
assertThat(response.completed().getContent()).extracting(MockApplyHomeItemResponse::mockApplyId)
.containsExactly(completedSecond.getId(), completedFirst.getId());
assertThat(response.completed().getTotalElements()).isEqualTo(2);
Expand All @@ -383,6 +402,7 @@ void getMyMockApplies() {
assertThat(response.completed().getContent().get(0).profileColor()).isEqualTo(JobPostingProfileColor.GREEN);
assertThat(response.completed().getContent().get(0).displayName()).isNull();
assertThat(response.completed().getContent().get(0).score()).isEqualTo(81);
assertThat(response.completed().getContent().get(0).analysisInProgress()).isFalse();
assertThat(response.completed().getContent().get(0).applyType()).isEqualTo(ApplyType.ACTUAL);
assertThat(response.completed().getContent().get(0).resumePath()).isEqualTo("/mock-applies/" + completedSecond.getId() + "/analysis");
}
Expand Down
Loading