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,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) {
Copy link
Copy Markdown
Member

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()를 사용해도 될 것 같아요

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) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

근데 mockApply 생성할 때 기본적으로 회사 + 직무를 고르고 들어가는거니까 그 회사 id를 받아와야할듯 이걸 하드코딩으로 박아두면 안될거 같음
글고 이거 공고 생성 자체를 rag기반 llm요청이니까 그부분 아직 구현 안됐으니 그냥껍데기로 하고 flow 맞춰주세요

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) {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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() + " 직무 관련 경험과 역량을 우대합니다.";
}
}
Loading
Loading