diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 0000000..118f2a9 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,64 @@ +name: Deploy to Amazon EC2 + +on: + push: + branches: + - master +env: + AWS_REGION: us-east-2 + S3_BUCKET_NAME: mypoolcbucket + CODE_DEPLOY_APPLICATION_NAME: poolc-reborn + CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: poolc-codedeploy-deployment-group + +permissions: + contents: read + +jobs: + deploy: + name: Deploy + runs-on: ubuntu-latest + environment: production + + steps: + + - name: Checkout + uses: actions/checkout@v3 + + + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + distribution: 'temurin' + java-version: '11' + + + - name: Build with Gradle + uses: gradle/gradle-build-action@0d13054264b0bb894ded474f08ebb30921341cee + with: + arguments: clean build -x test + + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v1 + with: + aws-access-key-id: ${{ secrets.AWS_ACCESS_KEY_ID }} + aws-secret-access-key: ${{ secrets.AWS_SECRET_ACCESS_KEY }} + aws-region: ${{ env.AWS_REGION }} + + + - name: Upload to AWS S3 + run: | + aws deploy push \ + --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \ + --ignore-hidden-files \ + --s3-location s3://$S3_BUCKET_NAME/$GITHUB_SHA.zip \ + --source . + + + - name: Deploy to AWS EC2 from S3 + run: | + aws deploy create-deployment \ + --application-name ${{ env.CODE_DEPLOY_APPLICATION_NAME }} \ + --deployment-config-name CodeDeployDefault.AllAtOnce \ + --deployment-group-name ${{ env.CODE_DEPLOY_DEPLOYMENT_GROUP_NAME }} \ + --s3-location bucket=$S3_BUCKET_NAME,key=$GITHUB_SHA.zip,bundleType=zip diff --git a/README.md b/README.md index 64046db..58aebc3 100644 --- a/README.md +++ b/README.md @@ -1,29 +1,18 @@ # PoolC-Spring-Practice -풀씨 백엔드와 유사하지만, 일부 기능들을 개선한 서버를 만들어봅시다. 물론 풀씨 페이지는 규모가 상당히 크기에, 모든 컴포넌트를 만들 수 없으니 기본적인 것들만 구현해 봅시다. +풀씨 (동아리) 서버와 유사하지만 주요기능에 집중하여 개선한 서버 사이드 프로젝트입니다. -진행 방식 ---- -- Spring 프로그래밍 연습 스터디는, 하나의 큰 과제를 수행해야 합니다. - - Java 스터디에 비해 자유도가 조금 높으며, 요구 사항이 다소 추상적입니다. -- 직접적인 main 브랜치로의 커밋은 금지 되며, 반드시 Step 수행 이후 Pull Request 요청을 통해 확인이 진행됩니다. - - 본인의 닉네임/이름에 해당하는 브랜치를 만들고, 각 Step 에 대한 브랜치를 만들어서 PR을 진행해 주세요. - - ex) KBC 브랜치를 만들고, Step 1에 대한 결과물은 KBC-step1 로 만들어 주세요. - - 그 이후, PR은 KBC-step1 -> KBC 꼴로 요청해 주세요. -- 각 커밋의 단위는 최소화 해야하며, 다음과 같은 커밋 메시지 양식을 준수해 주세요. - - https://vsfe.notion.site/Git-Convention-84e1df4868974a58a1609b052e815095 +### 주요 기능 목록 +- 회원가입, 로그인 및 회원수정 +- 유저 등급 분리 및 관리 +- 동아리 회원 확인 기능 +- 세미나 등록, 신청, 수정 기능 +- Naver API를 사용하거나 직접 입력으로 책 등록, 책 대출 및 반납 기능 +- AWS를 이용한 CI/CD 프로세스 구축 +- Github Action을 이용한 Pre-hook, Post-hook -요구 사항 (공통) ---- -- 해당 과제는 여러 Step으로 구성되어 있으며, 앞 Step에 대한 PR 및 리뷰가 완료 되어야 뒤 Step을 진행할 수 있습니다. -- 포함된 라이브러리는 기본적인 라이브러리만 포함되어 있으며, 필요에 따라 추가 라이브러리를 사용해도 됩니다. -- 모든 Java 코드는 반드시 Java 코드 컨벤션 가이드를 준수해야 합니다. -- 작성한 메서드에 대한 테스트 코드 작성이 진행되어야 합니다. - - Jacoco 기준, Test Coverage 및 Branch Coverage가 80% 이상이어야 합니다. - - 통합 테스트/단위 테스트 여부는 자유롭게 설정하셔도 됩니다. - - 하지만, 통합 테스트 수행 시, 실제 DB에 전혀 영향이 가지 않아야 합니다. -- 사용하는 DB는 제한이 없습니다. +### 사용 기술 -요구 사항 (단계) ---- -- 특정 Step 을 마치지 못했다면, 그 다음 Step의 요구 사항을 보지 않는 것을 권장합니다. https://vsfe.notion.site/Spring-PoolC-Backend-Reborn-281c69c2eaf543459fedace987868ea4 +- Spring Boot +- MySQL +- AWS 배포 diff --git a/appspec.yml b/appspec.yml new file mode 100644 index 0000000..9882a4f --- /dev/null +++ b/appspec.yml @@ -0,0 +1,23 @@ +version: 0.0 +os: linux + +files: + - source: / + destination: /home/ubuntu/app + overwrite: yes + +permissions: + - object: / + pattern: "**" + owner: ubuntu + group: ubuntu + +hooks: + AfterInstall: + - location: scripts/stop.sh + timeout: 60 + runas: ubuntu + ApplicationStart: + - location: scripts/start.sh + timeout: 60 + runas: ubuntu \ No newline at end of file diff --git a/build.gradle b/build.gradle index e1b2f02..1f311a5 100644 --- a/build.gradle +++ b/build.gradle @@ -50,3 +50,6 @@ dependencies { tasks.named('test') { useJUnitPlatform() } +jar { + enabled = false +} diff --git a/scripts/start.sh b/scripts/start.sh new file mode 100644 index 0000000..126c2a2 --- /dev/null +++ b/scripts/start.sh @@ -0,0 +1,21 @@ +#!/usr/bin/env bash + +PROJECT_ROOT="/home/ubuntu/app" +JAR_FILE="$PROJECT_ROOT/spring-webapp.jar" + +APP_LOG="$PROJECT_ROOT/application.log" +ERROR_LOG="$PROJECT_ROOT/error.log" +DEPLOY_LOG="$PROJECT_ROOT/deploy.log" + +TIME_NOW=$(date +%c) + +# build 파일 복사 +echo "$TIME_NOW > $JAR_FILE 파일 복사" >> $DEPLOY_LOG +cp $PROJECT_ROOT/build/libs/*.jar $JAR_FILE + +# jar 파일 실행 +echo "$TIME_NOW > $JAR_FILE 파일 실행" >> $DEPLOY_LOG +nohup java -jar $JAR_FILE > $APP_LOG 2> $ERROR_LOG & + +CURRENT_PID=$(pgrep -f $JAR_FILE) +echo "$TIME_NOW > 실행된 프로세스 아이디 $CURRENT_PID 입니다." >> $DEPLOY_LOG \ No newline at end of file diff --git a/scripts/stop.sh b/scripts/stop.sh new file mode 100644 index 0000000..755d347 --- /dev/null +++ b/scripts/stop.sh @@ -0,0 +1,19 @@ +#!/usr/bin/env bash + +PROJECT_ROOT="/home/ubuntu/app" +JAR_FILE="$PROJECT_ROOT/spring-webapp.jar" + +DEPLOY_LOG="$PROJECT_ROOT/deploy.log" + +TIME_NOW=$(date +%c) + +# 현재 구동 중인 애플리케이션 pid 확인 +CURRENT_PID=$(pgrep -f $JAR_FILE) + +# 프로세스가 켜져 있으면 종료 +if [ -z $CURRENT_PID ]; then + echo "$TIME_NOW > 현재 실행중인 애플리케이션이 없습니다" >> $DEPLOY_LOG +else + echo "$TIME_NOW > 실행중인 $CURRENT_PID 애플리케이션 종료 " >> $DEPLOY_LOG + kill -15 $CURRENT_PID +fi \ No newline at end of file diff --git a/src/main/java/com/poolc/springproject/poolcreborn/api/ApiSearchRequest.java b/src/main/java/com/poolc/springproject/poolcreborn/api/ApiSearchRequest.java index a5612bd..b706318 100644 --- a/src/main/java/com/poolc/springproject/poolcreborn/api/ApiSearchRequest.java +++ b/src/main/java/com/poolc/springproject/poolcreborn/api/ApiSearchRequest.java @@ -23,4 +23,4 @@ public ApiSearchRequest(String query, NaverApiInvokerCommand command) { this.query = query; this.command = command; } -} +} \ No newline at end of file diff --git a/src/main/java/com/poolc/springproject/poolcreborn/api/NaverApiInvoker.java b/src/main/java/com/poolc/springproject/poolcreborn/api/NaverApiInvoker.java index 64ca3c2..98ed122 100644 --- a/src/main/java/com/poolc/springproject/poolcreborn/api/NaverApiInvoker.java +++ b/src/main/java/com/poolc/springproject/poolcreborn/api/NaverApiInvoker.java @@ -61,4 +61,4 @@ private RequestEntity.HeadersBuilder buildRequest(HttpMethod method, URI uri) } } -} +} \ No newline at end of file diff --git a/src/main/java/com/poolc/springproject/poolcreborn/model/user/User.java b/src/main/java/com/poolc/springproject/poolcreborn/model/user/User.java index 8606de5..c7c4235 100644 --- a/src/main/java/com/poolc/springproject/poolcreborn/model/user/User.java +++ b/src/main/java/com/poolc/springproject/poolcreborn/model/user/User.java @@ -31,7 +31,7 @@ public class User { @IncludeCharInt private String username; - @NotEmpty(message = "암호는 필수 입력 항목입니다.") + @NotBlank(message = "암호는 필수 입력 항목입니다.") @Size(min = 8) private String password; @@ -39,20 +39,20 @@ public class User { private String name; @Email - @NotEmpty(message = "이메일은 필수 입력 항목입니다.") + @NotBlank(message = "이메일은 필수 입력 항목입니다.") private String email; - @NotEmpty(message = "전화번호 필수 입력 항목입니다.") + @NotBlank(message = "전화번호 필수 입력 항목입니다.") @Pattern(regexp = "^01(?:0|1|[6-9])[.-]?(\\d{3}|\\d{4})[.-]?(\\d{4})$") private String mobileNumber; - @NotEmpty(message = "전공은 필수 입력 항목입니다.") + @NotBlank(message = "전공은 필수 입력 항목입니다.") private String major; @NotNull(message = "학번은 필수 입력 항목입니다.") private int studentId; - @NotEmpty(message = "자기소개는 필수 입력 항목입니다.") + @NotBlank(message = "자기소개는 필수 입력 항목입니다.") private String description; @Enumerated(EnumType.STRING) diff --git a/src/main/java/com/poolc/springproject/poolcreborn/security/SecurityConfig.java b/src/main/java/com/poolc/springproject/poolcreborn/security/SecurityConfig.java index 1b41c7b..7a9d99b 100644 --- a/src/main/java/com/poolc/springproject/poolcreborn/security/SecurityConfig.java +++ b/src/main/java/com/poolc/springproject/poolcreborn/security/SecurityConfig.java @@ -102,6 +102,4 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http.build(); } - - } diff --git a/src/main/java/com/poolc/springproject/poolcreborn/service/BookService.java b/src/main/java/com/poolc/springproject/poolcreborn/service/BookService.java index 9fec4b6..c0fe6ab 100644 --- a/src/main/java/com/poolc/springproject/poolcreborn/service/BookService.java +++ b/src/main/java/com/poolc/springproject/poolcreborn/service/BookService.java @@ -1,7 +1,6 @@ package com.poolc.springproject.poolcreborn.service; import com.poolc.springproject.poolcreborn.api.NaverApiInvoker; -import com.poolc.springproject.poolcreborn.api.NaverApiInvokerCommand; import com.poolc.springproject.poolcreborn.exception.InvalidRequestException; import com.poolc.springproject.poolcreborn.exception.InvalidStateException; import com.poolc.springproject.poolcreborn.model.book.Book; @@ -35,7 +34,7 @@ public class BookService { private final UserRepository userRepository; public void saveBook(BookRequest bookRequest, String username) throws InvalidRequestException { User user = userRepository.findByUsername(username) - .orElseThrow(() -> new InvalidRequestException(Message.USER_DOES_NOT_EXIST)); + .orElseThrow(() -> new InvalidRequestException(Message.USER_DOES_NOT_EXIST)); if (user != null && user.isAdmin() && !bookRepository.existsByIsbn(bookRequest.getIsbn())) { Book book = new Book(); @@ -89,7 +88,7 @@ public void returnBook(Long currentBookId, String username) throws Exception { } } - public void registerNaverBook(ApiSearchRequest searchRequest, Long bookId) throws Exception{ + public void registerNaverBook(ApiSearchRequest searchRequest, Long bookId) throws Exception { List bookDtoList = bookSearch(searchRequest); BookDto bookDto = bookDtoList.get(bookId.intValue()); Book book = bookMapper.buildBookFromBookDto(bookDto); @@ -113,4 +112,4 @@ public List bookSearch(ApiSearchRequest searchRequest) throws InvalidRe ResponseEntity result = invoker.naverBookSearchApi(); return fromJSONtoBookDtoList(result.getBody()); } -} +} \ No newline at end of file