diff --git a/src/main/java/com/chaineeproject/chainee/controller/JobApplicationController.java b/src/main/java/com/chaineeproject/chainee/controller/JobApplicationController.java index fa47e02..2a4afd2 100644 --- a/src/main/java/com/chaineeproject/chainee/controller/JobApplicationController.java +++ b/src/main/java/com/chaineeproject/chainee/controller/JobApplicationController.java @@ -1,3 +1,4 @@ +// src/main/java/com/chaineeproject/chainee/controller/JobApplicationController.java package com.chaineeproject.chainee.controller; import com.chaineeproject.chainee.dto.JobApplicationRequest; @@ -6,8 +7,11 @@ import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.web.bind.annotation.*; @RestController @@ -19,14 +23,17 @@ public class JobApplicationController { private final JobApplicationService jobApplicationService; @PostMapping("/{postId}/apply") - @Operation(summary = "채용 공고 지원", description = "지정한 채용 공고 ID에 대해 지원서를 제출합니다.") + @Operation(summary = "채용 공고 지원", description = "요청 사용자의 JWT uid를 지원자로 하여 지정한 공고에 지원합니다.") public ResponseEntity applyToJob( + @AuthenticationPrincipal Jwt jwt, @Parameter(description = "지원할 채용 공고 ID", example = "1") @PathVariable Long postId, - - @RequestBody JobApplicationRequest request + @Valid @RequestBody JobApplicationRequest request ) { - jobApplicationService.applyToJob(postId, request); + Long applicantId = com.chaineeproject.chainee.security.SecurityUtils.uidOrNull(jwt); + if (applicantId == null) return ResponseEntity.status(401).build(); + + jobApplicationService.applyToJob(postId, applicantId, request.resumeId()); return ResponseEntity.ok(new JobApplicationResponse(true, "APPLICATION_SUBMITTED")); } } diff --git a/src/main/java/com/chaineeproject/chainee/dto/JobApplicationRequest.java b/src/main/java/com/chaineeproject/chainee/dto/JobApplicationRequest.java index 7e9295f..ca663b7 100644 --- a/src/main/java/com/chaineeproject/chainee/dto/JobApplicationRequest.java +++ b/src/main/java/com/chaineeproject/chainee/dto/JobApplicationRequest.java @@ -1,12 +1,11 @@ +// src/main/java/com/chaineeproject/chainee/dto/JobApplicationRequest.java package com.chaineeproject.chainee.dto; import io.swagger.v3.oas.annotations.media.Schema; +import jakarta.validation.constraints.NotNull; @Schema(description = "채용 공고 지원 요청 DTO") public record JobApplicationRequest( - @Schema(description = "지원자의 ID", example = "19") - Long applicantId, - - @Schema(description = "지원자가 선택한 이력서 ID", example = "42") - Long resumeId + @Schema(description = "지원자가 제출할 이력서 ID", example = "42") + @NotNull Long resumeId ) {} diff --git a/src/main/java/com/chaineeproject/chainee/service/JobApplicationService.java b/src/main/java/com/chaineeproject/chainee/service/JobApplicationService.java index aafd892..9db45e3 100644 --- a/src/main/java/com/chaineeproject/chainee/service/JobApplicationService.java +++ b/src/main/java/com/chaineeproject/chainee/service/JobApplicationService.java @@ -1,7 +1,6 @@ // src/main/java/com/chaineeproject/chainee/service/JobApplicationService.java package com.chaineeproject.chainee.service; -import com.chaineeproject.chainee.dto.JobApplicationRequest; import com.chaineeproject.chainee.entity.*; import com.chaineeproject.chainee.event.JobApplicationCreatedEvent; import com.chaineeproject.chainee.exception.ApplicationException; @@ -25,25 +24,28 @@ public class JobApplicationService { private final ApplicationEventPublisher eventPublisher; @Transactional - public void applyToJob(Long postId, JobApplicationRequest request) { - // 1. 지원자 조회 (ID) - User applicant = userRepository.findById(request.applicantId()) + public void applyToJob(Long postId, Long applicantId, Long resumeId) { + // 1) 지원자 조회(JWT uid로 강제) + User applicant = userRepository.findById(applicantId) .orElseThrow(() -> new ApplicationException(ErrorCode.APPLICANT_NOT_FOUND)); - // 2. 공고 조회 + // 2) 공고 조회 JobPost post = jobPostRepository.findById(postId) .orElseThrow(() -> new ApplicationException(ErrorCode.JOB_POST_NOT_FOUND)); - // 3. 이력서 조회 - Resume resume = resumeRepository.findById(request.resumeId()) + // 3) 이력서 조회 + 소유권 검증(남의 이력서로 지원 방지) + Resume resume = resumeRepository.findById(resumeId) .orElseThrow(() -> new ApplicationException(ErrorCode.RESUME_NOT_FOUND)); + if (resume.getOwner() == null || !resume.getOwner().getId().equals(applicantId)) { + throw new ApplicationException(ErrorCode.FORBIDDEN); // 또는 전용 에러코드 정의 가능: NOT_OWNER_OF_RESUME + } - // 4. 중복 지원 방지 - if (jobApplicationRepository.existsByPostIdAndApplicantId(postId, applicant.getId())) { + // 4) 중복 지원 방지 + if (jobApplicationRepository.existsByPostIdAndApplicantId(postId, applicantId)) { throw new ApplicationException(ErrorCode.DUPLICATE_APPLICATION); } - // 5. 지원 엔티티 생성 및 저장 + // 5) 저장 JobApplication application = new JobApplication(); application.setPost(post); application.setApplicant(applicant); @@ -53,6 +55,8 @@ public void applyToJob(Long postId, JobApplicationRequest request) { jobApplicationRepository.save(application); jobPostRepository.incrementApplicantCount(postId); + + // 6) 이벤트(알림용) eventPublisher.publishEvent(new JobApplicationCreatedEvent(application.getId())); } }