-
Notifications
You must be signed in to change notification settings - Fork 1
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
base: main
Are you sure you want to change the base?
Changes from 24 commits
e313192
fe9b97e
d85b433
5a563f8
71c21fb
10e249c
67e0277
247011c
c305540
bdc530d
e76bd33
e33831b
55fa8a2
3d14903
ea9c945
6630e49
8a8d54a
3d08cfe
877e468
a72394c
5d5be0a
9c66e7c
dd1ba9b
48c9199
d2f4145
7c153c1
2073414
ac9ac26
3b99ccf
6fd13b0
a8d6e2f
66e9e2c
122bb7b
d39ccfd
c510964
79683bd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.
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") //컨트롤러 메핑 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. REST API를 기준으로 API를 작성하기로 했다면, create, delete, update 등의 동작을 표현하는 용어들은 HTTP Method를 통해 표현되어야 해요
따라서 해당 API의 Path는
Suggested change
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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()) | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 개인적으로 여기에서 Article을 생성하는 것보다... createArticle이 모든 책임을 가져가는 게 좋다고 봐요! 만약 Article 중 어떤 추가적인 내용이 제공되어야 한다고 했을 때, 지금 구조에서는 ArticleController와 ArticleService에서 Article 생성이라는 동일 책임을 분배하고 있기 때문에, 어느 부분에 로직이 추가되어야 하는지를 다시 고민하게 되는 비용이 발생하게 되겠죠. 그리고 createArticle 함수의 정확한 책임이 Article 생성보다는, 내용만 들어 있는 Article에 User를 적용하고 저장(듣기만 해도 복잡해 보이죠)하는 책임을 담당하고 있기 때문에, 이를 |
||||||
.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 | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 이 따라서,
Suggested change
|
||||||
) { | ||||||
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를 사용할 수 없음! | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. PathVariable에 사용 가능한 걸로 알고 있는데... value 대신에 description에 한번 설명을 넣어 보는 걸 추천드려요! There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||||||
} | ||||||
} |
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; | ||
} |
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; | ||
} |
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); | ||||||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
Suggested change
|
||||||
List<Article> findByTitleLikeOrContentLike(String title, String content); | ||||||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
이런 스타일 관련 변경점은 새로운 PR을 통해 작성하는게 좋아요!
다른 사람과 작업을 할 때, 이 부분을 예를 들자면, 어플리케이션 실행 전 로그를 남기는 등의 동작을 추가하게 된다면 Conflict가 나게 되겠죠?