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 @@ -11,6 +11,13 @@
public interface AnalysisRepository extends JpaRepository<Analysis, Long> {
Optional<Analysis> findByMockApplyId(Long mockApplyId);

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("""
delete from Analysis a
where a.mockApply.id = :mockApplyId
""")
void deleteByMockApplyId(@Param("mockApplyId") Long mockApplyId);

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("""
delete from Analysis a
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,22 @@ public interface QuestionAnalysisRepository extends JpaRepository<QuestionAnalys
List<QuestionAnalysis> findAllByAnalysisIdOrderByQuestionIdAscIdAsc(Long analysisId);
void deleteAllByAnalysisId(Long analysisId);

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("""
delete from QuestionAnalysis qa
where qa.analysis.id in (
select a.id
from Analysis a
where a.mockApply.id = :mockApplyId
)
or qa.question.id in (
select q.id
from Question q
where q.mockApply.id = :mockApplyId
)
""")
void deleteAllByMockApplyId(@Param("mockApplyId") Long mockApplyId);

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("""
delete from QuestionAnalysis qa
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
Expand Down Expand Up @@ -49,6 +50,57 @@ public ApiResponse<MockApplyHomeResponse> getMyMockApplies(
);
}

@Operation(
summary = "모의 서류 지원 단건 삭제",
description = "mockApplyId에 해당하는 모의 서류 지원과 연결된 문항, 분석 결과만 삭제합니다. 같은 공고의 다른 모의 지원은 유지됩니다."
)
@ApiResponses(value = {
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "200",
description = "모의 서류 지원 삭제 성공",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ApiResponse.class),
examples = @ExampleObject(value = "{\"isSuccess\":true,\"code\":\"COMMON2000\",\"message\":\"모의 서류 지원이 삭제되었습니다.\",\"result\":null,\"error\":null}")
)
),
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "401",
description = "인증 정보 누락",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ApiResponse.class),
examples = @ExampleObject(value = "{\"isSuccess\":false,\"code\":\"AUTH_4011\",\"message\":\"인증 정보가 누락되었습니다.\",\"result\":null,\"error\":\"인증 정보가 누락되었습니다.\"}")
)
),
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "403",
description = "다른 사용자의 모의 서류 지원 접근",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ApiResponse.class),
examples = @ExampleObject(value = "{\"isSuccess\":false,\"code\":\"AUTH_4031\",\"message\":\"해당 모의 서류 지원에 접근할 수 없습니다.\",\"result\":null,\"error\":\"해당 모의 서류 지원에 접근할 수 없습니다.\"}")
)
),
@io.swagger.v3.oas.annotations.responses.ApiResponse(
responseCode = "404",
description = "모의 서류 지원 없음",
content = @Content(
mediaType = "application/json",
schema = @Schema(implementation = ApiResponse.class),
examples = @ExampleObject(value = "{\"isSuccess\":false,\"code\":\"MOCK_APPLY_4041\",\"message\":\"해당 모의 서류 지원을 찾을 수 없습니다. mockApplyId=999\",\"result\":null,\"error\":\"해당 모의 서류 지원을 찾을 수 없습니다. mockApplyId=999\"}")
)
)
})
@DeleteMapping("/{mockApplyId}")
public ApiResponse<Void> deleteMockApply(
@AuthenticationPrincipal UserDetailsImpl userDetails,
@PathVariable Long mockApplyId
) {
mockApplyService.deleteMockApply(userDetails.getUser(), mockApplyId);
return ApiResponse.onSuccess("모의 서류 지원이 삭제되었습니다.", null);
}

@Operation(
summary = "실제 공고 기반 모의 서류 지원 생성",
description = "공고 텍스트/URL 추출, 공고 저장, 사용자 확인 및 수정이 선행된 뒤 저장된 채용 공고 ID를 기준으로 로그인 사용자의 ACTUAL 타입 모의 서류 지원을 생성합니다."
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ public interface MockApplyRepository extends JpaRepository<MockApply, Long> {
""")
void deleteAllByJobPostingId(Long jobPostingId);

@Modifying(clearAutomatically = true, flushAutomatically = true)
@Query("""
delete from MockApply ma
where ma.id = :mockApplyId
""")
void deleteByMockApplyId(@Param("mockApplyId") Long mockApplyId);

@Query("""
select ma
from MockApply ma
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.repository.AnalysisRepository;
import com.jobdri.jobdri_api.domain.analysis.repository.QuestionAnalysisRepository;
import com.jobdri.jobdri_api.domain.analysis.repository.QuestionRepository;
import com.jobdri.jobdri_api.domain.company.entity.Company;
import com.jobdri.jobdri_api.domain.company.repository.CompanyRepository;
Expand Down Expand Up @@ -48,6 +50,8 @@ public class MockApplyService {
private static final String UNIQUE_VIOLATION_SQL_STATE = "23505";

private final MockApplyRepository mockApplyRepository;
private final QuestionAnalysisRepository questionAnalysisRepository;
private final AnalysisRepository analysisRepository;
private final JobPostingRepository jobPostingRepository;
private final CompanyRepository companyRepository;
private final QuestionRepository questionRepository;
Expand Down Expand Up @@ -213,6 +217,18 @@ public MockApplyHomeResponse getMyMockApplies(User user) {
);
}

@Transactional
@AuditLogEvent(action = "MOCK_APPLY_DELETE", targetType = "MOCK_APPLY", targetId = "#arg1")
public void deleteMockApply(User user, Long mockApplyId) {
User validatedUser = userService.validateUser(user);
getOwnedMockApply(validatedUser, mockApplyId);

questionAnalysisRepository.deleteAllByMockApplyId(mockApplyId);
analysisRepository.deleteByMockApplyId(mockApplyId);
questionRepository.deleteAllByMockApplyId(mockApplyId);
mockApplyRepository.deleteByMockApplyId(mockApplyId);
}

private List<MockApplyHomeItemResponse> filterByCompletion(
List<MockApplyHomeItemResponse> items,
boolean completed
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,10 @@

import com.jobdri.jobdri_api.domain.analysis.entity.Analysis;
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.AnalysisRepository;
import com.jobdri.jobdri_api.domain.analysis.repository.QuestionAnalysisRepository;
import com.jobdri.jobdri_api.domain.analysis.repository.QuestionRepository;
import com.jobdri.jobdri_api.domain.classification.entity.Classification;
import com.jobdri.jobdri_api.domain.classification.entity.DetailClassification;
Expand Down Expand Up @@ -65,6 +68,9 @@ class MockApplyServiceTest {
@Autowired
private AnalysisRepository analysisRepository;

@Autowired
private QuestionAnalysisRepository questionAnalysisRepository;

@Autowired
private QuestionRepository questionRepository;

Expand Down Expand Up @@ -349,6 +355,34 @@ void getMyMockApplies() {
assertThat(response.completed().get(0).resumePath()).isEqualTo("/mock-applies/" + completed.getId() + "/analysis");
}

@Test
@DisplayName("모의 서류 지원을 삭제하면 해당 지원의 문항과 분석만 삭제한다")
void deleteMockApplyDeletesOnlyTargetMockApplyResults() {
User user = saveUser("mock-apply-delete@example.com");
JobPosting jobPosting = saveJobPosting(user, "백엔드 개발");
MockApply target = saveMockApply(user, jobPosting, ApplyType.MOCK, 1);
MockApply remaining = saveMockApply(user, jobPosting, ApplyType.MOCK, 2);
Question targetQuestion = saveQuestion(target, "삭제 대상 문항", 1000, "삭제 대상 답변");
Question remainingQuestion = saveQuestion(remaining, "유지 대상 문항", 1000, "유지 대상 답변");
Analysis targetAnalysis = saveAnalysis(target, 70);
Analysis remainingAnalysis = saveAnalysis(remaining, 80);
QuestionAnalysis targetQuestionAnalysis = saveQuestionAnalysis(targetQuestion, targetAnalysis);
QuestionAnalysis remainingQuestionAnalysis = saveQuestionAnalysis(remainingQuestion, remainingAnalysis);

mockApplyService.deleteMockApply(user, target.getId());
mockApplyRepository.flush();

assertThat(mockApplyRepository.findById(target.getId())).isEmpty();
assertThat(questionRepository.findById(targetQuestion.getId())).isEmpty();
assertThat(analysisRepository.findById(targetAnalysis.getId())).isEmpty();
assertThat(questionAnalysisRepository.findById(targetQuestionAnalysis.getId())).isEmpty();
assertThat(jobPostingRepository.findById(jobPosting.getId())).isPresent();
assertThat(mockApplyRepository.findById(remaining.getId())).isPresent();
assertThat(questionRepository.findById(remainingQuestion.getId())).isPresent();
assertThat(analysisRepository.findById(remainingAnalysis.getId())).isPresent();
assertThat(questionAnalysisRepository.findById(remainingQuestionAnalysis.getId())).isPresent();
}

@Test
@DisplayName("존재하지 않는 공고 ID로 ACTUAL 타입 지원 생성 시 예외를 던진다")
void createActualApplyThrowsWhenJobPostingNotFound() {
Expand Down Expand Up @@ -436,6 +470,22 @@ void getMockApplyJobPostingThrowsWhenForbidden() {
.isEqualTo(GeneralErrorCode.FORBIDDEN);
}

@Test
@DisplayName("다른 사용자의 모의 서류 지원은 삭제할 수 없다")
void deleteMockApplyThrowsWhenForbidden() {
User owner = saveUser("delete-owner@example.com");
User otherUser = saveUser("delete-other@example.com");
JobPosting jobPosting = saveJobPosting(owner, "데이터 분석");
MockApply mockApply = saveMockApply(owner, jobPosting, ApplyType.MOCK, 1);

assertThatThrownBy(() -> mockApplyService.deleteMockApply(otherUser, mockApply.getId()))
.isInstanceOf(GeneralException.class)
.extracting("code")
.isEqualTo(GeneralErrorCode.FORBIDDEN);

assertThat(mockApplyRepository.findById(mockApply.getId())).isPresent();
}

private User saveUser(String email) {
return inNewTransaction(() -> userRepository.save(User.signup("테스트 사용자", email, "encoded-password")));
}
Expand Down Expand Up @@ -502,6 +552,30 @@ private Question saveQuestion(MockApply mockApply, String content, int limit, St
)));
}

private Analysis saveAnalysis(MockApply mockApply, int score) {
return inNewTransaction(() -> analysisRepository.save(Analysis.create(
mockApplyRepository.findById(mockApply.getId()).orElseThrow(),
score,
score,
score,
score,
"분석 결과입니다."
)));
}

private QuestionAnalysis saveQuestionAnalysis(Question question, Analysis analysis) {
return inNewTransaction(() -> questionAnalysisRepository.save(QuestionAnalysis.create(
questionRepository.findById(question.getId()).orElseThrow(),
analysisRepository.findById(analysis.getId()).orElseThrow(),
"답변입니다.",
"근거가 부족합니다.",
"구체적인 성과를 포함해 답변했습니다.",
QuestionAnalysisStatus.MENTIONED,
0,
5
)));
}

private <T> T inNewTransaction(Supplier<T> action) {
TransactionTemplate transactionTemplate = new TransactionTemplate(transactionManager);
transactionTemplate.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
Expand Down
Loading