diff --git a/.github/workflows/Prod.yml b/.github/workflows/Prod.yml index 6b97fbf..21eecb7 100644 --- a/.github/workflows/Prod.yml +++ b/.github/workflows/Prod.yml @@ -75,25 +75,6 @@ jobs: run: | docker push ${{ secrets.ECR_REGISTRY }}/prod:latest - deploy: - needs: build-and-push - runs-on: ubuntu-latest - - steps: - - name: Deploy on Staging Server - uses: appleboy/ssh-action@v0.1.10 - with: - host: ${{ secrets.AWS_STAGING }} - username: ${{ secrets.AWS_SSH_USERNAME }} - key: ${{ secrets.AWS_SSH_KEY }} - script_stop: true - script: | - cd /app - password=$(aws ecr get-login-password --region ${{ env.REGION }}) - docker login --username AWS --password $password ${{ secrets.ECR_REGISTRY }} - docker-compose pull - docker-compose up -d - # notification: # needs: deploy # runs-on: ubuntu-latest diff --git a/.gitignore b/.gitignore index 144afe6..9cbf0e7 100644 --- a/.gitignore +++ b/.gitignore @@ -36,4 +36,4 @@ out/ ### VS Code ### .vscode/ -http/* \ No newline at end of file +http/* diff --git a/src/main/java/com/kumofactory/cloud/appDeploy/AppDeployController.java b/src/main/java/com/kumofactory/cloud/appDeploy/AppDeployController.java index be3d9ac..bc4bec5 100644 --- a/src/main/java/com/kumofactory/cloud/appDeploy/AppDeployController.java +++ b/src/main/java/com/kumofactory/cloud/appDeploy/AppDeployController.java @@ -74,10 +74,18 @@ public Flux> getBuildStatus(@PathVariable("instanceId") return buildRequestService.getBuildStatus(instanceId); } - @PostMapping("/deployAsync/v2") - @AuthorizationFromToken - public ResponseEntity deployRequestAsyncV2(@RequestBody BuildRequestDto request, String userId) { - CompletableFuture.runAsync(() -> buildRequestService.RequestBuildAsync2(request, userId)); - return ResponseEntity.ok("Request Delivered"); - } + @PostMapping("/deployAsync/v2") + @AuthorizationFromToken + public ResponseEntity deployRequestAsyncV2(@RequestBody BuildRequestDto request, String userId) { + CompletableFuture.runAsync(() -> buildRequestService.RequestBuildAsync2(request, userId)); + return ResponseEntity.ok("Request Delivered"); + } + + @GetMapping("/info") + @AuthorizationFromToken + public ResponseEntity instanceInfo(@RequestParam("instanceId") String instanceId, String userId) { + + return ResponseEntity.ok(buildRequestService.getInstanceInfo(instanceId)); + } + } \ No newline at end of file diff --git a/src/main/java/com/kumofactory/cloud/appDeploy/domain/BuildLog.java b/src/main/java/com/kumofactory/cloud/appDeploy/domain/BuildLog.java index e5d32b7..b944e54 100644 --- a/src/main/java/com/kumofactory/cloud/appDeploy/domain/BuildLog.java +++ b/src/main/java/com/kumofactory/cloud/appDeploy/domain/BuildLog.java @@ -17,4 +17,4 @@ public class BuildLog { private String repository; private String branch; private Object logs; -} \ No newline at end of file +} diff --git a/src/main/java/com/kumofactory/cloud/appDeploy/dto/GitHubRepoDto.java b/src/main/java/com/kumofactory/cloud/appDeploy/dto/GitHubRepoDto.java index 8d9b869..446710e 100644 --- a/src/main/java/com/kumofactory/cloud/appDeploy/dto/GitHubRepoDto.java +++ b/src/main/java/com/kumofactory/cloud/appDeploy/dto/GitHubRepoDto.java @@ -9,9 +9,18 @@ public record RepoInfoDto( @JsonProperty("name") String name, @JsonProperty("fullName") String fullName, @JsonProperty("private") Boolean isPrivate, - @JsonProperty("fork") Boolean isFork - ) { - } + @JsonProperty("fork") Boolean isFork, + @JsonProperty("forksCount") Integer forksCount, + @JsonProperty("description") String description, + @JsonProperty("language") String language, + @JsonProperty("stargazersUrl") String stargazersUrl, + @JsonProperty("starCount") Integer starCount, + @JsonProperty("openIssuesCount") Integer openIssuesCount, + @JsonProperty("visibility") String visibility, + @JsonProperty("updatedAt") String updatedAt, + @JsonProperty("createdAt") String createdAt + + ) { } public record UserDto( // @JsonProperty("personal_repo_count") Integer personalRepoCount, diff --git a/src/main/java/com/kumofactory/cloud/appDeploy/service/BuildRequestService.java b/src/main/java/com/kumofactory/cloud/appDeploy/service/BuildRequestService.java index 9908206..24b80bb 100644 --- a/src/main/java/com/kumofactory/cloud/appDeploy/service/BuildRequestService.java +++ b/src/main/java/com/kumofactory/cloud/appDeploy/service/BuildRequestService.java @@ -13,7 +13,7 @@ public interface BuildRequestService { Flux> RequestBuildAsync(BuildRequestDto request, String oauthId); - void RequestBuildAsync2(BuildRequestDto request, String oauthId); + Flux> getBuildStatus(String instanceId); + String getInstanceInfo(String instanceId); - Flux> getBuildStatus(String instanceId); } diff --git a/src/main/java/com/kumofactory/cloud/appDeploy/service/BuildRequestServiceImpl.java b/src/main/java/com/kumofactory/cloud/appDeploy/service/BuildRequestServiceImpl.java index dc1cbed..d00cf21 100644 --- a/src/main/java/com/kumofactory/cloud/appDeploy/service/BuildRequestServiceImpl.java +++ b/src/main/java/com/kumofactory/cloud/appDeploy/service/BuildRequestServiceImpl.java @@ -32,192 +32,201 @@ @Repository @RequiredArgsConstructor public class BuildRequestServiceImpl implements BuildRequestService { - private final MemberRepository memberRepository; - private final CfnOutputRepository cfnOutputRepository; - private final BuildLogRepository buildLogRepository; - private final Logger logger = LoggerFactory.getLogger(BuildRequestServiceImpl.class); - private String token; - private String baseUri = "https://api.github.com"; - @Value("${build.server}") - private String buildServerUri; - - @Override - public void RequestBuild(BuildRequestDto request, String oauthId) { - Member member = memberRepository.findMemberByOauthId(oauthId); - this.token = member.getGithubAccessToken(); - String url = buildServerUri + "/api/v1/deploy"; - request.setDockerfile(isDockerfileExist(request.user(), request.repo())); - request.setgithubToken(token); - - HttpHeaders headers = new HttpHeaders(); - headers.set("Accept", "application/json"); - HttpEntity httpEntity = new HttpEntity<>(request, headers); - ResponseEntity response = new RestTemplate().exchange(url, HttpMethod.POST, httpEntity, String.class); - } - - @Override - public CfnOutput getMyResources(String blueprintUuid, String oauthId) { - logger.info("blueprintUuid: " + blueprintUuid); - CfnOutput output = cfnOutputRepository.findByKey(blueprintUuid); - return output; - } - - private Boolean isDockerfileExist(String userName, String repoName) { - String url = baseUri + "/repos/" + userName + "/" + repoName + "/contents/Dockerfile"; - try { - ResponseEntity response = RequestGitHubAPIs(url); - return true; - } catch (Exception e) { - return false; - } - } - - private ResponseEntity RequestGitHubAPIs(String uri) { - HttpHeaders headers = new HttpHeaders(); - headers.set("Accept", "application/json"); - if (StringUtils.hasText(token)) { - headers.setBearerAuth(token); - } - HttpEntity httpEntity = new HttpEntity<>(headers); - return new RestTemplate().exchange(uri, HttpMethod.GET, httpEntity, JsonNode.class); - } - - @Override - public Flux> RequestBuildAsync(BuildRequestDto request, String oauthId) { - logger.info("RequestBuildAsync"); - - WebClient webClient = WebClient.create(); - Member member = memberRepository.findMemberByOauthId(oauthId); - this.token = member.getGithubAccessToken(); - String url = buildServerUri + "/api/v1/deployAsync"; - request.setDockerfile(isDockerfileExist(request.user(), request.repo())); - request.setgithubToken(token); - - BuildLog buildLog = new BuildLog(); - buildLog.set_id(request.instanceId()); - buildLog.setStatus(0); - buildLog.setRepository(request.user() + '/' + request.repo()); - buildLog.setBranch(request.branch()); - buildLogRepository.save(buildLog); - - Flux sseFlux = webClient.post() - .uri(url) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.TEXT_EVENT_STREAM) - .bodyValue(request) - .retrieve() - .bodyToFlux(SseResponseDto.class); - - return sseFlux.map(response -> { - logger.info("Received SSE: " + response.event() + response.data()); - if (response.event().equals("fail")) { - logger.info("#@#@#@#@#@ fail"); - buildLog.setStatus(-1); - buildLogRepository.save(buildLog); - } else if (response.event().equals("success")) { - logger.info("#@#@#@#@#@ success"); - buildLog.setStatus(1); - buildLogRepository.save(buildLog); - } - return ServerSentEvent.builder() - .event(response.event()) - .data(response.data()) - .build(); - }) - .takeUntil(response -> response.event().equals("fail") || response.event().equals("success")) - .doOnError(error -> { - error.printStackTrace(); - }) - .doOnTerminate(() -> { - logger.info("SSE stream completed"); - }); - } - - @Override - public Flux> getBuildStatus(String instanceId) { - Optional buildLogOpt = buildLogRepository.findByInstanceId(instanceId); - // buildlog가 null이면 event: status, data:null 로 보내줌 - if (!buildLogOpt.isPresent()) { - return Flux.just(createSSE("status", "null")); - } - - BuildLog buildLog = buildLogOpt.get(); - - if (buildLog.getStatus() == -1) { - return Flux.just(createSSE("status", "fail")); - } else if (buildLog.getStatus() == 0 || buildLog.getStatus() == 1) { - return Flux.interval(Duration.ofSeconds(3)) - .map(tick -> { - BuildLog updatedLog = buildLogRepository.findByInstanceId(instanceId).orElse(null); - if (updatedLog == null) { - return createSSE("status", "null"); - } else if (updatedLog.getStatus() == 2) { - return createSSE("status", "success"); - } else if (updatedLog.getStatus() == 0) { - return createSSE("status", "building"); - } else if (updatedLog.getStatus() == 1) { - return createSSE("status", "deploying"); - } else { - return createSSE("status", "unknown"); - } - }) - .takeUntil(sse -> "success".equals(sse.data())); - } else if (buildLog.getStatus() == 2) { - return Flux.just(createSSE("status", "success")); - } else { - return Flux.empty(); - } - } - - - @Override - public void RequestBuildAsync2(BuildRequestDto request, String oauthId) { - logger.info("RequestBuildAsync"); - - WebClient webClient = WebClient.create(buildServerUri); - String endpoint = "/api/v1/deployAsync"; - - Member member = memberRepository.findMemberByOauthId(oauthId); - this.token = member.getGithubAccessToken(); - - request.setDockerfile(isDockerfileExist(request.user(), request.repo())); - request.setgithubToken(token); - - saveBuildLog(request, 0); - - Flux> events = webClient.post() - .uri(endpoint) - .contentType(MediaType.APPLICATION_JSON) - .accept(MediaType.TEXT_EVENT_STREAM) - .bodyValue(request) - .retrieve() - .bodyToFlux(new ParameterizedTypeReference>() { - }); - - events.takeWhile(event -> !event.event().equals("finish")) - .subscribe(event -> { - logger.info("Received SSE: " + event.event() + " " + event.data()); - if ("fail".equals(event.event()) || "error".equals(event.event())) { - saveBuildLog(request, -1); - } else if ("success".equals(event.event())) { - saveBuildLog(request, 1); - } - }); - } - - private void saveBuildLog(BuildRequestDto request, int status) { - BuildLog buildLog = new BuildLog(); - buildLog.set_id(request.instanceId()); - buildLog.setStatus(status); - buildLog.setRepository(request.user() + '/' + request.repo()); - buildLog.setBranch(request.branch()); - buildLogRepository.save(buildLog); - } - - private ServerSentEvent createSSE(String event, String data) { - return ServerSentEvent.builder() - .event(event) - .data(data) - .build(); - } + private final MemberRepository memberRepository; + private final CfnOutputRepository cfnOutputRepository; + private final BuildLogRepository buildLogRepository; + private final Logger logger = LoggerFactory.getLogger(BuildRequestServiceImpl.class); + private String token; + private String baseUri = "https://api.github.com"; + @Value("${build.server}") + private String buildServerUri; + + @Override + public void RequestBuild(BuildRequestDto request, String oauthId) { + Member member = memberRepository.findMemberByOauthId(oauthId); + this.token = member.getGithubAccessToken(); + String url = buildServerUri + "/api/v1/deploy"; + request.setDockerfile(isDockerfileExist(request.user(), request.repo())); + request.setgithubToken(token); + + HttpHeaders headers = new HttpHeaders(); + headers.set("Accept", "application/json"); + HttpEntity httpEntity = new HttpEntity<>(request, headers); + ResponseEntity response = new RestTemplate().exchange(url, HttpMethod.POST, httpEntity, String.class); + } + + @Override + public CfnOutput getMyResources(String blueprintUuid, String oauthId) { + logger.info("blueprintUuid: " + blueprintUuid); + CfnOutput output = cfnOutputRepository.findByKey(blueprintUuid); + return output; + } + + private Boolean isDockerfileExist(String userName, String repoName) { + String url = baseUri + "/repos/" + userName + "/" + repoName + "/contents/Dockerfile"; + try { + ResponseEntity response = RequestGitHubAPIs(url); + return true; + } catch (Exception e) { + return false; + } + } + + private ResponseEntity RequestGitHubAPIs(String uri) { + HttpHeaders headers = new HttpHeaders(); + headers.set("Accept", "application/json"); + if (StringUtils.hasText(token)) { + headers.setBearerAuth(token); + } + HttpEntity httpEntity = new HttpEntity<>(headers); + return new RestTemplate().exchange(uri, HttpMethod.GET, httpEntity, JsonNode.class); + } + + @Override + public Flux> RequestBuildAsync(BuildRequestDto request, String oauthId) { + logger.info("RequestBuildAsync"); + + WebClient webClient = WebClient.create(); + Member member = memberRepository.findMemberByOauthId(oauthId); + this.token = member.getGithubAccessToken(); + String url = buildServerUri + "/api/v1/deployAsync"; + request.setDockerfile(isDockerfileExist(request.user(), request.repo())); + request.setgithubToken(token); + + BuildLog buildLog = new BuildLog(); + buildLog.set_id(request.instanceId()); + buildLog.setStatus(0); + buildLog.setRepository(request.user()+'/'+request.repo()); + buildLog.setBranch(request.branch()); + buildLogRepository.save(buildLog); + + Flux sseFlux = webClient.post() + .uri(url) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.TEXT_EVENT_STREAM) + .bodyValue(request) + .retrieve() + .bodyToFlux(SseResponseDto.class); + + return sseFlux.map(response -> { + logger.info("Received SSE: " + response.event() + response.data()); + if(response.event().equals("fail")) { + logger.info("#@#@#@#@#@ fail"); + buildLog.setStatus(-1); + buildLogRepository.save(buildLog); + } else if(response.event().equals("success")) { + logger.info("#@#@#@#@#@ success"); + buildLog.setStatus(1); + buildLogRepository.save(buildLog); + } + return ServerSentEvent.builder() + .event(response.event()) + .data(response.data()) + .build(); + }) + .takeUntil(response -> response.event().equals("fail") || response.event().equals("success")) + .doOnError(error -> { + error.printStackTrace(); + }) + .doOnTerminate(() -> { + logger.info("SSE stream completed"); + }); + } + + @Override + public Flux> getBuildStatus(String instanceId) { + Optional buildLogOpt = buildLogRepository.findByInstanceId(instanceId); + // buildlog가 null이면 event: status, data:null 로 보내줌 + if (!buildLogOpt.isPresent()) { + return Flux.just(createSSE("status", "null")); + } + + BuildLog buildLog = buildLogOpt.get(); + + if (buildLog.getStatus() == -1) { + return Flux.just(createSSE("status", "fail")); + } else if (buildLog.getStatus() == 0 || buildLog.getStatus() == 1) { + return Flux.interval(Duration.ofSeconds(3)) + .map(tick -> { + BuildLog updatedLog = buildLogRepository.findByInstanceId(instanceId).orElse(null); + if (updatedLog == null) { + return createSSE("status", "null"); + } else if (updatedLog.getStatus() == 2) { + return createSSE("status", "success"); + } else if (updatedLog.getStatus() == 0) { + return createSSE("status", "building"); + } else if (updatedLog.getStatus() == 1) { + return createSSE("status", "deploying"); + } else { + return createSSE("status", "unknown"); + } + }) + .takeUntil(sse -> "success".equals(sse.data())); + } else if (buildLog.getStatus() == 2) { + return Flux.just(createSSE("status", "success")); + } else { + return Flux.empty(); + } + } + + + @Override + public void RequestBuildAsync2(BuildRequestDto request, String oauthId) { + logger.info("RequestBuildAsync"); + + WebClient webClient = WebClient.create(buildServerUri); + String endpoint = "/api/v1/deployAsync"; + + Member member = memberRepository.findMemberByOauthId(oauthId); + this.token = member.getGithubAccessToken(); + + request.setDockerfile(isDockerfileExist(request.user(), request.repo())); + request.setgithubToken(token); + + saveBuildLog(request, 0); + + Flux> events = webClient.post() + .uri(endpoint) + .contentType(MediaType.APPLICATION_JSON) + .accept(MediaType.TEXT_EVENT_STREAM) + .bodyValue(request) + .retrieve() + .bodyToFlux(new ParameterizedTypeReference>() {}); + + events.takeWhile(event -> !event.event().equals("finish")) + .subscribe(event -> { + logger.info("Received SSE: " + event.event() + " " + event.data()); + if ("fail".equals(event.event()) || "error".equals(event.event())) { + saveBuildLog(request, -1); + } else if ("success".equals(event.event())) { + saveBuildLog(request, 1); + } + }); + } + + private void saveBuildLog(BuildRequestDto request, int status) { + BuildLog buildLog = new BuildLog(); + buildLog.set_id(request.instanceId()); + buildLog.setStatus(status); + buildLog.setRepository(request.user()+'/'+request.repo()); + buildLog.setBranch(request.branch()); + buildLogRepository.save(buildLog); + } + + private ServerSentEvent createSSE(String event, String data) { + return ServerSentEvent.builder() + .event(event) + .data(data) + .build(); + } + + @Override + public String getInstanceInfo(String instanceId) { + Optional buildLogOpt = buildLogRepository.findByInstanceId(instanceId); + if (!buildLogOpt.isPresent()) { + return null; + } + BuildLog buildLog = buildLogOpt.get(); + return buildLog.getRepository() + " " + buildLog.getBranch(); + } } diff --git a/src/main/java/com/kumofactory/cloud/appDeploy/service/UserRepoServiceImpl.java b/src/main/java/com/kumofactory/cloud/appDeploy/service/UserRepoServiceImpl.java index 956c84a..07a0f59 100644 --- a/src/main/java/com/kumofactory/cloud/appDeploy/service/UserRepoServiceImpl.java +++ b/src/main/java/com/kumofactory/cloud/appDeploy/service/UserRepoServiceImpl.java @@ -44,8 +44,20 @@ public List RequestOrgRepoInfo(String org, String oau String fullName = node.get("full_name").asText(); Boolean isPrivate = node.get("private").asBoolean(); Boolean isFork = node.get("fork").asBoolean(); + Integer forksCount = node.get("forks_count").asInt(); + String description = node.get("description").asText(); + String language = node.get("language").asText(); + String stargazers_url = node.get("stargazers_url").asText(); + Integer starCount = node.get("stargazers_count").asInt(); + Integer open_issues_count = node.get("open_issues_count").asInt(); + String visibility = node.get("visibility").asText(); + String updatedAt = node.get("updated_at").asText(); + String createdAt = node.get("created_at").asText(); - GitHubRepoDto.RepoInfoDto repoInfo = new GitHubRepoDto.RepoInfoDto(name, fullName, isPrivate, isFork); + + + GitHubRepoDto.RepoInfoDto repoInfo = + new GitHubRepoDto.RepoInfoDto(name, fullName, isPrivate, isFork, forksCount, description, language, stargazers_url, starCount , open_issues_count, visibility, updatedAt, createdAt); repoInfoList.add(repoInfo); } return repoInfoList; @@ -76,13 +88,25 @@ private List listUserRepos(String user, String token) List repoInfoList = new ArrayList<>(); + for (JsonNode node : responseBody.get("items")) { String name = node.get("name").asText(); String fullName = node.get("full_name").asText(); Boolean isPrivate = node.get("private").asBoolean(); Boolean isFork = node.get("fork").asBoolean(); - - GitHubRepoDto.RepoInfoDto repoInfo = new GitHubRepoDto.RepoInfoDto(name, fullName, isPrivate, isFork); + Integer forksCount = node.get("forks_count").asInt(); + String description = node.get("description").asText(); + String language = node.get("language").asText(); + String stargazers_url = node.get("stargazers_url").asText(); + Integer starCount = node.get("stargazers_count").asInt(); + Integer open_issues_count = node.get("open_issues_count").asInt(); + String visibility = node.get("visibility").asText(); + String updatedAt = node.get("updated_at").asText(); + String createdAt = node.get("created_at").asText(); + + + GitHubRepoDto.RepoInfoDto repoInfo = + new GitHubRepoDto.RepoInfoDto(name, fullName, isPrivate, isFork, forksCount, description, language, stargazers_url, starCount ,open_issues_count, visibility, updatedAt, createdAt); repoInfoList.add(repoInfo); } return repoInfoList; diff --git a/src/main/java/com/kumofactory/cloud/blueprint/BlueprintController.java b/src/main/java/com/kumofactory/cloud/blueprint/BlueprintController.java index ab1858e..827fcc9 100644 --- a/src/main/java/com/kumofactory/cloud/blueprint/BlueprintController.java +++ b/src/main/java/com/kumofactory/cloud/blueprint/BlueprintController.java @@ -25,6 +25,7 @@ import javax.xml.transform.Result; import java.util.ArrayList; import java.util.List; +import java.util.Optional; @Tag(name = "AwsBlueprintService", description = "AwsBlueprintService") @RestController @@ -107,4 +108,10 @@ public ResultDto createAwsBlueprint(@RequestBody AwsBluePrintDto awsBluePrintDto .build(); } } + + @GetMapping("/cost/{uuid}") + @AuthorizationFromToken + public Object getCost(@PathVariable("uuid") String uuid, String userId) { + return awsBlueprintService.getInfraCost(uuid, userId); + } } diff --git a/src/main/java/com/kumofactory/cloud/blueprint/TemplateController.java b/src/main/java/com/kumofactory/cloud/blueprint/TemplateController.java index 47f1038..dc2feb0 100644 --- a/src/main/java/com/kumofactory/cloud/blueprint/TemplateController.java +++ b/src/main/java/com/kumofactory/cloud/blueprint/TemplateController.java @@ -20,8 +20,13 @@ import org.springframework.data.domain.Pageable; import org.springframework.web.bind.annotation.*; + +import java.io.IOException; + import java.util.List; +import static java.lang.Boolean.parseBoolean; + @Tag(name = "TemplateController", description = "TemplateController") @RestController @RequiredArgsConstructor @@ -31,7 +36,6 @@ public class TemplateController { private final AwsTemplateService templateService; private final AwsBlueprintService awsBlueprintService; private final Logger logger = LoggerFactory.getLogger(TemplateController.class); - private final MessageProducer sender; @Operation( summary = "Template 전체 조회하기", @@ -43,6 +47,18 @@ public List getAll(PagingDto page) { return templateService.getAll(pageable); } + @Operation( + summary = "template 으로 blueprint 생성하기(static file)", + description = "Requires authentication.", + security = @SecurityRequirement(name = "bearerAuth") + ) + @PostMapping("") + @AuthorizationFromToken + public ResultDto createTemplate(@RequestBody AwsBluePrintDto awsBluePrintDto, @RequestParam String name, @RequestParam String provision, String userId) throws IOException { + templateService.deployTemplate(awsBluePrintDto, name, parseBoolean(provision), userId); + return ResultDto.builder().result(true).build(); + } + @Operation( summary = "Kumofactory 에서 만든 Template 조회하기", description = "example : ?page=0&size=10" diff --git a/src/main/java/com/kumofactory/cloud/blueprint/domain/BaseBluePrint.java b/src/main/java/com/kumofactory/cloud/blueprint/domain/BaseBluePrint.java index a2bc484..b208719 100644 --- a/src/main/java/com/kumofactory/cloud/blueprint/domain/BaseBluePrint.java +++ b/src/main/java/com/kumofactory/cloud/blueprint/domain/BaseBluePrint.java @@ -1,9 +1,7 @@ package com.kumofactory.cloud.blueprint.domain; import com.kumofactory.cloud.member.domain.Member; -import lombok.Getter; -import lombok.NoArgsConstructor; -import lombok.Setter; +import lombok.*; import org.hibernate.annotations.CreationTimestamp; import org.hibernate.annotations.UpdateTimestamp; @@ -14,6 +12,8 @@ @Setter @NoArgsConstructor @Entity +@Builder +@AllArgsConstructor public class BaseBluePrint { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -29,6 +29,7 @@ public class BaseBluePrint { private String name; // 블루프린트 이름 + @Lob private String description; // 블루프린트 설명 private String keyName; // 썸네일 이미지 파일명 (S3) @@ -42,6 +43,8 @@ public class BaseBluePrint { @Enumerated(EnumType.STRING) private BluePrintScope scope; // 블루프린트 공개 범위 + private Boolean isTemplate; + @ManyToOne private Member member; diff --git a/src/main/java/com/kumofactory/cloud/blueprint/domain/InfraCost.java b/src/main/java/com/kumofactory/cloud/blueprint/domain/InfraCost.java new file mode 100644 index 0000000..99c3518 --- /dev/null +++ b/src/main/java/com/kumofactory/cloud/blueprint/domain/InfraCost.java @@ -0,0 +1,17 @@ +package com.kumofactory.cloud.blueprint.domain; + +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import org.springframework.data.annotation.Id; +import org.springframework.data.mongodb.core.mapping.Document; + +@Document("infracosts") +@Getter +@Setter +@NoArgsConstructor +public class InfraCost { + @Id + private String _id; // instanceId + private Object result; +} \ No newline at end of file diff --git a/src/main/java/com/kumofactory/cloud/blueprint/domain/aws/AwsBluePrint.java b/src/main/java/com/kumofactory/cloud/blueprint/domain/aws/AwsBluePrint.java index d9be224..41fb8a2 100644 --- a/src/main/java/com/kumofactory/cloud/blueprint/domain/aws/AwsBluePrint.java +++ b/src/main/java/com/kumofactory/cloud/blueprint/domain/aws/AwsBluePrint.java @@ -17,6 +17,9 @@ @Setter @NoArgsConstructor public class AwsBluePrint extends BaseBluePrint { + @Column(nullable = false) + private String templateName = "undefined"; + @OneToMany(mappedBy = "bluePrint", cascade = CascadeType.ALL, fetch = FetchType.LAZY) private List areas; diff --git a/src/main/java/com/kumofactory/cloud/blueprint/dto/aws/AwsBluePrintDto.java b/src/main/java/com/kumofactory/cloud/blueprint/dto/aws/AwsBluePrintDto.java index 95c3ec4..c01de02 100644 --- a/src/main/java/com/kumofactory/cloud/blueprint/dto/aws/AwsBluePrintDto.java +++ b/src/main/java/com/kumofactory/cloud/blueprint/dto/aws/AwsBluePrintDto.java @@ -8,15 +8,21 @@ import com.kumofactory.cloud.blueprint.domain.aws.AwsComponent; import com.kumofactory.cloud.blueprint.dto.ComponentLineDto; import io.swagger.v3.oas.annotations.media.Schema; +import lombok.AllArgsConstructor; import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; + + import java.util.ArrayList; import java.util.List; @Data @Builder +@NoArgsConstructor +@AllArgsConstructor @Schema(name = "AwsBluePrintDto", description = "AwsBluePrintDto") public class AwsBluePrintDto { @@ -30,6 +36,8 @@ public class AwsBluePrintDto { private List components; private List links; private String svgFile; + private Boolean isTemplate; + private String templateName; public static AwsBluePrintDto build(AwsBluePrint blueprint, List areas, List components, List links) { return AwsBluePrintDto.builder() @@ -42,6 +50,8 @@ public static AwsBluePrintDto build(AwsBluePrint blueprint, List areas, .downloadCount(blueprint.getScope() == BluePrintScope.PUBLIC ? blueprint.getDownloadCount() : -1) .components(awsComponentDtosMapper(components)) .links(componentLinkDtoListMapper(links)) + .isTemplate(blueprint.getIsTemplate() != null && blueprint.getIsTemplate()) + .templateName(blueprint.getTemplateName() == null ? "undefined" : blueprint.getTemplateName()) .build(); } diff --git a/src/main/java/com/kumofactory/cloud/blueprint/dto/aws/AwsBluePrintListDto.java b/src/main/java/com/kumofactory/cloud/blueprint/dto/aws/AwsBluePrintListDto.java index 905f9ad..18e23ad 100644 --- a/src/main/java/com/kumofactory/cloud/blueprint/dto/aws/AwsBluePrintListDto.java +++ b/src/main/java/com/kumofactory/cloud/blueprint/dto/aws/AwsBluePrintListDto.java @@ -26,6 +26,8 @@ public class AwsBluePrintListDto { private Date updatedAt; private String presignedUrl; private ProvisionStatus status; + private Boolean isTemplate; + private String templateName; public static AwsBluePrintListDto fromAwsBluePrint(AwsBluePrint awsBluePrint, String presignedUrl) { return AwsBluePrintListDto.builder() @@ -40,6 +42,8 @@ public static AwsBluePrintListDto fromAwsBluePrint(AwsBluePrint awsBluePrint, St .updatedAt(awsBluePrint.getUpdated_at()) .presignedUrl(presignedUrl) .status(awsBluePrint.getStatus()) + .isTemplate(awsBluePrint.getIsTemplate() != null && awsBluePrint.getIsTemplate()) + .templateName(awsBluePrint.getTemplateName() == null ? "undefined" : awsBluePrint.getTemplateName()) .build(); } } diff --git a/src/main/java/com/kumofactory/cloud/blueprint/repository/InfraCostRepository.java b/src/main/java/com/kumofactory/cloud/blueprint/repository/InfraCostRepository.java new file mode 100644 index 0000000..404e4a3 --- /dev/null +++ b/src/main/java/com/kumofactory/cloud/blueprint/repository/InfraCostRepository.java @@ -0,0 +1,13 @@ +package com.kumofactory.cloud.blueprint.repository; + +import com.kumofactory.cloud.blueprint.domain.InfraCost; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; + +import java.util.Optional; + +public interface InfraCostRepository extends MongoRepository { + @Query("{_id:'?0'}") + Optional findByUuid(String uuid); + +} \ No newline at end of file diff --git a/src/main/java/com/kumofactory/cloud/blueprint/service/AwsBlueprintService.java b/src/main/java/com/kumofactory/cloud/blueprint/service/AwsBlueprintService.java index e99d6db..6b97948 100644 --- a/src/main/java/com/kumofactory/cloud/blueprint/service/AwsBlueprintService.java +++ b/src/main/java/com/kumofactory/cloud/blueprint/service/AwsBlueprintService.java @@ -9,7 +9,9 @@ import com.kumofactory.cloud.blueprint.dto.aws.AwsBluePrintListDto; import com.kumofactory.cloud.global.rabbitmq.domain.CdkMessagePattern; +import java.io.IOException; import java.util.List; +import java.util.Optional; public interface AwsBlueprintService { @@ -17,11 +19,13 @@ public interface AwsBlueprintService { List getMyAwsBlueprints(String userId); - void store(AwsBluePrintDto awsBluePrintDto, String provision, CdkMessagePattern pattern, String userId) throws JsonProcessingException; + void store(AwsBluePrintDto awsBluePrintDto, String provision, CdkMessagePattern pattern, String userId) throws IOException; boolean delete(String uuid); // blueprint uuid 로 모든 정보 삭세 boolean updateBluePrintScope(BluePrintScope scope, String uuid, String userId); ProvisionStatus getProvisionStatus(String uuid, String userId); + + Object getInfraCost(String uuid, String userId); } diff --git a/src/main/java/com/kumofactory/cloud/blueprint/service/AwsBlueprintServiceImpl.java b/src/main/java/com/kumofactory/cloud/blueprint/service/AwsBlueprintServiceImpl.java index d9a5139..3075ecf 100644 --- a/src/main/java/com/kumofactory/cloud/blueprint/service/AwsBlueprintServiceImpl.java +++ b/src/main/java/com/kumofactory/cloud/blueprint/service/AwsBlueprintServiceImpl.java @@ -10,8 +10,8 @@ import com.kumofactory.cloud.blueprint.dto.ComponentLineDto; import com.kumofactory.cloud.blueprint.dto.aws.*; -import com.kumofactory.cloud.blueprint.repository.ComponentDotRepository; import com.kumofactory.cloud.blueprint.repository.ComponentLineRepository; +import com.kumofactory.cloud.blueprint.repository.InfraCostRepository; import com.kumofactory.cloud.blueprint.repository.aws.AwsAreaRepository; import com.kumofactory.cloud.blueprint.repository.aws.AwsBluePrintRepository; import com.kumofactory.cloud.blueprint.repository.aws.AwsComponentRepository; @@ -20,19 +20,15 @@ import com.kumofactory.cloud.member.MemberRepository; import com.kumofactory.cloud.member.domain.Member; import com.kumofactory.cloud.util.aws.s3.AwsS3Helper; -import io.swagger.v3.oas.annotations.tags.Tag; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.slf4j.Logger; import org.slf4j.LoggerFactory; -import org.springframework.beans.factory.annotation.Autowired; -import org.springframework.mock.web.MockMultipartFile; import org.springframework.stereotype.Repository; import org.springframework.stereotype.Service; -import org.springframework.web.multipart.MultipartFile; +import java.io.IOException; import java.util.ArrayList; -import java.util.Base64; import java.util.List; import static java.lang.Boolean.parseBoolean; @@ -48,6 +44,7 @@ public class AwsBlueprintServiceImpl implements AwsBlueprintService { private final AwsComponentRepository awsComponentRepository; private final ComponentLineRepository componentLineRepository; private final AwsAreaRepository awsAreaRepository; + private final InfraCostRepository infraCostRepository; private final MessageProducer sender; private final AwsS3Helper awsS3Helper; private final Logger logger = LoggerFactory.getLogger(AwsBlueprintServiceImpl.class); @@ -86,7 +83,7 @@ public List getMyAwsBlueprints(String oauthId) { } @Override - public void store(AwsBluePrintDto awsBluePrintDto, String provision, CdkMessagePattern pattern, String userId) throws JsonProcessingException { + public void store(AwsBluePrintDto awsBluePrintDto, String provision, CdkMessagePattern pattern, String userId) throws IOException { this.delete(awsBluePrintDto.getUuid()); // 기존 BluePrint 삭제 AwsBluePrint savedBlueprint = saveBlueprint(awsBluePrintDto, provision, userId); // BluePrint 저장 @@ -109,7 +106,11 @@ public void store(AwsBluePrintDto awsBluePrintDto, String provision, CdkMessageP awsComponentRepository.saveAll(components); if (parseBoolean(provision)) { + logger.info("send message to cdk server: {}", pattern); sender.sendAwsCdkOption(pattern, awsCdkDtos); + }else { + logger.info("send message to cdk server: {}", CdkMessagePattern.COST); + sender.sendAwsCdkOption(CdkMessagePattern.COST, awsCdkDtos); } } @@ -144,7 +145,9 @@ public ProvisionStatus getProvisionStatus(String uuid, String userId) { } // Blueprint 저장 - private AwsBluePrint saveBlueprint(AwsBluePrintDto awsBluePrintDto, String provision, String userId) { + private AwsBluePrint saveBlueprint(AwsBluePrintDto awsBluePrintDto, String provision, String userId) throws IOException { + logger.info("awsBluePrintDto {}", awsBluePrintDto.toString()); + Member member = memberRepository.findMemberByOauthId(userId); // Provision 여부 설정 ProvisionStatus status; @@ -155,7 +158,7 @@ private AwsBluePrint saveBlueprint(AwsBluePrintDto awsBluePrintDto, String provi } // thumbnail 저장 - String keyname = saveThumbnail(awsBluePrintDto, member); + String keyname = awsS3Helper.saveThumbnail(awsBluePrintDto, member); // BluePrint 저장 AwsBluePrint awsBluePrint = new AwsBluePrint(); @@ -167,27 +170,10 @@ private AwsBluePrint saveBlueprint(AwsBluePrintDto awsBluePrintDto, String provi awsBluePrint.setMember(member); awsBluePrint.setScope(awsBluePrintDto.getScope() == null ? BluePrintScope.PRIVATE : awsBluePrintDto.getScope()); awsBluePrint.setKeyName(keyname); - + awsBluePrint.setIsTemplate(false); return awsBluePrintRepository.save(awsBluePrint); } - // thumbnail 저장 - private String saveThumbnail(AwsBluePrintDto bluePrint, Member member) { - String objectKey = _getObjectKey(member.getOauthId(), bluePrint.getUuid()); - logger.info("thumbnail upload start: {}", objectKey); - try { - byte[] svgContent = Base64.getDecoder().decode(bluePrint.getSvgFile().split(",")[1]); - MultipartFile svgFile = new MockMultipartFile("file", objectKey, "image/svg+xml", svgContent); - awsS3Helper.putS3Object(svgFile, objectKey); - logger.info("thumbnail upload success: {}", objectKey); - logger.info("thumbnail url: {}", awsS3Helper.getPresignedUrl(objectKey)); - } catch (Exception e) { - logger.error("thumbnail upload failed: {}", e.getMessage()); - } - - return objectKey; - } - // ComponentLine 저장 private void saveComponentLines(AwsBluePrint blueprint, List lines) { List componentLines = new ArrayList<>(); @@ -207,7 +193,10 @@ private void saveAwsAreas(AwsBluePrint blueprint, List areas) { awsAreaRepository.saveAll(awsArea); } - private String _getObjectKey(String memberId, String blueprintId) { - return memberId + "/" + blueprintId + ".svg"; + @Override + public Object getInfraCost(String uuid, String userId) { + return infraCostRepository.findByUuid(uuid); } + + } diff --git a/src/main/java/com/kumofactory/cloud/blueprint/service/AwsTemplateService.java b/src/main/java/com/kumofactory/cloud/blueprint/service/AwsTemplateService.java index de81e74..1a120dc 100644 --- a/src/main/java/com/kumofactory/cloud/blueprint/service/AwsTemplateService.java +++ b/src/main/java/com/kumofactory/cloud/blueprint/service/AwsTemplateService.java @@ -2,14 +2,20 @@ import com.kumofactory.cloud.blueprint.dto.aws.AwsBluePrintDto; import com.kumofactory.cloud.blueprint.dto.template.TemplatePreviewDto; +import com.kumofactory.cloud.global.dto.ResultDto; import org.springframework.data.domain.Pageable; import software.amazon.awssdk.services.s3.model.S3Exception; +import java.io.IOException; import java.util.List; public interface AwsTemplateService { AwsBluePrintDto getAwsBlueprint(String uuid); + + ResultDto deployTemplate(AwsBluePrintDto dto, String templateName, boolean provision, String userId) throws IOException; + + // 전체 조회하기 List getAll(Pageable pageable) throws S3Exception; diff --git a/src/main/java/com/kumofactory/cloud/blueprint/service/AwsTemplateServiceImpl.java b/src/main/java/com/kumofactory/cloud/blueprint/service/AwsTemplateServiceImpl.java index d54d61f..a21e078 100644 --- a/src/main/java/com/kumofactory/cloud/blueprint/service/AwsTemplateServiceImpl.java +++ b/src/main/java/com/kumofactory/cloud/blueprint/service/AwsTemplateServiceImpl.java @@ -1,16 +1,25 @@ package com.kumofactory.cloud.blueprint.service; import com.kumofactory.cloud.blueprint.domain.ComponentLine; + +import com.kumofactory.cloud.blueprint.domain.ProvisionStatus; +import com.kumofactory.cloud.blueprint.domain.aws.AwsArea; + import com.kumofactory.cloud.blueprint.domain.aws.AwsBluePrint; import com.kumofactory.cloud.blueprint.domain.aws.AwsComponent; import com.kumofactory.cloud.blueprint.dto.aws.AwsBluePrintDto; +import com.kumofactory.cloud.blueprint.dto.aws.AwsCdkDto; import com.kumofactory.cloud.blueprint.dto.template.TemplatePreviewDto; import com.kumofactory.cloud.blueprint.repository.ComponentLineRepository; import com.kumofactory.cloud.blueprint.repository.aws.AwsBluePrintRepository; import com.kumofactory.cloud.blueprint.repository.aws.AwsComponentRepository; -import com.kumofactory.cloud.blueprint.domain.BluePrintScope; -import com.kumofactory.cloud.blueprint.domain.aws.AwsArea; -import com.kumofactory.cloud.blueprint.repository.aws.AwsAreaRepository; + +import com.kumofactory.cloud.global.dto.ResultDto; +import com.kumofactory.cloud.global.rabbitmq.MessageProducer; +import com.kumofactory.cloud.global.rabbitmq.domain.CdkMessagePattern; +import com.kumofactory.cloud.member.MemberRepository; +import com.kumofactory.cloud.member.domain.Member; + import com.kumofactory.cloud.util.aws.s3.AwsS3Helper; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; @@ -20,8 +29,11 @@ import org.springframework.stereotype.Service; import software.amazon.awssdk.services.s3.model.S3Exception; +import java.io.IOException; import java.util.ArrayList; +import java.util.HashMap; import java.util.List; +import java.util.Map; @Service @RequiredArgsConstructor @@ -31,6 +43,8 @@ public class AwsTemplateServiceImpl implements AwsTemplateService { private final ComponentLineRepository componentLineRepository; private final AwsAreaRepository awsAreaRepository; private final AwsComponentRepository awsComponentRepository; + private final MemberRepository memberRepository; + private final MessageProducer sender; private final AwsS3Helper s3; private final Logger logger = LoggerFactory.getLogger(AwsTemplateServiceImpl.class); @@ -48,6 +62,36 @@ public AwsBluePrintDto getAwsBlueprint(String uuid) { return AwsBluePrintDto.build(awsBluePrintById, awsAreas, awsComponents, componentLines); } + /** + * Thumbnail 저장 -> blueprint 에 저장 -> send message to aws cdk server + * + * @param templateName + * @param userId + * @return + */ + @Override + public ResultDto deployTemplate(AwsBluePrintDto dto, String templateName, boolean provision, String userId) throws IOException { + logger.info("dto {}", dto.toString()); + logger.info("USERID {}", userId); + this.delete(dto.getUuid()); // 기존 BluePrint 삭제 + Member member = memberRepository.findMemberByOauthId(userId); + ProvisionStatus status = provision ? ProvisionStatus.PROVISIONING : ProvisionStatus.PENDING; // provision 설정 + + String keyname = s3.saveThumbnail(dto, member); // thumbnail 저장 + // blueprint 저장 + saveAwsBluePrint(dto, member, templateName, status, keyname); + + if (provision) { + Map options = new HashMap<>(); + options.put("templateName", templateName); + AwsCdkDto awsCdkDto = AwsCdkDto.builder().id(dto.getUuid()).options(options).build(); + List list = new ArrayList<>(); + list.add(awsCdkDto); + sender.sendAwsCdkOption(CdkMessagePattern.TEMPLATE, list); // send message to aws cdk server + } + return null; + } + @Override public List getAll(Pageable pageable) throws S3Exception { List all = templateRepository.findAllByScopeNot(BluePrintScope.PRIVATE, pageable); @@ -81,4 +125,30 @@ private List mapToTemplatePreviewDto(List all) throw S3Exception.builder().build(); } } + + private void saveAwsBluePrint(AwsBluePrintDto dto, Member member, String templateName, ProvisionStatus status, String keyname) throws IOException { + logger.info("AWSBLUEPRINTDTO {}", dto); + logger.info("TEMPLATE NAME {}", templateName); + AwsBluePrint blueprint = new AwsBluePrint(); + blueprint.setUuid(dto.getUuid()); + blueprint.setName(dto.getName()); + blueprint.setDescription(dto.getDescription()); + blueprint.setDownloadCount(0); + blueprint.setStatus(status); + blueprint.setMember(member); + blueprint.setScope(dto.getScope()); + blueprint.setKeyName(keyname); + blueprint.setIsTemplate(true); + blueprint.setTemplateName(templateName); + templateRepository.save(blueprint); + } + + public boolean delete(String uuid) { + AwsBluePrint awsBluePrint = templateRepository.findAwsBluePrintByUuid(uuid); + if (awsBluePrint == null) { + return false; + } + templateRepository.delete(awsBluePrint); + return true; + } } diff --git a/src/main/java/com/kumofactory/cloud/global/rabbitmq/domain/CdkMessagePattern.java b/src/main/java/com/kumofactory/cloud/global/rabbitmq/domain/CdkMessagePattern.java index e58326f..0f8e1e5 100644 --- a/src/main/java/com/kumofactory/cloud/global/rabbitmq/domain/CdkMessagePattern.java +++ b/src/main/java/com/kumofactory/cloud/global/rabbitmq/domain/CdkMessagePattern.java @@ -1,5 +1,5 @@ package com.kumofactory.cloud.global.rabbitmq.domain; public enum CdkMessagePattern { - USER, KUMO + USER, KUMO, TEMPLATE, COST } diff --git a/src/main/java/com/kumofactory/cloud/util/aws/s3/AwsS3Helper.java b/src/main/java/com/kumofactory/cloud/util/aws/s3/AwsS3Helper.java index 1045e66..cd3b92a 100644 --- a/src/main/java/com/kumofactory/cloud/util/aws/s3/AwsS3Helper.java +++ b/src/main/java/com/kumofactory/cloud/util/aws/s3/AwsS3Helper.java @@ -1,5 +1,7 @@ package com.kumofactory.cloud.util.aws.s3; +import com.kumofactory.cloud.blueprint.dto.aws.AwsBluePrintDto; +import com.kumofactory.cloud.member.domain.Member; import org.springframework.web.multipart.MultipartFile; import software.amazon.awssdk.services.s3.model.S3Exception; @@ -10,4 +12,6 @@ public interface AwsS3Helper { void putS3Object(MultipartFile svgFile, String keyName) throws S3Exception, IOException; String getPresignedUrl(String keyName) throws S3Exception; + + String saveThumbnail(AwsBluePrintDto bluePrint, Member member) throws S3Exception, IOException; } diff --git a/src/main/java/com/kumofactory/cloud/util/aws/s3/AwsS3HelperImpl.java b/src/main/java/com/kumofactory/cloud/util/aws/s3/AwsS3HelperImpl.java index 5656eda..4df7acb 100644 --- a/src/main/java/com/kumofactory/cloud/util/aws/s3/AwsS3HelperImpl.java +++ b/src/main/java/com/kumofactory/cloud/util/aws/s3/AwsS3HelperImpl.java @@ -1,9 +1,12 @@ package com.kumofactory.cloud.util.aws.s3; +import com.kumofactory.cloud.blueprint.dto.aws.AwsBluePrintDto; import com.kumofactory.cloud.global.config.S3Config; +import com.kumofactory.cloud.member.domain.Member; import lombok.RequiredArgsConstructor; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.springframework.mock.web.MockMultipartFile; import org.springframework.stereotype.Service; import org.springframework.web.multipart.MultipartFile; import software.amazon.awssdk.core.sync.RequestBody; @@ -18,6 +21,7 @@ import java.io.IOException; import java.time.Duration; +import java.util.Base64; @Service @RequiredArgsConstructor @@ -80,4 +84,25 @@ public String getPresignedUrl(String keyName) { presigner.close(); } } + + // thumbnail 저장 + public String saveThumbnail(AwsBluePrintDto bluePrint, Member member) { + String objectKey = _getObjectKey(member.getOauthId(), bluePrint.getUuid()); + logger.info("thumbnail upload start: {}", objectKey); + try { + byte[] svgContent = Base64.getDecoder().decode(bluePrint.getSvgFile().split(",")[1]); + MultipartFile svgFile = new MockMultipartFile("file", objectKey, "image/svg+xml", svgContent); + putS3Object(svgFile, objectKey); + logger.info("thumbnail upload success: {}", objectKey); + logger.info("thumbnail url: {}", getPresignedUrl(objectKey)); + } catch (Exception e) { + logger.error("thumbnail upload failed: {}", e.getMessage()); + } + + return objectKey; + } + + private String _getObjectKey(String memberId, String blueprintId) { + return memberId + "/" + blueprintId + ".svg"; + } }