diff --git a/.github/workflows/dev_docker_gradle.yml b/.github/workflows/dev_docker_gradle.yml index 88b1f52..e8b6e29 100644 --- a/.github/workflows/dev_docker_gradle.yml +++ b/.github/workflows/dev_docker_gradle.yml @@ -1,58 +1,74 @@ -name: Java CI with Gradle +name: Java CI with Gradle # 워크플로우의 이름 설정 on: push: - branches: ["dev" ] + branches: ["develop"] # dev 브랜치에 푸시될 때 워크플로우 실행 pull_request: - branches: ["dev" ] + branches: ["develop"] # dev 브랜치에 대한 PR이 생성되거나 업데이트될 때 워크플로우 실행 permissions: - contents: read + contents: read # 워크플로우의 권한 설정 (read-only) jobs: build: - runs-on: ubuntu-latest + runs-on: ubuntu-latest # 워크플로우가 실행될 환경 설정 (Ubuntu 최신 버전) steps: - - uses: actions/checkout@v3 - - name: Set up JDK 17 - uses: actions/setup-java@v3 - with: - java-version: '17' - distribution: 'temurin' - - - - name: make application-prod.yml - run: | - cd ./src/main/resources - touch ./application-prod.yml - echo "${{ secrets.APPLICATION_PROD }}" > ./application-prod.yml - - - - name: Grant execute permission for gradlew - run: chmod +x gradlew - - - name: Build with Gradle - run: ./gradlew build -x test - - - name: Docker build - run: | - docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} - docker build -t app . - docker tag app ${{ secrets.DOCKER_USERNAME }}/sejongmate:latest - docker push ${{ secrets.DOCKER_USERNAME }}/sejongmate:latest - - - name: Deploy - uses: appleboy/ssh-action@master - with: - host: ${{ secrets.HOST }} # EC2 인스턴스 퍼블릭 DNS - username: ec2-user - key: ${{ secrets.PRIVATE_KEY }} # pem 키 - # 도커 작업 - script: | - docker pull ${{ secrets.DOCKER_USERNAME }}/sejongmate:latest - docker stop $(docker ps -a -q) - docker run -d --log-driver=syslog -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/sejongmate:latest - docker rm $(docker ps --filter 'status=exited' -a -q) - docker image prune -a -f + # 코드 체크아웃: GitHub Actions가 현재 리포지토리의 코드를 가져옵니다. + - uses: actions/checkout@v3 + + # JDK 17 설정: Java 17 버전을 사용하기 위해 JDK를 설치합니다. + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' # JDK 버전 17 설정 + distribution: 'temurin' # Temurin JDK 배포판 사용 + + # application.properites 파일 생성: dev 환경의 설정 파일을 생성하고 secrets에서 값을 가져와 입력합니다. + - name: make application.properties + run: | + cd ./src/main/resources # resources 디렉토리로 이동 + touch ./application.properties # application.properties 파일 생성 + echo "${{ secrets.APPLICATION_DEV }}" > ./application.properties # GitHub Secrets에서 설정값을 가져와 파일에 저장 + + # Gradle Wrapper 실행 권한 부여: gradlew에 실행 권한을 부여합니다. + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + # Gradle 빌드: Gradle을 사용해 프로젝트를 빌드하되, 테스트는 생략합니다. + - name: Build with Gradle + run: ./gradlew build -x test # 테스트는 실행하지 않고 빌드만 수행 + + # Docker 이미지 빌드 및 푸시 + - name: Docker build + run: | + docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} # Docker Hub에 로그인 + docker build -t app . # Docker 이미지를 'app'이라는 이름으로 빌드 + docker tag app ${{ secrets.DOCKER_USERNAME }}/ono:latest # 이미지를 Docker Hub 저장소로 태깅 + docker push ${{ secrets.DOCKER_USERNAME }}/ono:latest # 이미지를 Docker Hub에 푸시 + + # Bastion 서버를 통해 스프링 서버로 배포 + - name: Deploy via Bastion Server + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.DEV_BASTION_HOST }} # Bastion 서버의 퍼블릭 IP 또는 DNS 주소 + username: ubuntu # Bastion 서버의 사용자 이름 + key: ${{ secrets.DEV_BASTION_PRIVATE_KEY }} # Bastion 서버의 SSH 개인 키 (GitHub Secrets에서 가져옴) + port: 22 # SSH 연결에 사용할 포트 (기본값 22) + script: | + # Bastion 서버에서 스프링 서버로 SSH 연결 및 도커 작업 수행 + ssh -o StrictHostKeyChecking=no -i /home/ubuntu/new-dev-an2-ono-spring-key.pem ubuntu@${{ secrets.DEV_SPRING_SERVER_IP }} << 'EOSSH' + # 스프링 서버에서 Docker Hub에 로그인 + docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} + # 최신 Docker 이미지를 Docker Hub에서 Pull + docker pull ${{ secrets.DOCKER_USERNAME }}/ono:latest + # 실행 중인 모든 컨테이너를 중지 (실행 중인 컨테이너가 없을 경우 오류 무시) + docker stop $(docker ps -a -q) || true + # 중지된 모든 컨테이너를 제거 (없을 경우 오류 무시) + docker rm $(docker ps -a -q) || true + # 새로 받은 이미지를 기반으로 컨테이너 실행 (8080 포트 사용) + docker run -d --restart unless-stopped --log-driver=syslog -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/ono:latest + # 24시간 동안 사용되지 않은 모든 Docker 이미지를 삭제하여 공간 확보 + docker image prune -a -f --filter "until=24h" + EOSSH \ No newline at end of file diff --git a/.github/workflows/gradle.yml b/.github/workflows/gradle.yml index 7ab95c6..83b89da 100644 --- a/.github/workflows/gradle.yml +++ b/.github/workflows/gradle.yml @@ -1,116 +1,116 @@ -# This workflow uses actions that are not certified by GitHub. -# They are provided by a third-party and are governed by -# separate terms of service, privacy policy, and support -# documentation. -# This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time -# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle - -name: Java CI with Gradle - -on: - push: - branches: [ "main" ] - #pull_request: - #branches: [ "main" ] - -env: - AWS_REGION: ap-northeast-2 - AWS_S3_BUCKET: dev-an2-ono-bucket - AWS_CODE_DEPLOY_APPLICATION: dev-an2-ono-spring-application - AWS_CODE_DEPLOY_GROUP: prd-an2-ono-spring-bg - -jobs: - build: - - runs-on: ubuntu-latest - permissions: - contents: read - - steps: - - uses: actions/checkout@v4 - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. - # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md - - name: Setup Gradle - uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 - - # application.properties 파일 생성 - - name: set application.properites - run: | - mkdir -p src/main/resources - echo "${{ secrets.APPLICATION_PROPERTIES }}" | base64 --decode > src/main/resources/application.properties - find src - - # Build - - name: Build with Gradle Wrapper - run: ./gradlew clean build --exclude-task test - - # 전송할 파일을 담을 디렉토리 생성 - - name: Make Directory for deliver - run: mkdir deploy - - # Jar 파일 Copy - - name: Jar 파일 Copy - run: cp ./build/libs/*.jar ./deploy/ - - # 압축파일 형태로 전달 - - name: Make zip file - run: zip -r -qq -j ./springboot-ono-backend-build.zip ./deploy - - - name: AWS credential 설정 - uses: aws-actions/configure-aws-credentials@v1 - with: - aws-region: ${{ env.AWS_REGION }} - aws-access-key-id: ${{ secrets.AWS_CICD_ACCESS_KEY }} - aws-secret-access-key: ${{ secrets.AWS_CICD_SECRET_ACCESS_KEY }} - - # # S3 Bucket으로 copy - # - name: S3에 배포 - # env: - # AWS_ACCESS_KEY_ID: ${{ secrets.AWS_S3_ACCESS_KEY }} - # AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_S3_SECRET_ACCESS_KEY }} - # run: | - # aws s3 cp ./springboot-ono-backend-build.zip s3://dev-an2-ono-bucket/ \ - # --region ap-northeast-2 \ - # --acl private - - - name: S3에 업로드 - run: aws deploy push --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --ignore-hidden-files --s3-location s3://$AWS_S3_BUCKET/$GITHUB_SHA.zip --source . - - - name: EC2에 배포 - run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip - - # NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html). - # If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version. - # - # - name: Setup Gradle - # uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 - # with: - # gradle-version: '8.5' - # - # - name: Build with Gradle 8.5 - # run: gradle build - - dependency-submission: - - runs-on: ubuntu-22.04 - permissions: - contents: write - - steps: - - uses: actions/checkout@v4 - - name: Set up JDK 17 - uses: actions/setup-java@v4 - with: - java-version: '17' - distribution: 'temurin' - - # Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies. - # See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md - - name: Generate and submit dependency graph - uses: gradle/actions/dependency-submission@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 +## This workflow uses actions that are not certified by GitHub. +## They are provided by a third-party and are governed by +## separate terms of service, privacy policy, and support +## documentation. +## This workflow will build a Java project with Gradle and cache/restore any dependencies to improve the workflow execution time +## For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-java-with-gradle +# +#name: Java CI with Gradle +# +#on: +# push: +# branches: [ "main" ] +# #pull_request: +# #branches: [ "main" ] +# +#env: +# AWS_REGION: ap-northeast-2 +# AWS_S3_BUCKET: dev-an2-ono-bucket +# AWS_CODE_DEPLOY_APPLICATION: dev-an2-ono-spring-application +# AWS_CODE_DEPLOY_GROUP: prd-an2-ono-spring-bg +# +#jobs: +# build: +# +# runs-on: ubuntu-latest +# permissions: +# contents: read +# +# steps: +# - uses: actions/checkout@v4 +# - name: Set up JDK 17 +# uses: actions/setup-java@v4 +# with: +# java-version: '17' +# distribution: 'temurin' +# +# # Configure Gradle for optimal use in GitHub Actions, including caching of downloaded dependencies. +# # See: https://github.com/gradle/actions/blob/main/setup-gradle/README.md +# - name: Setup Gradle +# uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 +# +# # application.properties 파일 생성 +# - name: set application.properites +# run: | +# mkdir -p src/main/resources +# echo "${{ secrets.APPLICATION_PROPERTIES }}" | base64 --decode > src/main/resources/application.properties +# find src +# +# # Build +# - name: Build with Gradle Wrapper +# run: ./gradlew clean build --exclude-task test +# +# # 전송할 파일을 담을 디렉토리 생성 +# - name: Make Directory for deliver +# run: mkdir deploy +# +# # Jar 파일 Copy +# - name: Jar 파일 Copy +# run: cp ./build/libs/*.jar ./deploy/ +# +# # 압축파일 형태로 전달 +# - name: Make zip file +# run: zip -r -qq -j ./springboot-ono-backend-build.zip ./deploy +# +# - name: AWS credential 설정 +# uses: aws-actions/configure-aws-credentials@v1 +# with: +# aws-region: ${{ env.AWS_REGION }} +# aws-access-key-id: ${{ secrets.AWS_CICD_ACCESS_KEY }} +# aws-secret-access-key: ${{ secrets.AWS_CICD_SECRET_ACCESS_KEY }} +# +# # # S3 Bucket으로 copy +# # - name: S3에 배포 +# # env: +# # AWS_ACCESS_KEY_ID: ${{ secrets.AWS_S3_ACCESS_KEY }} +# # AWS_SECRET_ACCESS_KEY: ${{ secrets.AWS_S3_SECRET_ACCESS_KEY }} +# # run: | +# # aws s3 cp ./springboot-ono-backend-build.zip s3://dev-an2-ono-bucket/ \ +# # --region ap-northeast-2 \ +# # --acl private +# +# - name: S3에 업로드 +# run: aws deploy push --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --ignore-hidden-files --s3-location s3://$AWS_S3_BUCKET/$GITHUB_SHA.zip --source . +# +# - name: EC2에 배포 +# run: aws deploy create-deployment --application-name ${{ env.AWS_CODE_DEPLOY_APPLICATION }} --deployment-config-name CodeDeployDefault.AllAtOnce --deployment-group-name ${{ env.AWS_CODE_DEPLOY_GROUP }} --s3-location bucket=$AWS_S3_BUCKET,key=$GITHUB_SHA.zip,bundleType=zip +# +# # NOTE: The Gradle Wrapper is the default and recommended way to run Gradle (https://docs.gradle.org/current/userguide/gradle_wrapper.html). +# # If your project does not have the Gradle Wrapper configured, you can use the following configuration to run Gradle with a specified version. +# # +# # - name: Setup Gradle +# # uses: gradle/actions/setup-gradle@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 +# # with: +# # gradle-version: '8.5' +# # +# # - name: Build with Gradle 8.5 +# # run: gradle build +# +# dependency-submission: +# +# runs-on: ubuntu-22.04 +# permissions: +# contents: write +# +# steps: +# - uses: actions/checkout@v4 +# - name: Set up JDK 17 +# uses: actions/setup-java@v4 +# with: +# java-version: '17' +# distribution: 'temurin' +# +# # Generates and submits a dependency graph, enabling Dependabot Alerts for all project dependencies. +# # See: https://github.com/gradle/actions/blob/main/dependency-submission/README.md +# - name: Generate and submit dependency graph +# uses: gradle/actions/dependency-submission@417ae3ccd767c252f5661f1ace9f835f9654f2b5 # v3.1.0 diff --git a/.github/workflows/prod_docker_gradle.yml b/.github/workflows/prod_docker_gradle.yml new file mode 100644 index 0000000..0d1e182 --- /dev/null +++ b/.github/workflows/prod_docker_gradle.yml @@ -0,0 +1,72 @@ +name: Java CI with Gradle # 워크플로우의 이름 설정 + +on: + push: + branches: ["main"] # dev 브랜치에 푸시될 때 워크플로우 실행 + +permissions: + contents: read # 워크플로우의 권한 설정 (read-only) + +jobs: + build: + + runs-on: ubuntu-latest # 워크플로우가 실행될 환경 설정 (Ubuntu 최신 버전) + + steps: + # 코드 체크아웃: GitHub Actions가 현재 리포지토리의 코드를 가져옵니다. + - uses: actions/checkout@v3 + + # JDK 17 설정: Java 17 버전을 사용하기 위해 JDK를 설치합니다. + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' # JDK 버전 17 설정 + distribution: 'temurin' # Temurin JDK 배포판 사용 + + # application.properties 파일 생성: dev 환경의 설정 파일을 생성하고 secrets에서 값을 가져와 입력합니다. + - name: make application.properties + run: | + cd ./src/main/resources # resources 디렉토리로 이동 + touch ./application.properties # application.properties 파일 생성 + echo "${{ secrets.APPLICATION_PROD }}" > ./application.properties # GitHub Secrets에서 설정값을 가져와 파일에 저장 + + # Gradle Wrapper 실행 권한 부여: gradlew에 실행 권한을 부여합니다. + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + # Gradle 빌드: Gradle을 사용해 프로젝트를 빌드하되, 테스트는 생략합니다. + - name: Build with Gradle + run: ./gradlew build -x test # 테스트는 실행하지 않고 빌드만 수행 + + # Docker 이미지 빌드 및 푸시 + - name: Docker build + run: | + docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} # Docker Hub에 로그인 + docker build -t app . # Docker 이미지를 'app'이라는 이름으로 빌드 + docker tag app ${{ secrets.DOCKER_USERNAME }}/ono:latest # 이미지를 Docker Hub 저장소로 태깅 + docker push ${{ secrets.DOCKER_USERNAME }}/ono:latest # 이미지를 Docker Hub에 푸시 + + # Bastion 서버를 통해 스프링 서버로 배포 + - name: Deploy via Bastion Server + uses: appleboy/ssh-action@master + with: + host: ${{ secrets.PROD_BASTION_HOST }} # Bastion 서버의 퍼블릭 IP 또는 DNS 주소 + username: ubuntu # Bastion 서버의 사용자 이름 + key: ${{ secrets.PROD_BASTION_PRIVATE_KEY }} # Bastion 서버의 SSH 개인 키 (GitHub Secrets에서 가져옴) + port: 22 # SSH 연결에 사용할 포트 (기본값 22) + script: | + # Bastion 서버에서 스프링 서버로 SSH 연결 및 도커 작업 수행 + ssh -o StrictHostKeyChecking=no -i /home/ubuntu/dev-an2-ono-test-spring-key.pem ubuntu@${{ secrets.PROD_SPRING_SERVER_IP }} << 'EOSSH' + # 스프링 서버에서 Docker Hub에 로그인 + docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }} + # 최신 Docker 이미지를 Docker Hub에서 Pull + docker pull ${{ secrets.DOCKER_USERNAME }}/ono:latest + # 실행 중인 모든 컨테이너를 중지 (실행 중인 컨테이너가 없을 경우 오류 무시) + docker stop $(docker ps -a -q) || true + # 중지된 모든 컨테이너를 제거 (없을 경우 오류 무시) + docker rm $(docker ps -a -q) || true + # 새로 받은 이미지를 기반으로 컨테이너 실행 (8080 포트 사용) + docker run -d --restart unless-stopped --log-driver=syslog -p 8080:8080 ${{ secrets.DOCKER_USERNAME }}/ono:latest + # 24시간 동안 사용되지 않은 모든 Docker 이미지를 삭제하여 공간 확보 + docker image prune -a -f --filter "until=24h" + EOSSH \ No newline at end of file diff --git a/.gitignore b/.gitignore index 52ced05..f013ced 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,6 @@ out/ ### aws properties file application.properties -application-aws.properties \ No newline at end of file +application-aws.properties +application-dev.properties +application-prod.properties \ No newline at end of file diff --git a/src/main/java/com/aisip/OnO/backend/Dto/Problem/ProblemResponseDto.java b/src/main/java/com/aisip/OnO/backend/Dto/Problem/ProblemResponseDto.java index 194c21f..65eb837 100644 --- a/src/main/java/com/aisip/OnO/backend/Dto/Problem/ProblemResponseDto.java +++ b/src/main/java/com/aisip/OnO/backend/Dto/Problem/ProblemResponseDto.java @@ -1,5 +1,8 @@ package com.aisip.OnO.backend.Dto.Problem; +import com.aisip.OnO.backend.entity.Problem.TemplateType; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; import lombok.*; import java.time.LocalDateTime; @@ -24,6 +27,11 @@ public class ProblemResponseDto { private String reference; + private String analysis; + + @Enumerated(EnumType.STRING) + private TemplateType templateType; + private LocalDateTime solvedAt; private LocalDateTime createdAt; diff --git a/src/main/java/com/aisip/OnO/backend/controller/ImageProcessController.java b/src/main/java/com/aisip/OnO/backend/controller/ImageProcessController.java new file mode 100644 index 0000000..ea05860 --- /dev/null +++ b/src/main/java/com/aisip/OnO/backend/controller/ImageProcessController.java @@ -0,0 +1,81 @@ +package com.aisip.OnO.backend.controller; + +import com.aisip.OnO.backend.Dto.Process.ImageProcessRegisterDto; +import com.aisip.OnO.backend.entity.Image.ImageType; +import com.aisip.OnO.backend.entity.Problem.Problem; +import com.aisip.OnO.backend.exception.ProblemNotFoundException; +import com.aisip.OnO.backend.service.FileUploadService; +import com.aisip.OnO.backend.service.ProblemService; +import io.sentry.Sentry; +import lombok.RequiredArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.security.core.Authentication; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.io.IOException; +import java.util.Map; + +@Slf4j +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/process") +public class ImageProcessController { + + private final ProblemService problemService; + + private final FileUploadService fileUploadService; + + @PostMapping("/problemImage") + public ResponseEntity registerProblemImage( + Authentication authentication, + @RequestParam("problemImage") MultipartFile problemImage + ) { + try { + Long userId = (Long) authentication.getPrincipal(); + Problem problem = problemService.createProblem(userId); + + String problemImageUrl = fileUploadService.uploadFileToS3(problemImage, problem, ImageType.PROBLEM_IMAGE); + + return ResponseEntity.ok(Map.of("problemId", problem.getId(), "problemImageUrl", problemImageUrl)); + } catch (ProblemNotFoundException | IOException e) { + log.error(e.getMessage()); + Sentry.captureException(e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("이미지 등록에 실패했습니다."); + } + } + + @GetMapping("/analysis") + public ResponseEntity getProblemAnalysis( + Authentication authentication, + @RequestParam("problemImageUrl") String problemImageUrl + ) { + try { + String analysisResult = fileUploadService.getProblemAnalysis(problemImageUrl); + + return ResponseEntity.ok(Map.of("analysis", analysisResult)); + } catch (Exception e) { + log.error(e.getMessage()); + Sentry.captureException(e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("이미지 분석에 실패했습니다."); + } + } + + @GetMapping("/processImage") + public ResponseEntity getProcessImage( + Authentication authentication, + @ModelAttribute ImageProcessRegisterDto imageProcessRegisterDto + ) { + try { + String processImageUrl = fileUploadService.getProcessImageUrl(imageProcessRegisterDto); + + return ResponseEntity.ok(Map.of("processImageUrl", processImageUrl)); + } catch (Exception e) { + log.error(e.getMessage()); + Sentry.captureException(e); + return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("이미지 보정에 실패했습니다."); + } + } +} diff --git a/src/main/java/com/aisip/OnO/backend/converter/ProblemConverter.java b/src/main/java/com/aisip/OnO/backend/converter/ProblemConverter.java index dea1303..39340a4 100644 --- a/src/main/java/com/aisip/OnO/backend/converter/ProblemConverter.java +++ b/src/main/java/com/aisip/OnO/backend/converter/ProblemConverter.java @@ -2,7 +2,7 @@ import com.aisip.OnO.backend.Dto.Problem.ProblemResponseDto; import com.aisip.OnO.backend.entity.Image.ImageData; -import com.aisip.OnO.backend.entity.Problem; +import com.aisip.OnO.backend.entity.Problem.Problem; import java.util.List; @@ -20,6 +20,8 @@ public static ProblemResponseDto convertToResponseDto(Problem problem, List images; } diff --git a/src/main/java/com/aisip/OnO/backend/entity/Problem/TemplateType.java b/src/main/java/com/aisip/OnO/backend/entity/Problem/TemplateType.java new file mode 100644 index 0000000..c4721c7 --- /dev/null +++ b/src/main/java/com/aisip/OnO/backend/entity/Problem/TemplateType.java @@ -0,0 +1,33 @@ +package com.aisip.OnO.backend.entity.Problem; + +public enum TemplateType { + + SIMPLE_TEMPLATE(1, "simple template"), + CLEAN_TEMPLATE(2, "clean template"), + SPECIAL_TEMPLATE(3, "special template"); + + private final int code; + private final String description; + + TemplateType(int code, String description) { + this.code = code; + this.description = description; + } + + public int getCode() { + return code; + } + + public String getDescription() { + return description; + } + + public static TemplateType valueOf(int code) { + for (TemplateType type : values()) { + if (type.getCode() == code) { + return type; + } + } + throw new IllegalArgumentException("Invalid TemplateType code: " + code); + } +} diff --git a/src/main/java/com/aisip/OnO/backend/repository/ProblemRepository.java b/src/main/java/com/aisip/OnO/backend/repository/ProblemRepository.java index fcc5978..9e98125 100644 --- a/src/main/java/com/aisip/OnO/backend/repository/ProblemRepository.java +++ b/src/main/java/com/aisip/OnO/backend/repository/ProblemRepository.java @@ -1,6 +1,6 @@ package com.aisip.OnO.backend.repository; -import com.aisip.OnO.backend.entity.Problem; +import com.aisip.OnO.backend.entity.Problem.Problem; import org.springframework.data.jpa.repository.JpaRepository; import java.util.List; diff --git a/src/main/java/com/aisip/OnO/backend/service/FileUploadService.java b/src/main/java/com/aisip/OnO/backend/service/FileUploadService.java index 66ca901..3f903f2 100644 --- a/src/main/java/com/aisip/OnO/backend/service/FileUploadService.java +++ b/src/main/java/com/aisip/OnO/backend/service/FileUploadService.java @@ -3,7 +3,7 @@ import com.aisip.OnO.backend.Dto.Process.ImageProcessRegisterDto; import com.aisip.OnO.backend.entity.Image.ImageData; import com.aisip.OnO.backend.entity.Image.ImageType; -import com.aisip.OnO.backend.entity.Problem; +import com.aisip.OnO.backend.entity.Problem.Problem; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; @@ -12,7 +12,12 @@ public interface FileUploadService { String uploadFileToS3(MultipartFile file, Problem problem, ImageType imageType) throws IOException; - String saveProcessImageUrl(ImageProcessRegisterDto imageProcessRegisterDto, Problem problem, ImageType imageType); + String getProcessImageUrl(ImageProcessRegisterDto imageProcessRegisterDto); + + String saveAndGetProcessImageUrl(ImageProcessRegisterDto imageProcessRegisterDto, Problem problem, ImageType imageType); + + + String getProblemAnalysis(String problemImageUrl); String updateImage(MultipartFile file, Problem problem, ImageType imageType) throws IOException; diff --git a/src/main/java/com/aisip/OnO/backend/service/FileUploadServiceImpl.java b/src/main/java/com/aisip/OnO/backend/service/FileUploadServiceImpl.java index 823830d..4a93c8c 100644 --- a/src/main/java/com/aisip/OnO/backend/service/FileUploadServiceImpl.java +++ b/src/main/java/com/aisip/OnO/backend/service/FileUploadServiceImpl.java @@ -3,7 +3,7 @@ import com.aisip.OnO.backend.Dto.Process.ImageProcessRegisterDto; import com.aisip.OnO.backend.entity.Image.ImageData; import com.aisip.OnO.backend.entity.Image.ImageType; -import com.aisip.OnO.backend.entity.Problem; +import com.aisip.OnO.backend.entity.Problem.Problem; import com.aisip.OnO.backend.repository.ImageDataRepository; import com.amazonaws.services.s3.AmazonS3Client; import com.amazonaws.services.s3.model.DeleteObjectRequest; @@ -14,17 +14,17 @@ import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.annotation.Value; -import org.springframework.http.HttpEntity; -import org.springframework.http.HttpHeaders; -import org.springframework.http.MediaType; -import org.springframework.http.ResponseEntity; +import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.*; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.client.RestTemplate; import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.util.UriComponentsBuilder; import java.io.IOException; import java.util.List; +import java.util.Map; import java.util.Optional; @Slf4j @@ -56,7 +56,6 @@ public String uploadFileToS3(MultipartFile file, Problem problem, ImageType imag return fileUrl; } - private void saveImageData(String imageUrl, Problem problem, ImageType imageType) { ImageData imageData = ImageData.builder() @@ -69,7 +68,40 @@ private void saveImageData(String imageUrl, Problem problem, ImageType imageType } @Override - public String saveProcessImageUrl(ImageProcessRegisterDto imageProcessRegisterDto, Problem problem, ImageType imageType) { + public String getProcessImageUrl(ImageProcessRegisterDto imageProcessRegisterDto) { + RestTemplate restTemplate = new RestTemplate(); + String url = fastApiUrl + "/process-color"; + + HttpHeaders headers = new HttpHeaders(); + headers.setContentType(MediaType.APPLICATION_JSON); + + HttpEntity request = new HttpEntity<>(imageProcessRegisterDto, headers); + + log.info("remove colors on problemImage by colors: " + imageProcessRegisterDto.getColorsList()); + + try { + ResponseEntity response = restTemplate.postForEntity(url, request, String.class); + + log.info("Response from fastApi server: " + response); + + ObjectMapper objectMapper = new ObjectMapper(); + JsonNode rootNode = objectMapper.readTree(response.getBody()); + JsonNode pathNode = rootNode.path("path"); + String inputPath = pathNode.path("output_path").asText(); + + String fileUrl = "https://" + bucket + ".s3.ap-northeast-2.amazonaws.com/" + inputPath; + + log.info("process image url : " + fileUrl + " has successfully processed"); + return fileUrl; + } catch (Exception e) { + log.info(e.getMessage()); + Sentry.captureException(e); + throw new RuntimeException("Failed to parse response", e); + } + } + + @Override + public String saveAndGetProcessImageUrl(ImageProcessRegisterDto imageProcessRegisterDto, Problem problem, ImageType imageType) { RestTemplate restTemplate = new RestTemplate(); String url = fastApiUrl + "/process-color"; @@ -102,6 +134,41 @@ public String saveProcessImageUrl(ImageProcessRegisterDto imageProcessRegisterDt } } + @Override + public String getProblemAnalysis(String problemImageUrl){ + try{ + RestTemplate restTemplate = new RestTemplate(); + String url = UriComponentsBuilder.fromHttpUrl(fastApiUrl + "/analysis/whole") + .queryParam("problem_url", problemImageUrl) + .toUriString(); + + ResponseEntity> responseEntity = restTemplate.exchange( + url, + HttpMethod.GET, + null, + new ParameterizedTypeReference<>() { + } + ); + + // JSON 응답에서 필요한 값 추출 + if (responseEntity.getStatusCode().is2xxSuccessful()) { + Map response = responseEntity.getBody(); + + // 응답 본문에서 필요한 값 추출 + if (response != null) { + return (String) response.get("answer"); + } else { + throw new RuntimeException("응답 데이터가 비어있습니다."); + } + } else { + throw new RuntimeException("요청이 실패했습니다. 상태 코드: " + responseEntity.getStatusCode()); + } + } catch (Exception e) { + e.printStackTrace(); + throw new RuntimeException("문제 분석 요청 중 오류가 발생했습니다.", e); + } + } + @Override public String updateImage(MultipartFile file, Problem problem, ImageType imageType) throws IOException { @@ -158,14 +225,4 @@ private String createFileName(MultipartFile file, Problem problem, ImageType ima private String getFileUrl(String fileName) { return "https://" + bucket + ".s3.ap-northeast-2.amazonaws.com/" + fileName; } - - private String getFileExtension(String fileName) { - try { - return fileName.substring(fileName.lastIndexOf(".")); - } catch (StringIndexOutOfBoundsException e) { - log.warn(e.getMessage()); - Sentry.captureException(e); - throw new IllegalArgumentException(String.format("잘못된 형식의 파일 (%s) 입니다.", fileName)); - } - } } diff --git a/src/main/java/com/aisip/OnO/backend/service/FolderServiceImpl.java b/src/main/java/com/aisip/OnO/backend/service/FolderServiceImpl.java index 8d6999f..51c4004 100644 --- a/src/main/java/com/aisip/OnO/backend/service/FolderServiceImpl.java +++ b/src/main/java/com/aisip/OnO/backend/service/FolderServiceImpl.java @@ -4,7 +4,7 @@ import com.aisip.OnO.backend.Dto.Folder.FolderThumbnailResponseDto; import com.aisip.OnO.backend.Dto.Problem.ProblemResponseDto; import com.aisip.OnO.backend.entity.Folder; -import com.aisip.OnO.backend.entity.Problem; +import com.aisip.OnO.backend.entity.Problem.Problem; import com.aisip.OnO.backend.entity.User.User; import com.aisip.OnO.backend.exception.FolderNotFoundException; import com.aisip.OnO.backend.exception.ProblemNotFoundException; @@ -114,13 +114,10 @@ public FolderResponseDto findFolder(Long userId, Long folderId) { List subFolders = null; if (folder.getSubFolders() != null && !folder.getSubFolders().isEmpty()) { - subFolders = folder.getSubFolders().stream().map(subFolder -> { - return FolderThumbnailResponseDto.builder() - .folderId(subFolder.getId()) - .folderName(subFolder.getName()) - .build(); - - }).toList(); + subFolders = folder.getSubFolders().stream().map(subFolder -> FolderThumbnailResponseDto.builder() + .folderId(subFolder.getId()) + .folderName(subFolder.getName()) + .build()).toList(); } List problems = problemService.findAllProblemsByFolderId(folderId); diff --git a/src/main/java/com/aisip/OnO/backend/service/ProblemService.java b/src/main/java/com/aisip/OnO/backend/service/ProblemService.java index 659699f..dfba332 100644 --- a/src/main/java/com/aisip/OnO/backend/service/ProblemService.java +++ b/src/main/java/com/aisip/OnO/backend/service/ProblemService.java @@ -2,6 +2,7 @@ import com.aisip.OnO.backend.Dto.Problem.ProblemRegisterDto; import com.aisip.OnO.backend.Dto.Problem.ProblemResponseDto; +import com.aisip.OnO.backend.entity.Problem.Problem; import java.util.List; @@ -13,6 +14,8 @@ public interface ProblemService { List findAllProblemsByFolderId(Long folderId); + Problem createProblem(Long userId); + boolean saveProblem(Long userId, ProblemRegisterDto problemRegisterDto); boolean updateProblem(Long userId, ProblemRegisterDto problemRegisterDto); diff --git a/src/main/java/com/aisip/OnO/backend/service/ProblemServiceImpl.java b/src/main/java/com/aisip/OnO/backend/service/ProblemServiceImpl.java index 0549152..caa78e1 100644 --- a/src/main/java/com/aisip/OnO/backend/service/ProblemServiceImpl.java +++ b/src/main/java/com/aisip/OnO/backend/service/ProblemServiceImpl.java @@ -10,7 +10,7 @@ import com.aisip.OnO.backend.converter.ProblemConverter; import com.aisip.OnO.backend.entity.Image.ImageData; import com.aisip.OnO.backend.entity.Image.ImageType; -import com.aisip.OnO.backend.entity.Problem; +import com.aisip.OnO.backend.entity.Problem.Problem; import com.aisip.OnO.backend.exception.ProblemNotFoundException; import com.aisip.OnO.backend.exception.UserNotAuthorizedException; import com.aisip.OnO.backend.exception.UserNotFoundException; @@ -79,6 +79,24 @@ public List findAllProblemsByFolderId(Long folderId) { } + @Override + public Problem createProblem(Long userId) { + + Optional optionalUserEntity = userRepository.findById(userId); + if (optionalUserEntity.isPresent()) { + User user = optionalUserEntity.get(); + + Problem problem = Problem.builder() + .user(user) + .build(); + + return problemRepository.save(problem); + } else{ + throw new UserNotFoundException("유저를 찾을 수 없습니다!, userId : " + userId); + } + } + + @Override public boolean saveProblem(Long userId, ProblemRegisterDto problemRegisterDto) { Optional optionalUserEntity = userRepository.findById(userId); @@ -113,7 +131,7 @@ public boolean saveProblem(Long userId, ProblemRegisterDto problemRegisterDto) { .colorsList(problemRegisterDto.getColorsList()) .build(); - String processImageUrl = fileUploadService.saveProcessImageUrl(imageProcessRegisterDto, savedProblem, ImageType.PROCESS_IMAGE); + String processImageUrl = fileUploadService.saveAndGetProcessImageUrl(imageProcessRegisterDto, savedProblem, ImageType.PROCESS_IMAGE); } else{ String processImageUrl = fileUploadService.uploadFileToS3(problemRegisterDto.getProblemImage(), savedProblem, ImageType.PROCESS_IMAGE); } @@ -177,7 +195,7 @@ public boolean updateProblem(Long userId, ProblemRegisterDto problemRegisterDto) .colorsList(problemRegisterDto.getColorsList()) .build(); - String processImageUrl = fileUploadService.saveProcessImageUrl(imageProcessRegisterDto, problem, ImageType.PROCESS_IMAGE); + String processImageUrl = fileUploadService.saveAndGetProcessImageUrl(imageProcessRegisterDto, problem, ImageType.PROCESS_IMAGE); } else{ String processImageUrl = fileUploadService.uploadFileToS3(problemRegisterDto.getProblemImage(), problem, ImageType.PROCESS_IMAGE); }