diff --git a/.github/workflows/deployment.yml b/.github/workflows/deployment.yml new file mode 100644 index 0000000..bd84acb --- /dev/null +++ b/.github/workflows/deployment.yml @@ -0,0 +1,76 @@ +# 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: Deployment + +on: + workflow_dispatch: + push: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + - name: Build with Gradle + uses: gradle/gradle-build-action@67421db6bd0bf253fb4bd25b31ebb98943c375e1 + with: + arguments: build + - uses: actions/upload-artifact@v3 + with: + name: jar + path: build/libs + + send-jar: + needs: build + runs-on: ubuntu-latest + steps: + - name: Download jar + uses: actions/download-artifact@v3 + with: + name: jar + - name: Send jar to remote server + uses: appleboy/scp-action@master + with: + host: 34.64.186.225 + username: lth1283910 + source: "real_coding_server-0.0.1-SNAPSHOT.jar" + target: "/home/lth1283910" + key: ${{ secrets.PRIVATE_KEY }} + + run-app: + needs: send-jar + runs-on: ubuntu-latest + steps: + - name: Checkout + uses: actions/checkout@v3 + - name: Move deploy.sh + uses: appleboy/scp-action@master + with: + host: 34.64.186.225 + username: lth1283910 + source: "deploy.sh" + target: "/home/lth1283910" + key: ${{ secrets.PRIVATE_KEY }} + - name: Execute script + uses: appleboy/ssh-action@master + with: + username: lth1283910 + host: 34.64.186.225 + key: ${{ secrets.PRIVATE_KEY }} + script_stop: true + script: cd /home/lth1283910 && chmod +x deploy.sh && ./deploy.sh diff --git a/deploy.sh b/deploy.sh new file mode 100644 index 0000000..f2d9e47 --- /dev/null +++ b/deploy.sh @@ -0,0 +1,13 @@ +#!/bin/bash + +SERVER_HOME=$(pwd) +APPLICATION=real_coding_server-0.0.1-SNAPSHOT.jar + +if [ -f $SERVER_HOME/application.pid ];then + kill -9 $(cat $SERVER_HOME/application.pid) + rm $SERVER_HOME/application.pid +fi + +sleep 1s + +nohup java -jar $APPLICATION >> spring.out 2>&1 & echo $! > application.pid diff --git a/devblog b/devblog new file mode 160000 index 0000000..dee1706 --- /dev/null +++ b/devblog @@ -0,0 +1 @@ +Subproject commit dee1706626f6b0c10d74448f1ae521a840e1a110 diff --git a/post/build.gradle b/post/build.gradle new file mode 100644 index 0000000..7e08989 --- /dev/null +++ b/post/build.gradle @@ -0,0 +1,16 @@ +bootJar { + enabled = true +} +dependencies { + implementation 'org.springframework.boot:spring-boot-starter' + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2' + + // db + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' + implementation 'com.h2database:h2' + + // Resilience4j + implementation 'io.github.resilience4j:resilience4j-spring-boot3:2.0.2' + implementation 'org.springframework.boot:spring-boot-starter-aop' +} \ No newline at end of file diff --git a/post/src/main/java/com/cnu/post/Advertisement.java b/post/src/main/java/com/cnu/post/Advertisement.java new file mode 100644 index 0000000..43dfef7 --- /dev/null +++ b/post/src/main/java/com/cnu/post/Advertisement.java @@ -0,0 +1,9 @@ +package com.cnu.post; + +public record Advertisement( + String title, + String description, + String imageUrl, + String siteUrl +) { +} diff --git a/post/src/main/java/com/cnu/post/RealCodingServerApplication.java b/post/src/main/java/com/cnu/post/RealCodingServerApplication.java new file mode 100644 index 0000000..a168c98 --- /dev/null +++ b/post/src/main/java/com/cnu/post/RealCodingServerApplication.java @@ -0,0 +1,15 @@ +package com.cnu.post; + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; + +@EnableJpaAuditing +@SpringBootApplication +public class RealCodingServerApplication { + + public static void main(String[] args) { + SpringApplication.run(RealCodingServerApplication.class, args); + } + +} diff --git a/post/src/main/java/com/cnu/post/client/AdvertisementClient.java b/post/src/main/java/com/cnu/post/client/AdvertisementClient.java new file mode 100644 index 0000000..84d1bf5 --- /dev/null +++ b/post/src/main/java/com/cnu/post/client/AdvertisementClient.java @@ -0,0 +1,30 @@ +package com.cnu.post.client; + +import com.cnu.post.Advertisement; +import io.github.resilience4j.circuitbreaker.CallNotPermittedException; +import io.github.resilience4j.circuitbreaker.annotation.CircuitBreaker; +import org.springframework.stereotype.Component; +import org.springframework.web.client.RestTemplate; + +@Component +public class AdvertisementClient { + RestTemplate restTemplate = new RestTemplate(); + private static final Advertisement AD_FALLBACK = new Advertisement( + "Devblog로 개발 블로그를 만들어보자", + "Devblog는 개발자들을 위한 블로그 플랫폼입니다. Devblog로 개발 블로그를 만들어보세요!", + "https://devblog.com/images/og-image.png", + "https://devblog.com" + ); + + @CircuitBreaker(name = "ad", fallbackMethod = "fallback") + public Advertisement getAd() { + return restTemplate.getForObject( + "http://localhost:9090/ads", + Advertisement.class + ); + } + + private Advertisement fallback(CallNotPermittedException e) { + return AD_FALLBACK; + } +} diff --git a/post/src/main/java/com/cnu/post/controller/PostController.java b/post/src/main/java/com/cnu/post/controller/PostController.java new file mode 100644 index 0000000..ad8bbe1 --- /dev/null +++ b/post/src/main/java/com/cnu/post/controller/PostController.java @@ -0,0 +1,45 @@ +package com.cnu.post.controller; + +import com.cnu.post.entity.Post; +import com.cnu.post.model.request.PostRequest; +import com.cnu.post.model.response.PostResponse; +import com.cnu.post.service.PostService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/posts") +@RequiredArgsConstructor +public class PostController { + private final PostService postService; + + @PostMapping + public ResponseEntity createPost(@RequestBody PostRequest postRequest) { + return ResponseEntity.ok(postService.createPost(postRequest)); + } + + @GetMapping + public ResponseEntity> getPosts() { + return ResponseEntity.ok(postService.getPosts()); + } + @GetMapping("/{postId}") + public ResponseEntity getPost(@PathVariable("postId") Integer postId) { + return ResponseEntity.ok(postService.getPost(postId).orElse(null)); + } + + @PutMapping("/{postId}") + public ResponseEntity updatePost(@PathVariable("postId") Integer postId, + @RequestBody PostRequest postRequest) { + return ResponseEntity.ok(postService.updatePost(postId, postRequest).orElse(null)); + } + + @DeleteMapping("/{postId}") + public ResponseEntity deletePost(@PathVariable("postId") Integer postId) { + postService.deletePost(postId); + + return ResponseEntity.noContent().build(); + } +} diff --git a/post/src/main/java/com/cnu/post/entity/BaseEntity.java b/post/src/main/java/com/cnu/post/entity/BaseEntity.java new file mode 100644 index 0000000..db34549 --- /dev/null +++ b/post/src/main/java/com/cnu/post/entity/BaseEntity.java @@ -0,0 +1,24 @@ +package com.cnu.post.entity; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import java.time.LocalDateTime; + +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public class BaseEntity { + @Column + @CreatedDate + private LocalDateTime createdAt; + + @Column + @LastModifiedDate + private LocalDateTime updatedAt; +} diff --git a/post/src/main/java/com/cnu/post/entity/Post.java b/post/src/main/java/com/cnu/post/entity/Post.java new file mode 100644 index 0000000..4d36c9c --- /dev/null +++ b/post/src/main/java/com/cnu/post/entity/Post.java @@ -0,0 +1,33 @@ +package com.cnu.post.entity; + +import com.cnu.post.model.type.Tag; +import jakarta.persistence.*; +import lombok.*; + +@Getter +@Entity(name = "posts") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Post extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Column + @Setter + private String title; + + @Column + @Setter + private String contents; + + @Enumerated(EnumType.STRING) + @Setter + private Tag tag; + + @Builder + public Post(String title, String contents, Tag tag) { + this.title = title; + this.contents = contents; + this.tag = tag; + } +} diff --git a/post/src/main/java/com/cnu/post/entity/Project.java b/post/src/main/java/com/cnu/post/entity/Project.java new file mode 100644 index 0000000..918cfb6 --- /dev/null +++ b/post/src/main/java/com/cnu/post/entity/Project.java @@ -0,0 +1,54 @@ +package com.cnu.post.entity; + +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; + +@Getter +@Entity(name = "projects") +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Project extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @Column + @Setter + private String title; + + @Column + @Setter + private String summary; + + @Column + @Setter + private String description; + + @Column + @Setter + private LocalDateTime startDate; + + @Column + @Setter + private LocalDateTime endDate; + + @Column + @Setter + private Boolean isInProgress; + + @Builder + private Project(String title, + String summary, + String description, + LocalDateTime startDate, + LocalDateTime endDate, + Boolean isInProgress) { + this.title = title; + this.summary = summary; + this.description = description; + this.startDate = startDate; + this.endDate = endDate; + this.isInProgress = isInProgress; + } +} diff --git a/post/src/main/java/com/cnu/post/model/request/PostRequest.java b/post/src/main/java/com/cnu/post/model/request/PostRequest.java new file mode 100644 index 0000000..c738cd3 --- /dev/null +++ b/post/src/main/java/com/cnu/post/model/request/PostRequest.java @@ -0,0 +1,24 @@ +package com.cnu.post.model.request; + + +import com.cnu.post.entity.Post; +import com.cnu.post.model.type.Tag; +import jakarta.validation.Valid; +import lombok.Getter; + +@Getter +public class PostRequest { + @Valid + private String title; + private String contents; + + private Tag tag; + + public Post toEntity() { + return Post.builder() + .title(title) + .contents(contents) + .tag(tag) + .build(); + } +} diff --git a/post/src/main/java/com/cnu/post/model/request/ProjectRequest.java b/post/src/main/java/com/cnu/post/model/request/ProjectRequest.java new file mode 100644 index 0000000..697ec4f --- /dev/null +++ b/post/src/main/java/com/cnu/post/model/request/ProjectRequest.java @@ -0,0 +1,32 @@ +package com.cnu.post.model.request; + +import com.cnu.post.entity.Project; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +public class ProjectRequest { + private String title; + + private String summary; + + private String description; + + private LocalDateTime startDate; + + private LocalDateTime endDate; + + private Boolean isInProgress; + + public Project toEntity() { + return Project.builder() + .title(title) + .summary(summary) + .description(description) + .startDate(startDate) + .endDate(endDate) + .isInProgress(isInProgress) + .build(); + } +} diff --git a/post/src/main/java/com/cnu/post/model/response/PostResponse.java b/post/src/main/java/com/cnu/post/model/response/PostResponse.java new file mode 100644 index 0000000..1ee838a --- /dev/null +++ b/post/src/main/java/com/cnu/post/model/response/PostResponse.java @@ -0,0 +1,10 @@ +package com.cnu.post.model.response; + +import com.cnu.post.Advertisement; +import com.cnu.post.entity.Post; + +public record PostResponse( + Post post, + Advertisement advertisement +) { +} diff --git a/post/src/main/java/com/cnu/post/model/type/Tag.java b/post/src/main/java/com/cnu/post/model/type/Tag.java new file mode 100644 index 0000000..8d48a79 --- /dev/null +++ b/post/src/main/java/com/cnu/post/model/type/Tag.java @@ -0,0 +1,8 @@ +package com.cnu.post.model.type; + +public enum Tag { + JAVA, + SPRINGBOOT, + REACT, + DB +} diff --git a/post/src/main/java/com/cnu/post/repository/PostRepository.java b/post/src/main/java/com/cnu/post/repository/PostRepository.java new file mode 100644 index 0000000..b95d0b6 --- /dev/null +++ b/post/src/main/java/com/cnu/post/repository/PostRepository.java @@ -0,0 +1,13 @@ +package com.cnu.post.repository; + +import com.cnu.post.entity.Post; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface PostRepository extends JpaRepository { +// Post save(Post post); +// List findAll(); +// Optional findById(Integer postId); +// void delete(Post post); +} diff --git a/post/src/main/java/com/cnu/post/service/PostService.java b/post/src/main/java/com/cnu/post/service/PostService.java new file mode 100644 index 0000000..93cbee7 --- /dev/null +++ b/post/src/main/java/com/cnu/post/service/PostService.java @@ -0,0 +1,49 @@ +package com.cnu.post.service; + +import com.cnu.post.Advertisement; +import com.cnu.post.client.AdvertisementClient; +import com.cnu.post.entity.Post; +import com.cnu.post.model.request.PostRequest; +import com.cnu.post.model.response.PostResponse; +import com.cnu.post.repository.PostRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class PostService { + private final PostRepository postRepository; + private final AdvertisementClient advertisementClient; + + public Post createPost(PostRequest postRequest) { + return postRepository.save(postRequest.toEntity()); + } + + public List getPosts() { + return postRepository.findAll(); + } + + public Optional getPost(Integer postId) { + return postRepository.findById(postId) + .map(post -> new PostResponse(post, advertisementClient.getAd())); + } + + public Optional updatePost(Integer postId, PostRequest postRequest) { + return postRepository.findById(postId) + .map(post -> { + post.setTitle(postRequest.getTitle()); + post.setContents(postRequest.getContents()); + post.setTag(postRequest.getTag()); + return postRepository.save(post); + }); + } + + public void deletePost(Integer postId) { + postRepository.findById(postId) + .ifPresent(postRepository::delete); + } +} diff --git a/post/src/main/resources/application.yml b/post/src/main/resources/application.yml new file mode 100644 index 0000000..05ee92a --- /dev/null +++ b/post/src/main/resources/application.yml @@ -0,0 +1,32 @@ +spring: + # H2 Setting Info (H2 Console? ???? ?? ???? ??) + h2: + console: + enabled: true # H2 Console? ???? ?? (H2 Console? H2 Database? UI? ????? ??) + path: /h2-console # H2 Console? Path + # Database Setting Info (Database? H2? ???? ?? H2?? ?? ??) + datasource: + driver-class-name: org.h2.Driver # Database? H2? ?????. + url: jdbc:h2:mem:devblog # H2 ?? ?? + username: sa # H2 ?? ? ??? username ?? (??? ??? ??) + password: # H2 ?? ? ??? password ?? (??? ??? ??) + + jpa: + hibernate: + ddl-auto: create # ??????? ??? ? ??????? ????? ?? ??? ?? + properties: + hibernate: + format_sql: true # ???? query? ??? + +resilience4j: + circuitbreaker: + instances: + ad: + slidingWindowType: COUNT_BASED + slidingWindowSize: 5 + failureRateThreshold: 50 # 에러 비율로 해당 값 이상으로 에러 발생시 서킷이 open + slowCallRateThreshold: 100 + minimumNumberOfCalls: 3 # circuitbreaker가 에러비율 또는 slow call 비율을 계산하기 전에 요구되는 최소 호출 횟수 + +logging.level: + org.hibernate.SQL: debug \ No newline at end of file diff --git a/src/main/java/com/cnu/real_coding_server/RealCodingServerApplication.java b/src/main/java/com/cnu/real_coding_server/RealCodingServerApplication.java index fc49dd5..10f3d07 100644 --- a/src/main/java/com/cnu/real_coding_server/RealCodingServerApplication.java +++ b/src/main/java/com/cnu/real_coding_server/RealCodingServerApplication.java @@ -12,4 +12,4 @@ public static void main(String[] args) { SpringApplication.run(RealCodingServerApplication.class, args); } -} +} \ No newline at end of file diff --git a/src/main/java/com/cnu/real_coding_server/controller/PostController.java b/src/main/java/com/cnu/real_coding_server/controller/PostController.java index 8d7b907..3dfab51 100644 --- a/src/main/java/com/cnu/real_coding_server/controller/PostController.java +++ b/src/main/java/com/cnu/real_coding_server/controller/PostController.java @@ -1,7 +1,43 @@ package com.cnu.real_coding_server.controller; -import org.springframework.web.bind.annotation.RestController; +import com.cnu.real_coding_server.entity.Post; +import com.cnu.real_coding_server.model.request.PostRequest; +import com.cnu.real_coding_server.repository.PostRepository; +import com.cnu.real_coding_server.service.PostService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Service; +import org.springframework.web.bind.annotation.*; + +import java.util.List; @RestController +@RequiredArgsConstructor +@RequestMapping("/posts") public class PostController { -} + private final PostService postService; + @PostMapping + public ResponseEntity createPost(@RequestBody PostRequest postRequest){ + return ResponseEntity.ok(postService.createPost(postRequest)); + } + @GetMapping + public ResponseEntity> getPosts() { + return ResponseEntity.ok(postService.getPosts()); + } + @GetMapping("/{postId}") // 받아올 Id를 postId로 받아온다. + public ResponseEntity getPost(@PathVariable("postId") Integer postId){ + return ResponseEntity.ok(postService.getPost(postId).orElse(null)); + } + + public ResponseEntity updatePost(@PathVariable("postId") Integer postId, @RequestBody PostRequest postRequest){ + return ResponseEntity.ok(postService.updatePost(postId, postRequest).orElse(null)); + } + + @DeleteMapping("/{postId}") + public ResponseEntity deletePost(@PathVariable("postId")Integer postId){ + postService.deletePost(postId); + return ResponseEntity.noContent().build(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/cnu/real_coding_server/entity/Post.java b/src/main/java/com/cnu/real_coding_server/entity/Post.java index 5cfa06a..bb99748 100644 --- a/src/main/java/com/cnu/real_coding_server/entity/Post.java +++ b/src/main/java/com/cnu/real_coding_server/entity/Post.java @@ -20,6 +20,7 @@ public class Post extends BaseEntity { @Setter private String contents; + @Setter @Enumerated(EnumType.STRING) private Tag tag; diff --git a/src/main/java/com/cnu/real_coding_server/entity/Project.java b/src/main/java/com/cnu/real_coding_server/entity/Project.java index 07203b9..953c358 100644 --- a/src/main/java/com/cnu/real_coding_server/entity/Project.java +++ b/src/main/java/com/cnu/real_coding_server/entity/Project.java @@ -51,4 +51,4 @@ private Project(String title, this.endDate = endDate; this.isInProgress = isInProgress; } -} +} \ No newline at end of file diff --git a/src/main/java/com/cnu/real_coding_server/model/request/ProjectRequest.java b/src/main/java/com/cnu/real_coding_server/model/request/ProjectRequest.java index 2e80181..61f483b 100644 --- a/src/main/java/com/cnu/real_coding_server/model/request/ProjectRequest.java +++ b/src/main/java/com/cnu/real_coding_server/model/request/ProjectRequest.java @@ -29,4 +29,4 @@ public Project toEntity() { .isInProgress(isInProgress) .build(); } -} +} \ No newline at end of file diff --git a/src/main/java/com/cnu/real_coding_server/repository/PostRepository.java b/src/main/java/com/cnu/real_coding_server/repository/PostRepository.java index 0366c87..74d76fb 100644 --- a/src/main/java/com/cnu/real_coding_server/repository/PostRepository.java +++ b/src/main/java/com/cnu/real_coding_server/repository/PostRepository.java @@ -10,4 +10,4 @@ public interface PostRepository extends JpaRepository { // List findAll(); // Optional findById(Integer postId); // void delete(Post post); -} +} \ No newline at end of file diff --git a/src/main/java/com/cnu/real_coding_server/service/PostService.java b/src/main/java/com/cnu/real_coding_server/service/PostService.java index f79fbb8..f6eed14 100644 --- a/src/main/java/com/cnu/real_coding_server/service/PostService.java +++ b/src/main/java/com/cnu/real_coding_server/service/PostService.java @@ -1,7 +1,45 @@ package com.cnu.real_coding_server.service; +import com.cnu.real_coding_server.entity.Post; +import com.cnu.real_coding_server.model.request.PostRequest; +import com.cnu.real_coding_server.repository.PostRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; import org.springframework.stereotype.Service; +import java.util.List; +import java.util.Optional; + @Service +@RequiredArgsConstructor public class PostService { + private final PostRepository postRepository; + public Post createPost(PostRequest postRequest){ + return postRepository.save(postRequest.toEntity()); + } + + public List getPosts(){ + return postRepository.findAll(); + } + + public Optional getPost(Integer postId){ + return postRepository.findById(postId); + } + + // 특정 포스트를 수정하려면 이미 ID를 알고 있는 상황이어야 한다. + public Optional updatePost(Integer postId, PostRequest postRequest){ + return postRepository.findById(postId).map(post -> { + post.setTitle(postRequest.getTitle()); + post.setContents(postRequest.getContents()); + post.setTag(postRequest.getTag()); + return postRepository.save(post); + }); + } + //이제 수정을 받을 곳이 필요하다. + + public void deletePost(Integer postId){ + postRepository.findById(postId).ifPresent(postRepository::delete); // 안에 존재한다면 함수를 실행하라 + + } + } diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 9f1e564..323112d 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -19,4 +19,6 @@ spring: format_sql: true # ???? query? ??? logging.level: - org.hibernate.SQL: debug \ No newline at end of file + org.hibernate.SQL: debug +server: + port: 9090