diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/java/dev/ptnr/frontalobebackend/controller/BlogApiController.java b/src/main/java/dev/ptnr/frontalobebackend/controller/BlogApiController.java new file mode 100644 index 0000000..c14502f --- /dev/null +++ b/src/main/java/dev/ptnr/frontalobebackend/controller/BlogApiController.java @@ -0,0 +1,23 @@ +package dev.ptnr.frontalobebackend.controller; + +import dev.ptnr.frontalobebackend.domain.Article; +import dev.ptnr.frontalobebackend.dto.AddArticleRequest; +import dev.ptnr.frontalobebackend.service.BlogService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RequiredArgsConstructor +@RestController // HTTP Response Body의 객체 데이터를 JSON 형식으로 반환하는 컨트롤러 +public class BlogApiController { + private final BlogService blogService; + + @PostMapping("/api/articles") + public ResponseEntity
addArticle(@RequestBody AddArticleRequest request) { + Article savedArticle = blogService.save(request); + return ResponseEntity.status(HttpStatus.CREATED).body(savedArticle); + } +} diff --git a/src/main/java/dev/ptnr/frontalobebackend/domain/Article.java b/src/main/java/dev/ptnr/frontalobebackend/domain/Article.java new file mode 100644 index 0000000..52be7fd --- /dev/null +++ b/src/main/java/dev/ptnr/frontalobebackend/domain/Article.java @@ -0,0 +1,29 @@ +package dev.ptnr.frontalobebackend.domain; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Article { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + @Column(name = "id", updatable = false) + private Long id; + + @Column(name = "title", nullable = false) + private String title; + + @Column(name = "content", nullable = false) + private String content; + + @Builder + public Article(String title, String content) { + this.title = title; + this.content = content; + } +} diff --git a/src/main/java/dev/ptnr/frontalobebackend/dto/AddArticleRequest.java b/src/main/java/dev/ptnr/frontalobebackend/dto/AddArticleRequest.java new file mode 100644 index 0000000..d762d02 --- /dev/null +++ b/src/main/java/dev/ptnr/frontalobebackend/dto/AddArticleRequest.java @@ -0,0 +1,18 @@ +package dev.ptnr.frontalobebackend.dto; + +import dev.ptnr.frontalobebackend.domain.Article; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@NoArgsConstructor +@AllArgsConstructor +@Getter +public class AddArticleRequest { + private String title; + private String content; + + public Article toEntity() { + return Article.builder().title(title).content(content).build(); + } +} diff --git a/src/main/java/dev/ptnr/frontalobebackend/dto/ArticleResponse.java b/src/main/java/dev/ptnr/frontalobebackend/dto/ArticleResponse.java new file mode 100644 index 0000000..f70b104 --- /dev/null +++ b/src/main/java/dev/ptnr/frontalobebackend/dto/ArticleResponse.java @@ -0,0 +1,15 @@ +package dev.ptnr.frontalobebackend.dto; + +import dev.ptnr.frontalobebackend.domain.Article; +import lombok.Getter; + +@Getter +public class ArticleResponse { + private final String title; + private final String content; + + public ArticleResponse(Article article) { + this.title = article.getTitle(); + this.content = article.getContent(); + } +} diff --git a/src/main/java/dev/ptnr/frontalobebackend/repository/BlogRepository.java b/src/main/java/dev/ptnr/frontalobebackend/repository/BlogRepository.java new file mode 100644 index 0000000..74fbe9b --- /dev/null +++ b/src/main/java/dev/ptnr/frontalobebackend/repository/BlogRepository.java @@ -0,0 +1,8 @@ +package dev.ptnr.frontalobebackend.repository; + +import dev.ptnr.frontalobebackend.domain.Article; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface BlogRepository extends JpaRepository { + +} diff --git a/src/main/java/dev/ptnr/frontalobebackend/service/BlogService.java b/src/main/java/dev/ptnr/frontalobebackend/service/BlogService.java new file mode 100644 index 0000000..f5b032f --- /dev/null +++ b/src/main/java/dev/ptnr/frontalobebackend/service/BlogService.java @@ -0,0 +1,24 @@ +package dev.ptnr.frontalobebackend.service; + +import dev.ptnr.frontalobebackend.domain.Article; +import dev.ptnr.frontalobebackend.dto.AddArticleRequest; +import dev.ptnr.frontalobebackend.repository.BlogRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; + +@RequiredArgsConstructor // final or @NotNull이 붙은 생성자를 만들어 줌 +@Service // BlogService as Spring Bean +public class BlogService { + private final BlogRepository blogRepository; + + // Add Blog Article + public Article save(AddArticleRequest request) { + return blogRepository.save(request.toEntity()); + } + + public List
findAll() { + return blogRepository.findAll(); + } +} diff --git a/src/test/java/dev/ptnr/frontalobebackend/controller/BlogApiControllerTest.java b/src/test/java/dev/ptnr/frontalobebackend/controller/BlogApiControllerTest.java new file mode 100644 index 0000000..380fe36 --- /dev/null +++ b/src/test/java/dev/ptnr/frontalobebackend/controller/BlogApiControllerTest.java @@ -0,0 +1,72 @@ +package dev.ptnr.frontalobebackend.controller; + +import com.fasterxml.jackson.databind.ObjectMapper; +import dev.ptnr.frontalobebackend.domain.Article; +import dev.ptnr.frontalobebackend.dto.AddArticleRequest; +import dev.ptnr.frontalobebackend.repository.BlogRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; +import org.springframework.test.web.servlet.ResultActions; +import org.springframework.test.web.servlet.setup.MockMvcBuilders; +import org.springframework.web.context.WebApplicationContext; + +import java.util.List; + +import static org.assertj.core.api.AssertionsForClassTypes.assertThat; +import static org.junit.jupiter.api.Assertions.*; +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; + +@SpringBootTest +@AutoConfigureMockMvc +class BlogApiControllerTest { + @Autowired + protected MockMvc mockMvc; + + @Autowired + protected ObjectMapper objectMapper; // Marshal, Unmarshal을 위한 클래스 + + @Autowired + private WebApplicationContext context; + + @Autowired + BlogRepository blogRepository; + + @BeforeEach + public void mockMvcSetup() { + this.mockMvc = MockMvcBuilders.webAppContextSetup(context).build(); + blogRepository.deleteAll(); + } + + @DisplayName("addArticle: 블로그 글 추가 성공 확인") + @Test + public void addArticle() throws Exception { + // given + final String url = "/api/articles"; + final String title = "title"; + final String content = "content"; + final AddArticleRequest userRequest = new AddArticleRequest(title, content); + + // Marshal JSON + final String requestBody = objectMapper.writeValueAsString(userRequest); + + // when + // given에서 설정한 내용을 바탕으로 요청 전송 + ResultActions result = mockMvc.perform(post(url).contentType(MediaType.APPLICATION_JSON_VALUE).content(requestBody)); + + // then + result.andExpect(status().isCreated()); + + List
articles = blogRepository.findAll(); + + assertThat(articles.size()).isEqualTo(1); + assertThat(articles.get(0).getTitle()).isEqualTo(title); + assertThat(articles.get(0).getContent()).isEqualTo(content); + } +} \ No newline at end of file