Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Article CRUD and Comment CRUD #13

Open
wants to merge 36 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 24 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
e313192
Add: article create
thelight0804 Jul 5, 2023
fe9b97e
Add: find by title in article
thelight0804 Jul 5, 2023
d85b433
Add: like search by title or content in article
thelight0804 Jul 5, 2023
5a563f8
Add: Update article feature in test code
thelight0804 Jul 5, 2023
71c21fb
Add: Delete article feature in test code
thelight0804 Jul 5, 2023
10e249c
Add: Create comment
thelight0804 Jul 8, 2023
67e0277
Add: Read comment
thelight0804 Jul 8, 2023
247011c
Fix: test get comment
thelight0804 Jul 8, 2023
c305540
Add: update comment
thelight0804 Jul 8, 2023
bdc530d
Add: delete comment
thelight0804 Jul 8, 2023
e76bd33
Add save login user in comment
thelight0804 Jul 9, 2023
e33831b
Add save login user in Article
thelight0804 Jul 9, 2023
55fa8a2
Add method article controller and service
thelight0804 Jul 9, 2023
3d14903
Add Principal in article and comment controller method
thelight0804 Jul 10, 2023
ea9c945
Add Swagger annotation
thelight0804 Jul 11, 2023
6630e49
Fix DI to Article controller of UserService
thelight0804 Jul 12, 2023
8a8d54a
Fix sendError() when create Article
thelight0804 Jul 12, 2023
3d08cfe
Add dto in article
thelight0804 Jul 12, 2023
877e468
Fix http 400, 500 error of Article CRUD
thelight0804 Jul 12, 2023
a72394c
Fix add @controller in ArticleController
thelight0804 Jul 12, 2023
5d5be0a
Add comment DTO
thelight0804 Jul 12, 2023
9c66e7c
Add feature of Comment Create and Read
thelight0804 Jul 12, 2023
dd1ba9b
Fix typo postID -> commentID
thelight0804 Jul 12, 2023
48c9199
Add feature of comment update and delete
thelight0804 Jul 12, 2023
d2f4145
Update: apply mapping path as HTTP Method of REST API
thelight0804 Jul 14, 2023
7c153c1
Update: HttpServletRequest Swagger parameter to hidden
thelight0804 Jul 14, 2023
2073414
Update: add @Parameter to @PathVariable
thelight0804 Jul 14, 2023
ac9ac26
Remove Swagger documentation in service
thelight0804 Jul 14, 2023
3b99ccf
Add: get article by title
thelight0804 Jul 14, 2023
6fd13b0
Update: use orElseThrow() instead of if-else
thelight0804 Jul 14, 2023
a8d6e2f
Fix typo: 게시글 id instead of 댓글 id in swagger parameter annotation
thelight0804 Jul 14, 2023
66e9e2c
Remove findByArticleIdx method
thelight0804 Jul 14, 2023
122bb7b
Refactor: improve responsibility distribution by create Article, Comm…
thelight0804 Jul 14, 2023
d39ccfd
Add: Base Entity to remove Duplicate code in entity
thelight0804 Jul 14, 2023
c510964
Add: Transactional annotation
thelight0804 Jul 14, 2023
79683bd
Remove: save() method in update
thelight0804 Jul 14, 2023
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions .idea/.gitignore

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions .idea/.name

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

15 changes: 15 additions & 0 deletions .idea/compiler.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

20 changes: 20 additions & 0 deletions .idea/jarRepositories.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/misc.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 8 additions & 0 deletions .idea/modules/blog.main.iml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

6 changes: 6 additions & 0 deletions .idea/vcs.xml

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 3 additions & 5 deletions src/main/java/com/gdsc/blog/BlogApplication.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,7 @@

@SpringBootApplication
public class BlogApplication {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

이런 스타일 관련 변경점은 새로운 PR을 통해 작성하는게 좋아요!

다른 사람과 작업을 할 때, 이 부분을 예를 들자면, 어플리케이션 실행 전 로그를 남기는 등의 동작을 추가하게 된다면 Conflict가 나게 되겠죠?


public static void main(String[] args) {
SpringApplication.run(BlogApplication.class, args);
}

public static void main(String[] args) {
SpringApplication.run(BlogApplication.class, args);
}
}
124 changes: 124 additions & 0 deletions src/main/java/com/gdsc/blog/article/controller/ArticleController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
package com.gdsc.blog.article.controller;

import com.gdsc.blog.article.dto.ArticleCreateDto;
import com.gdsc.blog.article.dto.ArticleUpdateDto;
import com.gdsc.blog.article.entity.Article;
import com.gdsc.blog.article.service.ArticleService;
import com.gdsc.blog.user.entity.User;
import com.gdsc.blog.user.service.UserService;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.servlet.http.HttpServletRequest;
import java.util.List;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.crossstore.ChangeSetPersister;
import org.springframework.security.access.AccessDeniedException;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@Controller
@RequestMapping("/api/article")
@RestController
@RequiredArgsConstructor
@Tag(name = "게시글 Controller", description = "Article controller API")
@Slf4j
public class ArticleController {

private final ArticleService articleService;
private final UserService userService;

/**
* 게시글 생성
* @param dto 게시글 생성 정보
* @return 생성된 게시글
*/
@PostMapping("/create") //컨트롤러 메핑

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

REST API를 기준으로 API를 작성하기로 했다면, create, delete, update 등의 동작을 표현하는 용어들은 HTTP Method를 통해 표현되어야 해요

  • GET : 리스트 받아오기
  • GET {id} : 특정 데이터를 받아오기
  • POST : 생성하기
  • PUT {id} : 전체 수정/생성하기
  • PATCH {id} : 일부분 수정하기
  • DELETE {id} : 삭제하기

따라서 해당 API의 Path는 POST "/api/article" 이 적절하겠죠?

Suggested change
@PostMapping("/create") //컨트롤러 메핑
@PostMapping //컨트롤러 메핑

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

아래의 다른 RequestMapping 들도 마찬가지입니다!

@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") //권한 설정
@Operation(summary = "게시글 생성") //swagger 설명
public Article createArticle(
@Parameter(name="게시글 생성 DTO") @RequestBody ArticleCreateDto dto){
//게시글 생성
Article article = Article.builder()
.title(dto.getTitle())
.content(dto.getContent())

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

저는 개인적으로 여기에서 Article을 생성하는 것보다... createArticle이 모든 책임을 가져가는 게 좋다고 봐요!

만약 Article 중 어떤 추가적인 내용이 제공되어야 한다고 했을 때, 지금 구조에서는 ArticleController와 ArticleService에서 Article 생성이라는 동일 책임을 분배하고 있기 때문에, 어느 부분에 로직이 추가되어야 하는지를 다시 고민하게 되는 비용이 발생하게 되겠죠.

그리고 createArticle 함수의 정확한 책임이 Article 생성보다는, 내용만 들어 있는 Article에 User를 적용하고 저장(듣기만 해도 복잡해 보이죠)하는 책임을 담당하고 있기 때문에, 이를 Article 객체를 생성하고 데이터를 입력 이라던지, 생성된 Article객체를 인자로 받아 DB에 저장 이라던지, 혹은 둘 다 진행 가능한 책임을 주던지, 결론적으로 이런 분배가 현재 구조에서는 그닥 유리해 보이지 않네요...

.build();

//로그인 유저 정보 가져오기
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
String userName = authentication.getName();

User user = userService.getUserByName(userName); //유저 이름 가져오기
return articleService.createArticle(article, user);
}

/**
* 모든 게시글 조회
* @param req HTTP 파싱 객체
* @return 게시글 목록
*/
@GetMapping("/allArticle")
@Operation(summary = "모든 게시글 조회")
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
public List<Article> getUserArticle(
@Parameter(name = "HTTP 파싱 객체") HttpServletRequest req

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

HttpServletRequest 파라미터는 사용자가 입력하는 것이 아니라, Spring Boot 프레임워크에서 넘겨주는 것이기 떄문에, Swagger에서 표시되면 안 되는 친구에요!

따라서, @Parameter 어노테이션의 hidden 옵션을 달아 주는 게 좋을 거 같네요!

Suggested change
@Parameter(name = "HTTP 파싱 객체") HttpServletRequest req
@Parameter(hidden = true) HttpServletRequest req

) {
User user = userService.whoami(req); //로그인 유저 정보 가져오기

return articleService.getUserArticle(user);
}

@GetMapping("/{id}")
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
@Operation(summary = "id로 게시글 조회")
public Article getArticleById(
@PathVariable("id") Long id, //PathVariable에는 @Parameter를 사용할 수 없음!

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

PathVariable에 사용 가능한 걸로 알고 있는데... value 대신에 description에 한번 설명을 넣어 보는 걸 추천드려요!

springdoc/springdoc-openapi#651

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

다른 친구들도 마찬가지입니다!

@Parameter(name = "HTTP 파싱 객체") HttpServletRequest req){
return articleService.getArticleById(id);
}

/**
* 게시글 수정
* @param id 게시글 id
* @param dto 게시글 수정 정보
* @param req HTTP 파싱 객체
*/
@PostMapping("/update/{id}") //생성 & 수정은 PostMapping
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
@Operation(summary = "게시글 수정")
public Article updateArticle(
@PathVariable(value = "id") Long id,
@Parameter(name="게시글 수정 DTO") @RequestBody ArticleUpdateDto dto,
@Parameter(name = "HTTP 파싱 객체") HttpServletRequest req) throws ChangeSetPersister.NotFoundException, AccessDeniedException {
User user = userService.whoami(req);

return articleService.updateArticle(id, dto, user);
}

/**
* 게시글 삭제
* @param id 게시글 id
* @param req HTTP 파싱 객체
*/
@GetMapping("/delete/{id}")
@PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')")
@Operation(summary = "게시글 삭제")
public void deleteArticle(
@PathVariable(value = "id") Long id,
@Parameter(name = "HTTP 파싱 객체") HttpServletRequest req) {
User user = userService.whoami(req);

Article article = articleService.getArticleById(id);

articleService.deleteArticle(article);
}
}
21 changes: 21 additions & 0 deletions src/main/java/com/gdsc/blog/article/dto/ArticleCreateDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.gdsc.blog.article.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
public class ArticleCreateDto {
@Schema(description = "제목", example = "게시글 제목")
public String title;

@Schema(description = "내용", example = "게시글 내용")
public String content;
}
21 changes: 21 additions & 0 deletions src/main/java/com/gdsc/blog/article/dto/ArticleUpdateDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package com.gdsc.blog.article.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@AllArgsConstructor
@NoArgsConstructor
@Builder
@Getter
@Setter
public class ArticleUpdateDto {
@Schema(description = "수정할 제목", example = "수정된 게시글 제목")
public String title;

@Schema(description = "수정할 내용", example = "수정된 게시글 내용")
public String content;
}
52 changes: 52 additions & 0 deletions src/main/java/com/gdsc/blog/article/entity/Article.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.gdsc.blog.article.entity;

import com.fasterxml.jackson.annotation.JsonBackReference;
import com.gdsc.blog.comment.entity.Comment;
import com.gdsc.blog.user.entity.User;
import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import java.time.LocalDateTime;
import java.util.List;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@Entity
@NoArgsConstructor
@AllArgsConstructor //생성자 자동 생성
@Getter
@Setter
@Builder
@Table(name = "articles")
public class Article {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long idx; //id

@Column(length = 200)
private String title;

@Column(columnDefinition = "TEXT") //no limit length of text
private String content;

private LocalDateTime createDate; //생성 날짜
private LocalDateTime modifyDate; //수정된 날짜

//set relationship between article and user
@ManyToOne
@JsonBackReference
private User user;

@OneToMany(mappedBy = "article", cascade = CascadeType.REMOVE)
private List<Comment> comments;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.gdsc.blog.article.repository;

import com.gdsc.blog.article.entity.Article;
import java.util.List;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ArticleRepository extends JpaRepository<Article, Long> {
Article findByTitle(String title);

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

findByTitle 메소드는 제목을 통한 검색 메소드로 보이는데...제목이 없을 경우를 대비해서 Return type이 Optional이 되는 것이 조금 더 나을 거 같아요!

Suggested change
Article findByTitle(String title);
Optional<Article> findByTitle(String title);

List<Article> findByTitleLikeOrContentLike(String title, String content);
}
Loading