package kr.re.etri.autoflow.controllers; import io.minio.MinioClient; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.media.Content; import io.swagger.v3.oas.annotations.responses.ApiResponse; import io.swagger.v3.oas.annotations.responses.ApiResponses; import kr.re.etri.autoflow.entity.StorageAttachmentEntity; import kr.re.etri.autoflow.entity.WorkflowEntity; import kr.re.etri.autoflow.payload.request.CreateRunRequest; import kr.re.etri.autoflow.service.StorageAttachmentService; import kr.re.etri.autoflow.service.PipelineUploadService; import kr.re.etri.autoflow.service.WorkFlowService; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.http.HttpStatus; import org.springframework.http.MediaType; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import org.springframework.web.multipart.MultipartFile; import java.net.URLDecoder; import java.nio.charset.StandardCharsets; import java.util.HashMap; import java.util.Map; @Slf4j @RestController @RequestMapping("/pipelines") @RequiredArgsConstructor @CrossOrigin(origins = "*") @io.swagger.v3.oas.annotations.tags.Tag(name = "Kubeflow Pipeline", description = "Kubeflow 파이프라인 API") public class PipelineUploadController { private final PipelineUploadService pipelineUploadService; private final WorkFlowService workFlowService; private final StorageAttachmentService minioAttachmentService; @PostMapping(value = "/upload", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) public ResponseEntity> uploadPipeline( @RequestParam("uploadfile") MultipartFile file, @RequestParam("name") String name, @RequestParam("display_name") String displayName, @RequestParam(value = "description", required = false) String description, @RequestParam(value = "namespace", required = false) String namespace, @RequestParam("regUserId") String regUserId, @RequestParam("projectId") Long projectId ) { try { // description 한글 디코딩 String decodedDescription = description != null ? URLDecoder.decode(description, StandardCharsets.UTF_8) : null; // 1. Kubeflow 업로드 Map result = pipelineUploadService.uploadPipeline( file, name, displayName, decodedDescription, namespace); WorkflowEntity workflow = WorkflowEntity.builder() .pipelineId((String) result.get("pipeline_id")) .displayName((String) result.get("display_name")) .name((String) result.get("name")) .description(decodedDescription) // 여기 디코딩 적용 .namespace((String) result.get("namespace")) .regUserId(regUserId) .projectId(projectId) .kubeflowStatus("Created") .version(1) .build(); workFlowService.save(workflow); // 2. MinIO 업로드 StorageAttachmentEntity attachment = minioAttachmentService.uploadFile( file, "workflows/" + projectId, workflow.getId(), "workflows", displayName, decodedDescription, // 디코딩 적용 1, regUserId, projectId ); String minioUrl = minioAttachmentService.getFileUrl(attachment.getStoragePath()); // 3. 최종 응답 Map response = new HashMap<>(); response.put("pipeline", result); response.put("workflow", workflow); response.put("attachment", attachment); response.put("minioUrl", minioUrl); return ResponseEntity.ok(response); } catch (Exception e) { log.error("Pipeline + MinIO upload failed", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Map.of( "status", 500, "error", "Internal Server Error", "message", e.getMessage() )); } } @PostMapping("/runs") @Operation( summary = "Kubeflow Run 생성", description = "Kubeflow에 Run을 생성합니다." ) @ApiResponses({ @ApiResponse(responseCode = "200", description = "Run 생성 성공"), @ApiResponse(responseCode = "400", description = "display_name 필수"), @ApiResponse(responseCode = "500", description = "서버 오류") }) public ResponseEntity> createRun( @RequestBody CreateRunRequest runRequest ) { try { Map result = pipelineUploadService.createRun(runRequest); return ResponseEntity.ok(result); } catch (IllegalArgumentException e) { log.error("Invalid run request", e); return ResponseEntity.status(HttpStatus.BAD_REQUEST) .body(Map.of( "status", 400, "error", "Bad Request", "message", e.getMessage() )); } catch (Exception e) { log.error("Run creation failed", e); return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR) .body(Map.of( "status", 500, "error", "Internal Server Error", "message", e.getMessage() )); } } }