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 @@ -2,19 +2,28 @@

import com.techfork.domain.auth.dto.DeveloperTokenResponse;
import com.techfork.domain.auth.service.AuthService;
import com.techfork.domain.source.service.CrawlingService;
import com.techfork.global.common.code.SuccessCode;
import com.techfork.global.response.BaseResponse;
import com.techfork.global.security.oauth.UserPrincipal;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.batch.core.*;
import org.springframework.batch.core.launch.JobLauncher;
import org.springframework.batch.core.repository.JobExecutionAlreadyRunningException;
import org.springframework.batch.core.repository.JobInstanceAlreadyCompleteException;
import org.springframework.batch.core.repository.JobRestartException;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@Tag(name = "Admin", description = "관리자 API")
@Slf4j
@RestController
Expand All @@ -23,6 +32,9 @@
public class AdminController {

private final AuthService authService;
private final JobLauncher jobLauncher;
private final Job summaryAndEmbeddingJob;
private final CrawlingService crawlingService;

@Operation(
summary = "개발자 토큰 발급 (ADMIN 전용)",
Expand All @@ -35,4 +47,33 @@ public ResponseEntity<BaseResponse<DeveloperTokenResponse>> generateDeveloperTok
DeveloperTokenResponse response = authService.generateDeveloperToken(userPrincipal.getId());
return BaseResponse.of(SuccessCode.OK, response);
}

@Operation(
summary = "요약 추출 + 임베딩 생성 배치 실행 (ADMIN 전용)",
description = "요약이 없는 게시글의 요약을 추출하고, 임베딩을 생성하여 Elasticsearch에 색인합니다. (크롤링 제외)"
)
@PostMapping("/batch/summary-and-embedding")
public ResponseEntity<BaseResponse<Void>> runSummaryAndEmbeddingBatch() {
try {
JobParameters jobParameters = new JobParametersBuilder()
.addLong("timestamp", System.currentTimeMillis())
.toJobParameters();

jobLauncher.run(summaryAndEmbeddingJob, jobParameters);

return BaseResponse.of(SuccessCode.OK);

} catch (JobExecutionAlreadyRunningException | JobRestartException |
JobInstanceAlreadyCompleteException | JobParametersInvalidException e) {
log.error("배치 실행 실패", e);
throw new RuntimeException("배치 실행 중 오류 발생: " + e.getMessage(), e);
}
}

@Operation(summary = "RSS 크롤링 실행", description = "모든 테크 블로그의 RSS를 크롤링하여 DB에 저장합니다.")
@PostMapping("/batch/crawl-rss")
public ResponseEntity<BaseResponse<String>> crawlRss() {
crawlingService.executeCrawling();
return BaseResponse.of(SuccessCode.OK, "RSS 크롤링이 성공적으로 시작되었습니다.");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,14 @@ public Job rssCrawlingJob() {
.build();
}

@Bean
public Job summaryAndEmbeddingJob() {
return new JobBuilder("summaryAndEmbeddingJob", jobRepository)
.start(extractSummaryStep())
.next(embedAndIndexStep())
.build();
}

@Bean
public Step fetchAndSaveRssStep() {
return new StepBuilder("fetchAndSaveRssStep", jobRepository)
Expand Down

This file was deleted.

1 change: 0 additions & 1 deletion src/main/java/com/techfork/global/constant/Constants.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@ private Constants() {}
};

public static final String[] ADMIN_ENDPOINTS = {
"/api/v1/batch/**",
"/api/v1/admin/**"
};

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -203,7 +203,7 @@ void success_ValidTokenAccess() throws Exception {
@DisplayName("403 - 일반 사용자가 관리자 전용 엔드포인트 접근")
void forbidden_UserAccessAdminEndpoint() throws Exception {
// When & Then - 일반 사용자 토큰으로 관리자 엔드포인트 접근
mockMvc.perform(get("/api/v1/batch/run")
mockMvc.perform(get("/api/v1/admin/developer-token")
.header("Authorization", "Bearer " + validAccessToken))
.andDo(print())
.andExpect(status().isForbidden())
Expand Down Expand Up @@ -231,8 +231,7 @@ void forbidden_WithdrawnUserAccessAPI() throws Exception {
@DisplayName("200 - 관리자가 관리자 전용 엔드포인트 접근 (정상)")
void success_AdminAccessAdminEndpoint() throws Exception {
// When & Then - 관리자 토큰으로 접근 시 정상 처리
// 실제 배치 실행은 안 되고 404나 다른 에러가 날 수 있지만, 403이 아니어야 함
mockMvc.perform(get("/api/v1/batch/run")
mockMvc.perform(get("/api/v1/admin/batch/crawl-rss")
.header("Authorization", "Bearer " + adminAccessToken))
.andDo(print())
.andExpect(result -> {
Expand Down
Loading