-
Notifications
You must be signed in to change notification settings - Fork 0
[Feat] 모의 서류 지원 생성 API 구현 (#38) #43
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,137 @@ | ||
| package com.jobdri.jobdri_api.domain.mockapply.controller; | ||
|
|
||
| import com.jobdri.jobdri_api.domain.mockapply.dto.request.MockApplyCreateActualRequest; | ||
| 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.service.MockApplyService; | ||
| import com.jobdri.jobdri_api.domain.user.entity.User; | ||
| import com.jobdri.jobdri_api.global.apiPayload.ApiResponse; | ||
| import com.jobdri.jobdri_api.global.apiPayload.code.GeneralErrorCode; | ||
| import com.jobdri.jobdri_api.global.apiPayload.exception.GeneralException; | ||
| import com.jobdri.jobdri_api.global.security.UserDetailsImpl; | ||
| import io.swagger.v3.oas.annotations.Operation; | ||
| import io.swagger.v3.oas.annotations.media.Content; | ||
| import io.swagger.v3.oas.annotations.media.ExampleObject; | ||
| import io.swagger.v3.oas.annotations.media.Schema; | ||
| import io.swagger.v3.oas.annotations.responses.ApiResponses; | ||
| import io.swagger.v3.oas.annotations.tags.Tag; | ||
| import jakarta.validation.Valid; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.security.core.annotation.AuthenticationPrincipal; | ||
| import org.springframework.web.bind.annotation.PostMapping; | ||
| import org.springframework.web.bind.annotation.RequestBody; | ||
| import org.springframework.web.bind.annotation.RequestMapping; | ||
| import org.springframework.web.bind.annotation.RestController; | ||
|
|
||
| @RestController | ||
| @RequiredArgsConstructor | ||
| @RequestMapping("/api/mock-applies") | ||
| @Tag(name = "MockApply", description = "모의 서류 지원 생성 API") | ||
| public class MockApplyController { | ||
|
|
||
| private final MockApplyService mockApplyService; | ||
|
|
||
| @Operation( | ||
| summary = "실제 공고 기반 모의 서류 지원 생성", | ||
| description = "기존 채용 공고 ID를 기준으로 로그인 사용자의 ACTUAL 타입 모의 서류 지원을 생성합니다." | ||
| ) | ||
| @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\":{\"jobPostingId\":1,\"mockApplyId\":10,\"applyType\":\"ACTUAL\"},\"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 = "404", | ||
| description = "채용 공고 없음", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiResponse.class), | ||
| examples = @ExampleObject(value = "{\"isSuccess\":false,\"code\":\"JOB_POSTING_4041\",\"message\":\"해당 공고를 찾을 수 없습니다. jobPostingId=999\",\"result\":null,\"error\":\"해당 공고를 찾을 수 없습니다. jobPostingId=999\"}") | ||
| ) | ||
| ) | ||
| }) | ||
| @PostMapping("/actual") | ||
| public ApiResponse<MockApplyCreateResponse> createActualApply( | ||
| @AuthenticationPrincipal UserDetailsImpl userDetails, | ||
| @Valid @RequestBody MockApplyCreateActualRequest request | ||
| ) { | ||
| return ApiResponse.onSuccess( | ||
| "모의 서류 지원이 생성되었습니다.", | ||
| mockApplyService.createActualApply(getCurrentUser(userDetails), request.jobPostingId()) | ||
| ); | ||
| } | ||
|
|
||
| @Operation( | ||
| summary = "가상 공고 기반 모의 서류 지원 생성", | ||
| description = "선택한 소분류를 기준으로 가상 채용 공고와 MOCK 타입 모의 서류 지원을 함께 생성합니다." | ||
| ) | ||
| @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\":{\"jobPostingId\":1,\"mockApplyId\":10,\"applyType\":\"MOCK\"},\"error\":null}") | ||
| ) | ||
| ), | ||
| @io.swagger.v3.oas.annotations.responses.ApiResponse( | ||
| responseCode = "400", | ||
| description = "요청값 검증 실패", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiResponse.class), | ||
| examples = @ExampleObject(value = "{\"isSuccess\":false,\"code\":\"REQ_4002\",\"message\":\"파라미터 형식이 잘못되었습니다.\",\"result\":null,\"error\":[\"[detailClassificationId] 소분류 ID는 필수입니다. (입력값: 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 = "404", | ||
| description = "소분류 없음", | ||
| content = @Content( | ||
| mediaType = "application/json", | ||
| schema = @Schema(implementation = ApiResponse.class), | ||
| examples = @ExampleObject(value = "{\"isSuccess\":false,\"code\":\"CLASSIFICATION_4041\",\"message\":\"해당 소분류를 찾을 수 없습니다. detailClassificationId=999\",\"result\":null,\"error\":\"해당 소분류를 찾을 수 없습니다. detailClassificationId=999\"}") | ||
| ) | ||
| ) | ||
| }) | ||
| @PostMapping("/mock") | ||
| public ApiResponse<MockApplyCreateResponse> createMockApply( | ||
| @AuthenticationPrincipal UserDetailsImpl userDetails, | ||
| @Valid @RequestBody MockApplyCreateMockRequest request | ||
| ) { | ||
| return ApiResponse.onSuccess( | ||
| "모의 서류 지원이 생성되었습니다.", | ||
| mockApplyService.createMockApply(getCurrentUser(userDetails), request) | ||
| ); | ||
| } | ||
|
|
||
| private User getCurrentUser(UserDetailsImpl userDetails) { | ||
| if (userDetails == null) { | ||
| throw new GeneralException(GeneralErrorCode.MISSING_AUTH_INFO); | ||
| } | ||
| return userDetails.getUser(); | ||
| } | ||
| } | ||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,9 @@ | ||
| package com.jobdri.jobdri_api.domain.mockapply.dto.request; | ||
|
|
||
| import jakarta.validation.constraints.NotNull; | ||
|
|
||
| public record MockApplyCreateActualRequest( | ||
| @NotNull(message = "공고 ID는 필수입니다.") | ||
| Long jobPostingId | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,15 @@ | ||
| package com.jobdri.jobdri_api.domain.mockapply.dto.request; | ||
|
|
||
| import jakarta.validation.constraints.NotNull; | ||
|
|
||
| public record MockApplyCreateMockRequest( | ||
| @NotNull(message = "소분류 ID는 필수입니다.") | ||
| Long detailClassificationId, | ||
|
|
||
| String task, | ||
|
|
||
| String requirement, | ||
|
|
||
| String preferred | ||
| ) { | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,18 @@ | ||
| package com.jobdri.jobdri_api.domain.mockapply.dto.response; | ||
|
|
||
| import com.jobdri.jobdri_api.domain.mockapply.entity.ApplyType; | ||
| import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; | ||
|
|
||
| public record MockApplyCreateResponse( | ||
| Long jobPostingId, | ||
| Long mockApplyId, | ||
| ApplyType applyType | ||
| ) { | ||
| public static MockApplyCreateResponse from(MockApply mockApply) { | ||
| return new MockApplyCreateResponse( | ||
| mockApply.getJobPosting().getId(), | ||
| mockApply.getId(), | ||
| mockApply.getApplyType() | ||
| ); | ||
| } | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,92 @@ | ||
| package com.jobdri.jobdri_api.domain.mockapply.service; | ||
|
|
||
| import com.jobdri.jobdri_api.domain.classification.entity.DetailClassification; | ||
| import com.jobdri.jobdri_api.domain.classification.repository.DetailClassificationRepository; | ||
| import com.jobdri.jobdri_api.domain.company.entity.Company; | ||
| import com.jobdri.jobdri_api.domain.company.entity.CompanySize; | ||
| import com.jobdri.jobdri_api.domain.company.repository.CompanyRepository; | ||
| import com.jobdri.jobdri_api.domain.jobposting.entity.JobPosting; | ||
| import com.jobdri.jobdri_api.domain.jobposting.repository.JobPostingRepository; | ||
| 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.entity.ApplyType; | ||
| import com.jobdri.jobdri_api.domain.mockapply.entity.MockApply; | ||
| import com.jobdri.jobdri_api.domain.mockapply.repository.MockApplyRepository; | ||
| import com.jobdri.jobdri_api.domain.user.entity.User; | ||
| import com.jobdri.jobdri_api.global.apiPayload.code.GeneralErrorCode; | ||
| import com.jobdri.jobdri_api.global.apiPayload.exception.GeneralException; | ||
| import lombok.RequiredArgsConstructor; | ||
| import org.springframework.stereotype.Service; | ||
| import org.springframework.transaction.annotation.Transactional; | ||
| import org.springframework.util.StringUtils; | ||
|
|
||
| @Service | ||
| @RequiredArgsConstructor | ||
| @Transactional(readOnly = true) | ||
| public class MockApplyService { | ||
|
|
||
| private static final String VIRTUAL_COMPANY_NAME = "가상 기업"; | ||
| private static final CompanySize VIRTUAL_COMPANY_SIZE = CompanySize.STARTUP; | ||
|
|
||
| private final MockApplyRepository mockApplyRepository; | ||
| private final JobPostingRepository jobPostingRepository; | ||
| private final DetailClassificationRepository detailClassificationRepository; | ||
| private final CompanyRepository companyRepository; | ||
|
|
||
| @Transactional | ||
| public MockApplyCreateResponse createActualApply(User user, Long jobPostingId) { | ||
| JobPosting jobPosting = jobPostingRepository.findById(jobPostingId) | ||
| .orElseThrow(() -> new GeneralException( | ||
| GeneralErrorCode.JOB_POSTING_NOT_FOUND, | ||
| "해당 공고를 찾을 수 없습니다. jobPostingId=" + jobPostingId | ||
| )); | ||
|
|
||
| MockApply mockApply = MockApply.create(user, jobPosting, ApplyType.ACTUAL); | ||
| return MockApplyCreateResponse.from(mockApplyRepository.save(mockApply)); | ||
| } | ||
|
|
||
| @Transactional | ||
| public MockApplyCreateResponse createMockApply(User user, MockApplyCreateMockRequest request) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 근데 mockApply 생성할 때 기본적으로 회사 + 직무를 고르고 들어가는거니까 그 회사 id를 받아와야할듯 이걸 하드코딩으로 박아두면 안될거 같음 |
||
| DetailClassification detailClassification = detailClassificationRepository.findById(request.detailClassificationId()) | ||
| .orElseThrow(() -> new GeneralException( | ||
| GeneralErrorCode.CLASSIFICATION_NOT_FOUND, | ||
| "해당 소분류를 찾을 수 없습니다. detailClassificationId=" + request.detailClassificationId() | ||
| )); | ||
|
|
||
| Company company = companyRepository.findByName(VIRTUAL_COMPANY_NAME) | ||
| .orElseGet(() -> companyRepository.save(Company.create(VIRTUAL_COMPANY_NAME, VIRTUAL_COMPANY_SIZE))); | ||
|
|
||
| JobPosting jobPosting = JobPosting.create( | ||
| company, | ||
| detailClassification, | ||
| resolveTask(request.task(), detailClassification), | ||
| resolveRequirement(request.requirement(), detailClassification), | ||
| resolvePreferred(request.preferred(), detailClassification) | ||
| ); | ||
| JobPosting savedJobPosting = jobPostingRepository.save(jobPosting); | ||
|
|
||
| MockApply mockApply = MockApply.create(user, savedJobPosting, ApplyType.MOCK); | ||
| return MockApplyCreateResponse.from(mockApplyRepository.save(mockApply)); | ||
| } | ||
|
|
||
| private String resolveTask(String task, DetailClassification detailClassification) { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이건 왜 이렇게 하는건가요? |
||
| if (StringUtils.hasText(task)) { | ||
| return task; | ||
| } | ||
| return detailClassification.getDetailName() + " 직무 기반 가상 주요 업무를 수행합니다."; | ||
| } | ||
|
|
||
| private String resolveRequirement(String requirement, DetailClassification detailClassification) { | ||
| if (StringUtils.hasText(requirement)) { | ||
| return requirement; | ||
| } | ||
| return detailClassification.getDetailName() + " 직무 수행에 필요한 기본 자격 요건을 갖춥니다."; | ||
| } | ||
|
|
||
| private String resolvePreferred(String preferred, DetailClassification detailClassification) { | ||
| if (StringUtils.hasText(preferred)) { | ||
| return preferred; | ||
| } | ||
| return detailClassification.getDetailName() + " 직무 관련 경험과 역량을 우대합니다."; | ||
| } | ||
| } | ||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
userDetails가 없는 경우는 스프링 시큐리티의 AuthenticationEntryPoint에서 이미 AUTH_4011로 처리하고 있어서 컨트롤러에서 동일한 null 검증을 한 번 더 할 필요는 없고 여기서는 바로 userDetails.getUser()를 사용해도 될 것 같아요