From 500e454c4f17f8e28ad8878575bfdabaae9aa328 Mon Sep 17 00:00:00 2001 From: rogi-rogi Date: Thu, 4 Jul 2024 12:07:45 +0900 Subject: [PATCH 1/4] [feature] connect mysql and test for post board --- .gitignore | 3 ++- build.gradle | 4 ++-- .../main/controller/BoardController.java | 20 +++++++++++++++++++ src/main/java/codeview/main/entity/Board.java | 13 ++++++++++++ .../main/repository/BoardRepository.java | 9 +++++++++ .../codeview/main/service/BoardService.java | 15 ++++++++++++++ 6 files changed, 61 insertions(+), 3 deletions(-) create mode 100644 src/main/java/codeview/main/controller/BoardController.java create mode 100644 src/main/java/codeview/main/entity/Board.java create mode 100644 src/main/java/codeview/main/repository/BoardRepository.java create mode 100644 src/main/java/codeview/main/service/BoardService.java diff --git a/.gitignore b/.gitignore index 0597b0e..4847fa6 100644 --- a/.gitignore +++ b/.gitignore @@ -38,4 +38,5 @@ out/ ### SET ### -!**/src/main/resources/application.properties \ No newline at end of file +application.properties +application.yml \ No newline at end of file diff --git a/build.gradle b/build.gradle index 07e03f8..e76c10b 100644 --- a/build.gradle +++ b/build.gradle @@ -25,8 +25,8 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jpa' - implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' - implementation 'org.springframework.boot:spring-boot-starter-security' +// implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' +// implementation 'org.springframework.boot:spring-boot-starter-security' implementation 'org.springframework.boot:spring-boot-starter-web' compileOnly 'org.projectlombok:lombok' runtimeOnly 'com.mysql:mysql-connector-j' diff --git a/src/main/java/codeview/main/controller/BoardController.java b/src/main/java/codeview/main/controller/BoardController.java new file mode 100644 index 0000000..fe09135 --- /dev/null +++ b/src/main/java/codeview/main/controller/BoardController.java @@ -0,0 +1,20 @@ +package codeview.main.controller; + +import codeview.main.entity.Board; +import codeview.main.service.BoardService; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; + +@Controller +@RequiredArgsConstructor +public class BoardController { + private final BoardService boardService; + + + @PostMapping("/board/write") + public void boardSave(@RequestBody Board board) { + boardService.save(board); + } +} diff --git a/src/main/java/codeview/main/entity/Board.java b/src/main/java/codeview/main/entity/Board.java new file mode 100644 index 0000000..61eefae --- /dev/null +++ b/src/main/java/codeview/main/entity/Board.java @@ -0,0 +1,13 @@ +package codeview.main.entity; + +import jakarta.persistence.*; +import lombok.Getter; +import lombok.Setter; + +@Entity +@Getter @Setter +public class Board { + @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; + @Column + private String title; +} diff --git a/src/main/java/codeview/main/repository/BoardRepository.java b/src/main/java/codeview/main/repository/BoardRepository.java new file mode 100644 index 0000000..262b5b4 --- /dev/null +++ b/src/main/java/codeview/main/repository/BoardRepository.java @@ -0,0 +1,9 @@ +package codeview.main.repository; + +import codeview.main.entity.Board; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface BoardRepository extends JpaRepository { +} diff --git a/src/main/java/codeview/main/service/BoardService.java b/src/main/java/codeview/main/service/BoardService.java new file mode 100644 index 0000000..e5cd38d --- /dev/null +++ b/src/main/java/codeview/main/service/BoardService.java @@ -0,0 +1,15 @@ +package codeview.main.service; + +import codeview.main.entity.Board; +import codeview.main.repository.BoardRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class BoardService { + private final BoardRepository boardRepository; + public void save(Board board) { + boardRepository.save(board); + } +} From 31eb2f62666ea8051b2a78edeb334872a95b60cd Mon Sep 17 00:00:00 2001 From: rogi-rogi Date: Thu, 4 Jul 2024 16:00:44 +0900 Subject: [PATCH 2/4] [feature] implement simple CRUD board --- .../main/controller/BoardController.java | 59 ++++++++++++++++--- .../java/codeview/main/dto/ApiResponse.java | 20 +++++++ .../java/codeview/main/dto/BoardRequest.java | 9 +++ .../java/codeview/main/dto/BoardResponse.java | 17 ++++++ src/main/java/codeview/main/entity/Board.java | 18 +++++- .../java/codeview/main/entity/Comment.java | 10 ++++ .../java/codeview/main/entity/Content.java | 12 ++++ .../codeview/main/service/BoardService.java | 24 +++++++- .../codeview/main/MainApplicationTests.java | 13 ---- 9 files changed, 157 insertions(+), 25 deletions(-) create mode 100644 src/main/java/codeview/main/dto/ApiResponse.java create mode 100644 src/main/java/codeview/main/dto/BoardRequest.java create mode 100644 src/main/java/codeview/main/dto/BoardResponse.java create mode 100644 src/main/java/codeview/main/entity/Comment.java create mode 100644 src/main/java/codeview/main/entity/Content.java delete mode 100644 src/test/java/codeview/main/MainApplicationTests.java diff --git a/src/main/java/codeview/main/controller/BoardController.java b/src/main/java/codeview/main/controller/BoardController.java index fe09135..a08fdbc 100644 --- a/src/main/java/codeview/main/controller/BoardController.java +++ b/src/main/java/codeview/main/controller/BoardController.java @@ -1,20 +1,65 @@ package codeview.main.controller; +import codeview.main.dto.ApiResponse; +import codeview.main.dto.BoardRequest; +import codeview.main.dto.BoardResponse; import codeview.main.entity.Board; import codeview.main.service.BoardService; import lombok.RequiredArgsConstructor; -import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.PostMapping; -import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; -@Controller +import java.util.Optional; + + +@RestController @RequiredArgsConstructor +@RequestMapping("/board") public class BoardController { private final BoardService boardService; + @GetMapping("/{id}") + public ResponseEntity getBoardById(@PathVariable Long id) { + Optional findBoardObj = boardService.findBoardById(id); + if (findBoardObj.isPresent()) { + BoardResponse response = new BoardResponse(findBoardObj.get()); + return ResponseEntity.ok(response); + } else { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); + } + } + @PostMapping("/write") + public ResponseEntity> boardSave(@RequestBody Board board) { + Board saveBoard = boardService.save(board); + BoardResponse boardResponse = new BoardResponse(saveBoard); + ApiResponse response = new ApiResponse<>(HttpStatus.CREATED, "Board Write Success", boardResponse); + return ResponseEntity.status(HttpStatus.CREATED) + .body(response); + } + @PutMapping("/{id}") + public ResponseEntity updateBoard( + @PathVariable Long id, + @RequestBody BoardRequest boardRequest) { + Board updatedBoard = new Board(); + updatedBoard.setTitle(boardRequest.getTitle()); + + try { + Board savedBoard = boardService.updateBoard(id, updatedBoard); + BoardResponse responseDto = new BoardResponse(savedBoard); + return ResponseEntity.ok(responseDto); + } catch (RuntimeException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND).body(null); + } + } - @PostMapping("/board/write") - public void boardSave(@RequestBody Board board) { - boardService.save(board); + @DeleteMapping("/{id}") + public ResponseEntity deleteBoard(@PathVariable Long id) { + try { + boardService.deleteBoard(id); + return ResponseEntity.noContent().build(); + }catch (RuntimeException e){ + return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + } } } diff --git a/src/main/java/codeview/main/dto/ApiResponse.java b/src/main/java/codeview/main/dto/ApiResponse.java new file mode 100644 index 0000000..89677fa --- /dev/null +++ b/src/main/java/codeview/main/dto/ApiResponse.java @@ -0,0 +1,20 @@ +package codeview.main.dto; + + +import lombok.Getter; +import lombok.Setter; +import org.springframework.http.HttpStatus; + +@Getter +@Setter +public class ApiResponse { + private HttpStatus httpStatus; + private String message; + private T data; + + public ApiResponse(HttpStatus httpStatus, String message, T data) { + this.httpStatus = httpStatus; + this.message = message; + this.data = data; + } +} \ No newline at end of file diff --git a/src/main/java/codeview/main/dto/BoardRequest.java b/src/main/java/codeview/main/dto/BoardRequest.java new file mode 100644 index 0000000..1e5d805 --- /dev/null +++ b/src/main/java/codeview/main/dto/BoardRequest.java @@ -0,0 +1,9 @@ +package codeview.main.dto; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class BoardRequest { + private String title; +} \ No newline at end of file diff --git a/src/main/java/codeview/main/dto/BoardResponse.java b/src/main/java/codeview/main/dto/BoardResponse.java new file mode 100644 index 0000000..d539ce9 --- /dev/null +++ b/src/main/java/codeview/main/dto/BoardResponse.java @@ -0,0 +1,17 @@ +package codeview.main.dto; + +import codeview.main.entity.Board; +import lombok.Getter; +import lombok.Setter; + +@Getter +@Setter +public class BoardResponse { + private Long id; + private String title; + + public BoardResponse(Board board) { + this.id = board.getId(); + this.title = board.getTitle(); + } +} diff --git a/src/main/java/codeview/main/entity/Board.java b/src/main/java/codeview/main/entity/Board.java index 61eefae..34c43cb 100644 --- a/src/main/java/codeview/main/entity/Board.java +++ b/src/main/java/codeview/main/entity/Board.java @@ -1,13 +1,25 @@ package codeview.main.entity; import jakarta.persistence.*; -import lombok.Getter; -import lombok.Setter; +import lombok.*; + +import java.util.List; @Entity @Getter @Setter +@NoArgsConstructor public class Board { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column private String title; -} +// +// @Column @OneToOne +// private Content content; +// +// @Column @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL) +// private List commentList; + + public Board(String title) { + this.title = title; + } +} \ No newline at end of file diff --git a/src/main/java/codeview/main/entity/Comment.java b/src/main/java/codeview/main/entity/Comment.java new file mode 100644 index 0000000..f572b03 --- /dev/null +++ b/src/main/java/codeview/main/entity/Comment.java @@ -0,0 +1,10 @@ +package codeview.main.entity; + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity +public class Comment { + @Id + private Long id; +} diff --git a/src/main/java/codeview/main/entity/Content.java b/src/main/java/codeview/main/entity/Content.java new file mode 100644 index 0000000..c07afcb --- /dev/null +++ b/src/main/java/codeview/main/entity/Content.java @@ -0,0 +1,12 @@ +package codeview.main.entity; + + +import jakarta.persistence.Entity; +import jakarta.persistence.Id; + +@Entity +public class Content { + @Id + private Long id; + +} diff --git a/src/main/java/codeview/main/service/BoardService.java b/src/main/java/codeview/main/service/BoardService.java index e5cd38d..cf0246e 100644 --- a/src/main/java/codeview/main/service/BoardService.java +++ b/src/main/java/codeview/main/service/BoardService.java @@ -5,11 +5,31 @@ import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.Optional; + @Service @RequiredArgsConstructor public class BoardService { private final BoardRepository boardRepository; - public void save(Board board) { - boardRepository.save(board); + public Board save(Board board) { + return boardRepository.save(board); + } + public Optional findBoardById(Long id) { + return boardRepository.findById(id); + } + public Board updateBoard(Long id, Board updateBoard) { + return boardRepository.findById(id) + .map(board -> { + board.setTitle(updateBoard.getTitle()); + return board; + }) + .orElseThrow(() -> new RuntimeException("Board not found")); + } + public void deleteBoard(Long id) { + if (boardRepository.existsById(id)) { + boardRepository.deleteById(id); + } else { + throw new RuntimeException("Board not found"); + } } } diff --git a/src/test/java/codeview/main/MainApplicationTests.java b/src/test/java/codeview/main/MainApplicationTests.java deleted file mode 100644 index 797cc27..0000000 --- a/src/test/java/codeview/main/MainApplicationTests.java +++ /dev/null @@ -1,13 +0,0 @@ -package codeview.main; - -import org.junit.jupiter.api.Test; -import org.springframework.boot.test.context.SpringBootTest; - -@SpringBootTest -class MainApplicationTests { - - @Test - void contextLoads() { - } - -} From d0f979e90dfc5df3b4b9505d0f3ab3ceaa23b867 Mon Sep 17 00:00:00 2001 From: rogi-rogi Date: Thu, 4 Jul 2024 19:35:55 +0900 Subject: [PATCH 3/4] set project folder name --- settings.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/settings.gradle b/settings.gradle index 7bef29d..85b26a6 100644 --- a/settings.gradle +++ b/settings.gradle @@ -1 +1 @@ -rootProject.name = 'main' +rootProject.name = 'code-view' From 75796815149b68fee978b1fa9013de4a07026c0a Mon Sep 17 00:00:00 2001 From: rogi-rogi Date: Fri, 23 Aug 2024 17:30:54 +0900 Subject: [PATCH 4/4] =?UTF-8?q?init=20-=20=EC=84=9C=EB=B2=84=20=EC=A0=90?= =?UTF-8?q?=EA=B2=80=20=EC=BB=A8=ED=8A=B8=EB=A1=A4=EB=9F=AC=20=EA=B0=9C?= =?UTF-8?q?=EB=B0=9C=20-=20Dockerfile=20=EC=9E=91=EC=84=B1=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20-=20CICD.yml=20=EC=9E=91=EC=84=B1=20=EC=99=84?= =?UTF-8?q?=EB=A3=8C=20->=20=EB=B0=B0=ED=8F=AC=20=EA=B0=80=EB=8A=A5=20-=20?= =?UTF-8?q?gitignore=20=EC=84=A4=EC=A0=95=20=EC=99=84=EB=A3=8C?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .github/workflows/CICD.yml | 140 ++++++++++++++++++ .gitignore | 2 + Dockerfile | 6 + .../ServerHealthCheckController.java | 38 +++++ src/main/resources/application.yml | 71 +++++++++ 5 files changed, 257 insertions(+) create mode 100644 .github/workflows/CICD.yml create mode 100644 Dockerfile create mode 100644 src/main/java/codeview/main/controller/ServerHealthCheckController.java create mode 100644 src/main/resources/application.yml diff --git a/.github/workflows/CICD.yml b/.github/workflows/CICD.yml new file mode 100644 index 0000000..c3aa6bd --- /dev/null +++ b/.github/workflows/CICD.yml @@ -0,0 +1,140 @@ +name: CICD + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - name: Install JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + + - name: Create application-secret-deploy.yml + run: | + echo "Decoding SECRET_FILE secret and create application-secret-deploy.yml" + echo "${{ secrets.SECRET_FILE }}" | base64 -d > ./src/main/resources/application-secret-deploy.yml + + + - name: Build with Gradle + run: | + chmod 777 ./gradlew + ./gradlew clean assemble -x test + + - name: Login to DockerHub + if: github.event_name == 'push' + uses: docker/login-action@v1 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_TOKEN }} + + - name: Build Docker + if: github.event_name == 'push' + run: docker build --platform linux/amd64 -t ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO_NAME }} . + + - name: Push Docker + if: github.event_name == 'push' + run: docker push ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO_NAME }}:latest + + deploy: + needs: build + if: github.event_name == 'push' + runs-on: ubuntu-latest + steps: + - name: Set target IP + run: | + STATUS=$(curl -o /dev/null -w "%{http_code}" "http://${{ secrets.LIVE_SERVER_IP }}/env") + echo $STATUS + if [ $STATUS = 200 ]; then + CURRENT_UPSTREAM=$(curl -s "http://${{ secrets.LIVE_SERVER_IP }}/env") + else + CURRENT_UPSTREAM=green + fi + echo CURRENT_UPSTREAM=$CURRENT_UPSTREAM >> $GITHUB_ENV + if [ $CURRENT_UPSTREAM = blue ]; then + echo "CURRENT_PORT=8080" >> $GITHUB_ENV + echo "STOPPED_PORT=8081" >> $GITHUB_ENV + echo "TARGET_UPSTREAM=green" >> $GITHUB_ENV + elif [ $CURRENT_UPSTREAM = green ]; then + echo "CURRENT_PORT=8081" >> $GITHUB_ENV + echo "STOPPED_PORT=8080" >> $GITHUB_ENV + echo "TARGET_UPSTREAM=blue" >> $GITHUB_ENV + else + echo "error" + exit 1 + fi + + - name: Login Server + uses: appleboy/ssh-action@master + with: + username: ${{ secrets.SERVER_USER_NAME }} + host: ${{ secrets.LIVE_SERVER_IP }} + key: ${{ secrets.SSH_KEY }} + script_stop: true + + - name: Docker compose + uses: appleboy/ssh-action@master + with: + username: ${{ secrets.SERVER_USER_NAME }} + host: ${{ secrets.LIVE_SERVER_IP }} + key: ${{ secrets.SSH_KEY }} + script_stop: true + script: | + sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/${{ secrets.DOCKERHUB_REPO_NAME }}:latest + sudo docker-compose -f docker-compose-${{env.TARGET_UPSTREAM}}.yml up -d + + - name: Check deploy server URL + uses: jtalk/url-health-check-action@v3 + with: + url: http://${{ secrets.LIVE_SERVER_IP }}:${{env.STOPPED_PORT}}/env + max-attempts: 5 + retry-delay: 10s + + - name: Change nginx upstream + uses: appleboy/ssh-action@master + with: + username: ${{ secrets.SERVER_USER_NAME }} + host: ${{ secrets.LIVE_SERVER_IP }} + key: ${{ secrets.SSH_KEY }} + script_stop: true + script: | + sudo docker exec -i nginxserver bash -c 'echo "set \$service_url ${{ env.TARGET_UPSTREAM }};" > /etc/nginx/conf.d/service-env.inc && nginx -s reload' + + - name: Stop current server + uses: appleboy/ssh-action@master + with: + username: ${{ secrets.SERVER_USER_NAME }} + host: ${{ secrets.LIVE_SERVER_IP }} + key: ${{ secrets.SSH_KEY }} + script_stop: true + script: | + if [ $(sudo docker ps -a -q -f name=${{env.CURRENT_UPSTREAM}}) ]; then + sudo docker stop ${{env.CURRENT_UPSTREAM}} + sudo docker rm ${{env.CURRENT_UPSTREAM}} + else + echo "Container ${{env.CURRENT_UPSTREAM}} does not exist, skipping stop and remove steps." + fi + + - name: Delete old docker images + uses: appleboy/ssh-action@master + with: + username: ${{ secrets.SERVER_USER_NAME }} + host: ${{ secrets.LIVE_SERVER_IP }} + key: ${{ secrets.SSH_KEY }} + script: | + dangling_images=$(sudo docker images -f "dangling=true" -q) + if [ ! -z "$dangling_images" ]; then + sudo docker rmi $dangling_images + else + echo "No dangling images found" + fi diff --git a/.gitignore b/.gitignore index 4847fa6..4cc7f9f 100644 --- a/.gitignore +++ b/.gitignore @@ -4,6 +4,8 @@ build/ !gradle/wrapper/gradle-wrapper.jar !**/src/main/**/build/ !**/src/test/**/build/ +!**/src/main/resoureces/application.yml +application-*.yml ### STS ### .apt_generated diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..6b5ef88 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM amazoncorretto:17-alpine-jdk +ARG JAR_FILE=build/libs/*.jar +ARG PROFILES +ARG ENV +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java", "-Dspring.profiles.active=${PROFILES}", "-Dserver.env=${ENV}", "-jar", "app.jar"] \ No newline at end of file diff --git a/src/main/java/codeview/main/controller/ServerHealthCheckController.java b/src/main/java/codeview/main/controller/ServerHealthCheckController.java new file mode 100644 index 0000000..e85962b --- /dev/null +++ b/src/main/java/codeview/main/controller/ServerHealthCheckController.java @@ -0,0 +1,38 @@ +package codeview.main.controller; + +import org.springframework.beans.factory.annotation.Value; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.RestController; + +import java.util.Map; +import java.util.TreeMap; + +@RestController +public class ServerHealthCheckController { + @Value("${server.env}") + private String env; + @Value("${server.port}") + private String serverPort; + @Value("${server.serverAddress}") + private String serverAddress; + @Value("${serverName}") + private String serverName; + + @GetMapping("/hc") + public ResponseEntity healthCheck() { + Map responseData = new TreeMap<>(); + responseData.put("serverName", serverName); + responseData.put("serverAddress", serverAddress); + responseData.put("serverPort", serverPort); + responseData.put("env", env); + return ResponseEntity.ok(responseData); + } + + @GetMapping("/env") + @ResponseBody + public ResponseEntity getEnv() { + return ResponseEntity.ok().body(env); + } +} \ No newline at end of file diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml new file mode 100644 index 0000000..4bf15cc --- /dev/null +++ b/src/main/resources/application.yml @@ -0,0 +1,71 @@ +spring: + profiles: + active: local + group: + local: local, common, secret-dev + blue: blue, common, secret-deploy + green: green, common, secret-deploy + +server: + env: blue + +--- + +spring: + config: + activate: + on-profile: local + +server: + port: 8080 + serverAddress: localhost + +serverName: local_server + +--- + +spring: + config: + activate: + on-profile: blue + +server: + port: 8080 + serverAddress: 34.64.190.54 + +serverName: blue_server + +--- + +spring: + config: + activate: + on-profile: green + +server: + port: 8081 + serverAddress: 34.64.190.54 + +serverName: green_server + +--- + +spring: + config: + activate: + on-profile: common + + application: + name: codeview + jpa: + hibernate: + ddl-auto: create + show_sql: true + properties: + hibernate: + format_sql: true + dialect: org.hibernate.dialect.MySQLDialect + +logging: + level: + org.springframework.security: DEBUG \ No newline at end of file