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 @@ -13,7 +13,6 @@
import com.jobdri.jobdri_api.domain.analysis.service.async.AnalysisWorkerBridgeService;
import com.jobdri.jobdri_api.domain.workerresult.dto.WorkerTaskResultResponse;
import com.jobdri.jobdri_api.global.apiPayload.ApiResponse;
import com.jobdri.jobdri_api.global.security.InternalApiKeyValidator;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand All @@ -23,7 +22,6 @@
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -34,32 +32,25 @@
@Tag(name = "Analysis Worker Internal", description = "자소서 분석 worker 내부 통신 API")
public class AnalysisWorkerInternalController {

private static final String INTERNAL_API_KEY_HEADER = "X-Internal-Api-Key";

private final InternalApiKeyValidator internalApiKeyValidator;
private final AnalysisWorkerBridgeService analysisWorkerBridgeService;
private final AnalysisAsyncTaskService analysisAsyncTaskService;

@Operation(summary = "자소서 분석 worker 작업 실행 상태 반영", description = "worker가 taskId 기준 자소서 분석 작업을 실행 중 상태로 변경합니다.")
@PostMapping("/tasks/{taskId}/running")
public ApiResponse<Void> markRunning(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@PathVariable String taskId,
@Valid @RequestBody AnalysisWorkerRunningRequest request
) {
internalApiKeyValidator.validate(internalApiKey);
analysisWorkerBridgeService.markRunning(taskId, request.workerId(), request.retryCount(), request.submittedAt());
return ApiResponse.onSuccess("자소서 분석 worker 작업 시작 상태를 반영했습니다.");
}

@Operation(summary = "자소서 분석 worker 작업 재시도 상태 반영", description = "worker가 자소서 분석 작업 실패 후 재시도 상태와 메타데이터를 반영합니다.")
@PostMapping("/tasks/{taskId}/retry")
public ApiResponse<Void> markRetry(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@PathVariable String taskId,
@Valid @RequestBody AnalysisWorkerRetryRequest request
) {
internalApiKeyValidator.validate(internalApiKey);
analysisWorkerBridgeService.markRetry(
taskId,
request.failureReason(),
Expand All @@ -74,11 +65,9 @@ public ApiResponse<Void> markRetry(
@Operation(summary = "자소서 분석 worker 작업 실패 반영", description = "worker가 자소서 분석 작업 실패 상태와 실패 메타데이터를 반영합니다.")
@PostMapping("/tasks/{taskId}/failed")
public ApiResponse<Void> failTask(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@PathVariable String taskId,
@Valid @RequestBody AnalysisWorkerFailureRequest request
) {
internalApiKeyValidator.validate(internalApiKey);
analysisWorkerBridgeService.failTask(
taskId,
request.failureReason(),
Expand All @@ -93,10 +82,8 @@ public ApiResponse<Void> failTask(
@Operation(summary = "자소서 분석 worker 컨텍스트 조회", description = "worker가 분석 실행에 필요한 자소서, 공고, 지원 정보 컨텍스트를 조회합니다.")
@PostMapping("/context")
public ApiResponse<AnalysisWorkerContextResponse> getContext(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@Valid @RequestBody AnalysisWorkerContextRequest request
) {
internalApiKeyValidator.validate(internalApiKey);
return ApiResponse.onSuccess(
"자소서 분석 worker 컨텍스트 조회에 성공했습니다.",
analysisWorkerBridgeService.getContext(request.taskId(), request.userId(), request.mockApplyId())
Expand All @@ -106,11 +93,9 @@ public ApiResponse<AnalysisWorkerContextResponse> getContext(
@Operation(summary = "자소서 분석 worker 작업 완료 반영", description = "worker가 생성한 분석 결과를 저장하고 taskId 기준 작업 완료 상태를 반영합니다.")
@PostMapping("/tasks/{taskId}/complete")
public ApiResponse<AnalysisResponse> completeTask(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@PathVariable String taskId,
@Valid @RequestBody AnalysisWorkerCompleteRequest request
) {
internalApiKeyValidator.validate(internalApiKey);
return ApiResponse.onSuccess(
"자소서 분석 worker 작업 완료 상태를 반영했습니다.",
analysisWorkerBridgeService.completeTask(taskId, request)
Expand All @@ -120,22 +105,18 @@ public ApiResponse<AnalysisResponse> completeTask(
@Operation(summary = "자소서 분석 worker 결과 선저장", description = "worker가 complete 호출 전에 taskId 기준 분석 결과를 durable storage에 저장합니다.")
@PostMapping("/tasks/{taskId}/result")
public ApiResponse<Void> storeResult(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@PathVariable String taskId,
@Valid @RequestBody AnalysisWorkerResultStoreRequest request
) {
internalApiKeyValidator.validate(internalApiKey);
analysisWorkerBridgeService.storeGeneratedResult(taskId, request);
return ApiResponse.onSuccess("자소서 분석 worker 결과 선저장에 성공했습니다.");
}

@Operation(summary = "자소서 분석 worker 저장 결과 조회", description = "worker가 taskId 기준으로 저장된 분석 결과 payload를 조회합니다.")
@GetMapping("/tasks/{taskId}/result")
public ApiResponse<WorkerTaskResultResponse> getStoredResult(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@PathVariable String taskId
) {
internalApiKeyValidator.validate(internalApiKey);
return ApiResponse.onSuccess(
"자소서 분석 worker 저장 결과 조회에 성공했습니다.",
analysisWorkerBridgeService.getStoredResult(taskId)
Expand All @@ -145,10 +126,8 @@ public ApiResponse<WorkerTaskResultResponse> getStoredResult(
@Operation(summary = "자소서 분석 worker 작업 상태 조회", description = "taskId 기준 자소서 분석 worker 비동기 작업 상태를 내부 용도로 조회합니다.")
@GetMapping("/tasks/{taskId}")
public ApiResponse<AnalysisAsyncStatusResponse> getTask(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@PathVariable String taskId
) {
internalApiKeyValidator.validate(internalApiKey);
return ApiResponse.onSuccess(
"자소서 분석 worker 작업 상태 조회에 성공했습니다.",
analysisAsyncTaskService.getTaskStatusByTaskId(taskId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@
import com.jobdri.jobdri_api.domain.jobposting.service.JobPostingWorkerBridgeService;
import com.jobdri.jobdri_api.domain.workerresult.dto.WorkerTaskResultResponse;
import com.jobdri.jobdri_api.global.apiPayload.ApiResponse;
import com.jobdri.jobdri_api.global.security.InternalApiKeyValidator;
import io.swagger.v3.oas.annotations.Hidden;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
Expand All @@ -25,7 +24,6 @@
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestHeader;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

Expand All @@ -38,32 +36,25 @@
@Tag(name = "JobPosting Worker Internal", description = "채용 공고 worker 내부 통신 API")
public class JobPostingWorkerInternalController {

private static final String INTERNAL_API_KEY_HEADER = "X-Internal-Api-Key";

private final InternalApiKeyValidator internalApiKeyValidator;
private final JobPostingWorkerBridgeService jobPostingWorkerBridgeService;
private final JobPostingAsyncFacadeService jobPostingAsyncFacadeService;

@Operation(summary = "채용 공고 worker 작업 실행 상태 반영", description = "worker가 taskId 기준 채용 공고 작업을 실행 중 상태로 변경합니다.")
@PostMapping("/tasks/{taskId}/running")
public ApiResponse<Void> markRunning(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@PathVariable String taskId,
@Valid @RequestBody JobPostingWorkerRunningRequest request
) {
internalApiKeyValidator.validate(internalApiKey);
jobPostingWorkerBridgeService.markRunning(taskId, request.workerId(), request.retryCount(), request.submittedAt());
return ApiResponse.onSuccess("채용 공고 worker 작업 시작 상태를 반영했습니다.");
}

@Operation(summary = "채용 공고 worker 작업 재시도 상태 반영", description = "worker가 채용 공고 작업 실패 후 재시도 상태와 메타데이터를 반영합니다.")
@PostMapping("/tasks/{taskId}/retry")
public ApiResponse<Void> markRetry(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@PathVariable String taskId,
@Valid @RequestBody JobPostingWorkerRetryRequest request
) {
internalApiKeyValidator.validate(internalApiKey);
jobPostingWorkerBridgeService.markRetry(
taskId,
request.failureReason(),
Expand All @@ -78,11 +69,9 @@ public ApiResponse<Void> markRetry(
@Operation(summary = "채용 공고 worker 작업 완료 반영", description = "legacy worker가 조립한 최종 응답으로 taskId 기준 작업 완료 상태를 반영합니다.")
@PostMapping("/tasks/{taskId}/complete")
public ApiResponse<JobPostingIngestResponse> completeTask(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@PathVariable String taskId,
@Valid @RequestBody JobPostingIngestResponse result
) {
internalApiKeyValidator.validate(internalApiKey);
return ApiResponse.onSuccess(
"채용 공고 worker 작업 완료 상태를 반영했습니다.",
jobPostingWorkerBridgeService.completeTask(taskId, result)
Expand All @@ -92,22 +81,18 @@ public ApiResponse<JobPostingIngestResponse> completeTask(
@Operation(summary = "채용 공고 worker 결과 선저장", description = "worker가 finalize 호출 전에 taskId 기준 채용 공고 결과를 durable storage에 저장합니다.")
@PostMapping("/tasks/{taskId}/result")
public ApiResponse<Void> storeResult(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@PathVariable String taskId,
@Valid @RequestBody JobPostingWorkerResultStoreRequest request
) {
internalApiKeyValidator.validate(internalApiKey);
jobPostingWorkerBridgeService.storeFinalizeResult(taskId, request);
return ApiResponse.onSuccess("채용 공고 worker 결과 선저장에 성공했습니다.");
}

@Operation(summary = "채용 공고 worker 저장 결과 조회", description = "worker가 taskId 기준으로 저장된 채용 공고 결과 payload를 조회합니다.")
@GetMapping("/tasks/{taskId}/result")
public ApiResponse<WorkerTaskResultResponse> getStoredResult(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@PathVariable String taskId
) {
internalApiKeyValidator.validate(internalApiKey);
return ApiResponse.onSuccess(
"채용 공고 worker 저장 결과 조회에 성공했습니다.",
jobPostingWorkerBridgeService.getStoredResult(taskId)
Expand All @@ -117,11 +102,9 @@ public ApiResponse<WorkerTaskResultResponse> getStoredResult(
@Operation(summary = "채용 공고 worker 작업 실패 반영", description = "worker가 채용 공고 작업 실패 상태와 실패 메타데이터를 반영합니다.")
@PostMapping("/tasks/{taskId}/failed")
public ApiResponse<Void> failTask(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@PathVariable String taskId,
@Valid @RequestBody JobPostingWorkerFailureRequest request
) {
internalApiKeyValidator.validate(internalApiKey);
jobPostingWorkerBridgeService.failTask(
taskId,
request.failureReason(),
Expand All @@ -136,10 +119,8 @@ public ApiResponse<Void> failTask(
@Operation(summary = "채용 공고 worker 컨텍스트 조회", description = "worker가 이미지 기반 채용 공고 처리를 위해 읽기 가능한 컨텍스트 정보를 조회합니다.")
@PostMapping("/ingest/context")
public ApiResponse<JobPostingWorkerContextResponse> getContext(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@Valid @RequestBody JobPostingWorkerContextRequest request
) {
internalApiKeyValidator.validate(internalApiKey);
List<String> imageUrls = jobPostingWorkerBridgeService.createReadableImageUrls(
request.userId(),
request.imageObjectKey(),
Expand All @@ -157,10 +138,8 @@ public ApiResponse<JobPostingWorkerContextResponse> getContext(
@Operation(summary = "채용 공고 분류 후보 조회", description = "추출된 채용 공고 정보를 바탕으로 분류 후보 목록을 조회합니다.")
@PostMapping("/classification/candidates")
public ApiResponse<List<JobPostingClassificationCandidateResponse>> getCandidates(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@Valid @RequestBody JobPostingExtractResponse extracted
) {
internalApiKeyValidator.validate(internalApiKey);
return ApiResponse.onSuccess(
"채용 공고 분류 후보 조회에 성공했습니다.",
jobPostingWorkerBridgeService.findCandidates(extracted)
Expand All @@ -170,10 +149,8 @@ public ApiResponse<List<JobPostingClassificationCandidateResponse>> getCandidate
@Operation(summary = "채용 공고 적재 후처리 및 완료", description = "추출, 분류, 생성 결과를 바탕으로 채용 공고 저장과 비동기 완료 처리를 한 번에 수행하는 주 성공 callback입니다.")
@PostMapping("/ingest/finalize")
public ApiResponse<JobPostingIngestResponse> finalizeTask(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@Valid @RequestBody JobPostingWorkerFinalizeRequest request
) {
internalApiKeyValidator.validate(internalApiKey);
return ApiResponse.onSuccess(
"채용 공고 저장 및 비동기 완료 처리에 성공했습니다.",
jobPostingWorkerBridgeService.finalizeAndComplete(
Expand All @@ -190,10 +167,8 @@ public ApiResponse<JobPostingIngestResponse> finalizeTask(
@Operation(summary = "채용 공고 worker 작업 상태 조회", description = "taskId 기준 채용 공고 worker 비동기 작업 상태를 내부 용도로 조회합니다.")
@GetMapping("/tasks/{taskId}")
public ApiResponse<JobPostingAsyncStatusResponse> getTask(
@RequestHeader(INTERNAL_API_KEY_HEADER) String internalApiKey,
@PathVariable String taskId
) {
internalApiKeyValidator.validate(internalApiKey);
return ApiResponse.onSuccess(
"채용 공고 worker 작업 상태 조회에 성공했습니다.",
jobPostingAsyncFacadeService.getTaskInternal(taskId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@
import com.jobdri.jobdri_api.global.jwt.JwtAuthenticationFilter;
import com.jobdri.jobdri_api.global.jwt.JwtUtil;
import com.jobdri.jobdri_api.global.metrics.AuthRedisMetricsRecorder;
import com.jobdri.jobdri_api.global.security.InternalApiKeyValidator;
import com.jobdri.jobdri_api.global.security.InternalWorkerApiKeyFilter;
import com.jobdri.jobdri_api.global.security.UserDetailsServiceImpl;
import com.jobdri.jobdri_api.global.logging.RequestContextLoggingFilter;
import lombok.RequiredArgsConstructor;
Expand All @@ -24,6 +26,7 @@
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
import org.springframework.web.cors.CorsConfigurationSource;
import com.fasterxml.jackson.databind.ObjectMapper;

import java.util.List;

Expand Down Expand Up @@ -52,6 +55,14 @@ public PortOneWebhookRateLimitFilter portOneWebhookRateLimitFilter() {
return new PortOneWebhookRateLimitFilter();
}

@Bean
public InternalWorkerApiKeyFilter internalWorkerApiKeyFilter(
InternalApiKeyValidator internalApiKeyValidator,
ObjectMapper objectMapper
) {
return new InternalWorkerApiKeyFilter(internalApiKeyValidator, objectMapper);
}

@Bean
public RequestContextLoggingFilter requestContextLoggingFilter(
@Value("${app.logging.request-id-max-length:64}") int requestIdMaxLength,
Expand Down Expand Up @@ -87,12 +98,22 @@ public FilterRegistrationBean<PortOneWebhookRateLimitFilter> portOneWebhookRateL
return registration;
}

@Bean
public FilterRegistrationBean<InternalWorkerApiKeyFilter> internalWorkerApiKeyFilterRegistration(
InternalWorkerApiKeyFilter internalWorkerApiKeyFilter
) {
FilterRegistrationBean<InternalWorkerApiKeyFilter> registration = new FilterRegistrationBean<>(internalWorkerApiKeyFilter);
registration.setEnabled(false);
return registration;
}

@Bean
public SecurityFilterChain securityFilterChain(
HttpSecurity http,
RequestContextLoggingFilter requestContextLoggingFilter,
JwtAuthenticationFilter jwtAuthenticationFilter,
PortOneWebhookRateLimitFilter portOneWebhookRateLimitFilter
PortOneWebhookRateLimitFilter portOneWebhookRateLimitFilter,
InternalWorkerApiKeyFilter internalWorkerApiKeyFilter
) throws Exception {

http.cors((cors) -> cors.configurationSource(corsConfigurationSource()));
Expand Down Expand Up @@ -128,6 +149,7 @@ public SecurityFilterChain securityFilterChain(
);

http.addFilterBefore(portOneWebhookRateLimitFilter, UsernamePasswordAuthenticationFilter.class);
http.addFilterBefore(internalWorkerApiKeyFilter, PortOneWebhookRateLimitFilter.class);
http.addFilterBefore(requestContextLoggingFilter, UsernamePasswordAuthenticationFilter.class);
http.addFilterAfter(jwtAuthenticationFilter, RequestContextLoggingFilter.class);

Expand Down
Loading
Loading