diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..13566b8 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,8 @@ +# Default ignored files +/shelf/ +/workspace.xml +# Editor-based HTTP Client requests +/httpRequests/ +# Datasource local storage ignored files +/dataSources/ +/dataSources.local.xml diff --git a/.idea/.name b/.idea/.name new file mode 100644 index 0000000..0d50744 --- /dev/null +++ b/.idea/.name @@ -0,0 +1 @@ +blog \ No newline at end of file diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..a410458 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,15 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/jarRepositories.xml b/.idea/jarRepositories.xml new file mode 100644 index 0000000..fdc392f --- /dev/null +++ b/.idea/jarRepositories.xml @@ -0,0 +1,20 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..de0c428 --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 0000000..0f4e222 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/modules/blog.main.iml b/.idea/modules/blog.main.iml new file mode 100644 index 0000000..afc1871 --- /dev/null +++ b/.idea/modules/blog.main.iml @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..35eb1dd --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/src/main/java/com/gdsc/blog/BlogApplication.java b/src/main/java/com/gdsc/blog/BlogApplication.java index 56b0143..9d91992 100644 --- a/src/main/java/com/gdsc/blog/BlogApplication.java +++ b/src/main/java/com/gdsc/blog/BlogApplication.java @@ -2,12 +2,12 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; @SpringBootApplication +@EnableJpaAuditing //자동으로 시간을 매핑 public class BlogApplication { - - public static void main(String[] args) { - SpringApplication.run(BlogApplication.class, args); - } - + public static void main(String[] args) { + SpringApplication.run(BlogApplication.class, args); + } } diff --git a/src/main/java/com/gdsc/blog/article/controller/ArticleController.java b/src/main/java/com/gdsc/blog/article/controller/ArticleController.java new file mode 100644 index 0000000..70e7c59 --- /dev/null +++ b/src/main/java/com/gdsc/blog/article/controller/ArticleController.java @@ -0,0 +1,137 @@ +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.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 //컨트롤러 메핑 + @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") //권한 설정 + @Operation(summary = "게시글 생성") //swagger 설명 + public Article createArticle( + @Parameter(name="게시글 생성 DTO") @RequestBody ArticleCreateDto dto){ + //로그인 유저 정보 가져오기 + Authentication authentication = SecurityContextHolder.getContext().getAuthentication(); + String userName = authentication.getName(); + User user = userService.getUserByName(userName); //유저 이름 가져오기 + + return articleService.createArticle(dto, user); + } + + /** + * 모든 게시글 조회 + * @param req HTTP 파싱 객체 + * @return 게시글 목록 + */ + @GetMapping("/get") + @Operation(summary = "모든 게시글 조회") + @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") + public List
getUserArticle( + @Parameter(hidden = true) HttpServletRequest req + ) { + User user = userService.whoami(req); //로그인 유저 정보 가져오기 + + return articleService.getUserArticle(user); + } + + /** + * id로 게시글 조회 + * @param id 게시글 id + * @param req HTTP 파싱 객체 + * @return 게시글 + */ + @GetMapping("/get/id/{id}") + @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") + @Operation(summary = "id로 게시글 조회") + public Article getArticleById( + @Parameter(description = "게시글 id") @PathVariable("id") Long id, + @Parameter(hidden = true) HttpServletRequest req){ + return articleService.getArticleById(id); + } + + /** + * 제목으로 게시글 조회 + * @param title 게시글 제목 + * @param req HTTP 파싱 객체 + * @return 게시글 + */ + @GetMapping("/get/title/{title}") + @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") + @Operation(summary = "제목으로 게시글 조회") + public Article getArticleByTitle( + @Parameter(description = "게시글 제목") @PathVariable(value = "title") String title, + @Parameter(hidden = true) HttpServletRequest req){ + return articleService.getArticleByTitle(title); + } + + /** + * 게시글 수정 + * @param id 게시글 id + * @param dto 게시글 수정 정보 + * @param req HTTP 파싱 객체 + */ + @PostMapping("/patch/{id}") //생성 & 수정은 PostMapping + @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") + @Operation(summary = "게시글 수정") + public void updateArticle( + @Parameter(description = "게시글 id") @PathVariable(value = "id") Long id, + @Parameter(name="게시글 수정 DTO") @RequestBody ArticleUpdateDto dto, + @Parameter(hidden = true) HttpServletRequest req) { + User user = userService.whoami(req); + + articleService.updateArticle(id, dto); + } + + /** + * 게시글 삭제 + * @param id 게시글 id + * @param req HTTP 파싱 객체 + */ + @GetMapping("/delete/{id}") + @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") + @Operation(summary = "게시글 삭제") + public void deleteArticle( + @Parameter(description = "게시글 id") @PathVariable(value = "id") Long id, + @Parameter(hidden = true) HttpServletRequest req) { + User user = userService.whoami(req); + + Article article = articleService.getArticleById(id); + + articleService.deleteArticle(article); + } +} diff --git a/src/main/java/com/gdsc/blog/article/dto/ArticleCreateDto.java b/src/main/java/com/gdsc/blog/article/dto/ArticleCreateDto.java new file mode 100644 index 0000000..96634a7 --- /dev/null +++ b/src/main/java/com/gdsc/blog/article/dto/ArticleCreateDto.java @@ -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; +} diff --git a/src/main/java/com/gdsc/blog/article/dto/ArticleUpdateDto.java b/src/main/java/com/gdsc/blog/article/dto/ArticleUpdateDto.java new file mode 100644 index 0000000..bd8aaf6 --- /dev/null +++ b/src/main/java/com/gdsc/blog/article/dto/ArticleUpdateDto.java @@ -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; +} diff --git a/src/main/java/com/gdsc/blog/article/entity/Article.java b/src/main/java/com/gdsc/blog/article/entity/Article.java new file mode 100644 index 0000000..6d3b2dd --- /dev/null +++ b/src/main/java/com/gdsc/blog/article/entity/Article.java @@ -0,0 +1,37 @@ +package com.gdsc.blog.article.entity; + +import com.gdsc.blog.base.entity.Base; +import com.gdsc.blog.comment.entity.Comment; +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.OneToMany; +import jakarta.persistence.Table; +import java.util.List; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; + +@Entity +@NoArgsConstructor +@AllArgsConstructor //생성자 자동 생성 +@Getter +@Setter +@SuperBuilder +@Table(name = "articles") +public class Article extends Base { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; //id + + @Column(length = 200) + private String title; //제목 + + @OneToMany(mappedBy = "article", cascade = CascadeType.REMOVE) + private List comments; +} \ No newline at end of file diff --git a/src/main/java/com/gdsc/blog/article/repository/ArticleRepository.java b/src/main/java/com/gdsc/blog/article/repository/ArticleRepository.java new file mode 100644 index 0000000..b10ed8e --- /dev/null +++ b/src/main/java/com/gdsc/blog/article/repository/ArticleRepository.java @@ -0,0 +1,13 @@ +package com.gdsc.blog.article.repository; + +import com.gdsc.blog.article.entity.Article; +import java.util.List; +import java.util.Optional; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +@Repository +public interface ArticleRepository extends JpaRepository { + Optional
findByTitle(String title); + List
findByTitleLikeOrContentLike(String title, String content); +} \ No newline at end of file diff --git a/src/main/java/com/gdsc/blog/article/service/ArticleService.java b/src/main/java/com/gdsc/blog/article/service/ArticleService.java new file mode 100644 index 0000000..d8193d6 --- /dev/null +++ b/src/main/java/com/gdsc/blog/article/service/ArticleService.java @@ -0,0 +1,88 @@ +package com.gdsc.blog.article.service; + +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.repository.ArticleRepository; +import com.gdsc.blog.user.entity.User; +import jakarta.transaction.Transactional; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class ArticleService { + private final ArticleRepository articleRepository; + + /** + * 게시글 생성 + * + * @param dto 게시글 생성 정보 + * @param user 작성자 + */ + @Transactional + public Article createArticle(ArticleCreateDto dto, User user) { //create new article + Article article = Article.builder() + .title(dto.getTitle()) + .content(dto.getContent()) + .user(user) + .build(); + + return articleRepository.save(article); + } + + /** + * 유저 게시글 목록 조회 + * + * @return 게시글 목록 + */ + public List
getUserArticle(User user) { //get all articles + return user.getArticles(); + } + + /** + * id로 게시글 조회 + * + * @param idx 게시글 id + * @return Article 객체 + */ + public Article getArticleById(Long idx) { //get article by id + Optional
article = articleRepository.findById(idx); + + return article.orElseThrow(() -> new NullPointerException("Not found Article by id")); + } + + public Article getArticleByTitle(String title){ + Optional
article = articleRepository.findByTitle(title); + + return article.orElseThrow(() -> new NullPointerException("Not found Article by title")); + } + + /** + * 게시글 수정 + * + * @param id 게시글 id + * @param dto 게시글 수정 DTO + */ + @Transactional + public void updateArticle(Long id, ArticleUpdateDto dto) { + Article article = getArticleById(id); //게시글 id로 게시글 가져오기 + article.setTitle(dto.getTitle()); + article.setContent(dto.getContent()); + + //articleRepository.save(article); + //Transactional 어노테이션으로 save() 메소드를 사용하지 않아도 된다. + } + + /** + * 게시글 삭제 + * + * @param article 게시글 객체 + */ + @Transactional + public void deleteArticle(Article article) { + articleRepository.delete(article); + } +} diff --git a/src/main/java/com/gdsc/blog/base/entity/Base.java b/src/main/java/com/gdsc/blog/base/entity/Base.java new file mode 100644 index 0000000..1c0516b --- /dev/null +++ b/src/main/java/com/gdsc/blog/base/entity/Base.java @@ -0,0 +1,39 @@ +package com.gdsc.blog.base.entity; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.gdsc.blog.user.entity.User; +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.MappedSuperclass; +import java.time.LocalDateTime; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@MappedSuperclass //엔티티 부모 클래스 +@NoArgsConstructor +@Getter +@Setter +@SuperBuilder +@EntityListeners(AuditingEntityListener.class) +public abstract class Base { + @Column(columnDefinition = "TEXT") //no limit length of text + private String content; + + @ManyToOne + @JsonBackReference + private User user; + + @CreatedDate + @Column(updatable = false) //수정 방지 + private LocalDateTime createDate; //생성 날짜 + + @LastModifiedDate + private LocalDateTime modifyDate; //수정된 날짜 +} diff --git a/src/main/java/com/gdsc/blog/comment/controller/CommentController.java b/src/main/java/com/gdsc/blog/comment/controller/CommentController.java new file mode 100644 index 0000000..ae42a1a --- /dev/null +++ b/src/main/java/com/gdsc/blog/comment/controller/CommentController.java @@ -0,0 +1,128 @@ +package com.gdsc.blog.comment.controller; + +import com.gdsc.blog.article.entity.Article; +import com.gdsc.blog.article.service.ArticleService; +import com.gdsc.blog.comment.dto.CommentCreateDto; +import com.gdsc.blog.comment.dto.CommentUpdateDto; +import com.gdsc.blog.comment.entity.Comment; +import com.gdsc.blog.comment.service.CommentService; +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.security.access.prepost.PreAuthorize; +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/comment") +@RestController +@RequiredArgsConstructor +@Slf4j +@Tag(name = "댓글", description = "Comment Controller API") +public class CommentController { + private final ArticleService articleService; + private final CommentService commentService; + private final UserService userService; + + /** + * 댓글 생성 + * @param id 게시글 id + * @param dto 댓글 생성 정보 + * @param req HTTP 파싱 객체 + */ + @PostMapping("/{postId}") //컨트롤러 메핑 + @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") //권한 설정 + @Operation(summary = "댓글 생성") + public Comment createComment( + @Parameter(description = "게시글 id") @PathVariable("postId") Long id, + @Parameter(name = "댓글 생성 DTO") @RequestBody CommentCreateDto dto, + @Parameter(hidden = true) HttpServletRequest req) { + User user = userService.whoami(req); //로그인 유저 정보 가져오기 + Article article = articleService.getArticleById(id); //get article object + + return commentService.create(article, user); //create comment + } + + /** + * 게시글에 대한 모든 댓글 조회 + * @param id 게시글 id + * @param req HTTP 파싱 객체 + * @return 댓글 객체 List + */ + @GetMapping("/get/{postId}") + @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") + @Operation(summary = "게시글에 대한 모든 댓글 조회") + public List getAllCommand( + @Parameter(description = "게시글 id") @PathVariable("postId") Long id, + @Parameter(hidden = true) HttpServletRequest req) { + User user = userService.whoami(req); + + Article article = articleService.getArticleById(id); //get article object + + return commentService.getAllCommandByArticle(article); //return comment content + } + + /** + * id로 댓글 조회 + * @param id 댓글 id + * @param req HTTP 파싱 객체 + * @return 댓글 객체 + */ + @GetMapping("/get/all/{commentId}") + @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") + @Operation(summary = "id로 댓글 조회") + public Comment getCommentById( + @Parameter(description = "댓글 id") @PathVariable("commentId") Long id, + @Parameter(hidden = true) HttpServletRequest req) { + User user = userService.whoami(req); + + return commentService.getCommentById(id); + } + + /** + * 댓글 수정 + * @param id 댓글 id + * @param dto 댓글 수정 정보 + * @param req HTTP 파싱 객체 + */ + @PostMapping("/patch/{commentId}") + @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") + @Operation(summary = "댓글 수정") + public void updateComment( + @Parameter(description = "댓글 id") @PathVariable("commentId") Long id, + @Parameter(name="댓글 수정 DTO") @RequestBody CommentUpdateDto dto, + @Parameter(hidden = true) HttpServletRequest req){ + User user = userService.whoami(req); + + commentService.updateComment(id, dto); + } + + /** + * 댓글 삭제 + * @param id 댓글 id + * @param req HTTP 파싱 객체 + */ + @GetMapping("/delete/{commentId}") + @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") + @Operation(summary = "댓글 삭제") + public void deleteComment( + @Parameter(description = "댓글 id") @PathVariable("commentId") Long id, + @Parameter(hidden = true) HttpServletRequest req){ + User user = userService.whoami(req); + + Comment comment = commentService.getCommentById(id); //get comment object + + commentService.delete(comment); //delete comment + } +} diff --git a/src/main/java/com/gdsc/blog/comment/dto/CommentCreateDto.java b/src/main/java/com/gdsc/blog/comment/dto/CommentCreateDto.java new file mode 100644 index 0000000..3dec1a6 --- /dev/null +++ b/src/main/java/com/gdsc/blog/comment/dto/CommentCreateDto.java @@ -0,0 +1,18 @@ +package com.gdsc.blog.comment.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 CommentCreateDto { + @Schema(description = "내용", example = "댓글 내용") + public String content; +} diff --git a/src/main/java/com/gdsc/blog/comment/dto/CommentUpdateDto.java b/src/main/java/com/gdsc/blog/comment/dto/CommentUpdateDto.java new file mode 100644 index 0000000..0409303 --- /dev/null +++ b/src/main/java/com/gdsc/blog/comment/dto/CommentUpdateDto.java @@ -0,0 +1,18 @@ +package com.gdsc.blog.comment.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 CommentUpdateDto { + @Schema(description = "수정할 내용", example = "수정된 댓글 내용") + public String content; +} diff --git a/src/main/java/com/gdsc/blog/comment/entity/Comment.java b/src/main/java/com/gdsc/blog/comment/entity/Comment.java new file mode 100644 index 0000000..96c96d1 --- /dev/null +++ b/src/main/java/com/gdsc/blog/comment/entity/Comment.java @@ -0,0 +1,37 @@ +package com.gdsc.blog.comment.entity; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import com.gdsc.blog.article.entity.Article; +import com.gdsc.blog.base.entity.Base; +import jakarta.persistence.Entity; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.ManyToOne; +import jakarta.persistence.Table; +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; +import lombok.Setter; +import lombok.experimental.SuperBuilder; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +@Entity +@NoArgsConstructor +@AllArgsConstructor +@Getter +@Setter +@SuperBuilder +@EntityListeners(AuditingEntityListener.class) +@Table(name = "comments") +public class Comment extends Base { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; //id + + @ManyToOne + @JsonBackReference + private Article article; + +} diff --git a/src/main/java/com/gdsc/blog/comment/repository/CommentRepository.java b/src/main/java/com/gdsc/blog/comment/repository/CommentRepository.java new file mode 100644 index 0000000..e88a578 --- /dev/null +++ b/src/main/java/com/gdsc/blog/comment/repository/CommentRepository.java @@ -0,0 +1,10 @@ +package com.gdsc.blog.comment.repository; + +import com.gdsc.blog.article.entity.Article; +import com.gdsc.blog.comment.entity.Comment; +import java.util.List; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface CommentRepository extends JpaRepository{ + List findByArticle(Article article); +} \ No newline at end of file diff --git a/src/main/java/com/gdsc/blog/comment/service/CommentService.java b/src/main/java/com/gdsc/blog/comment/service/CommentService.java new file mode 100644 index 0000000..c6b4fc8 --- /dev/null +++ b/src/main/java/com/gdsc/blog/comment/service/CommentService.java @@ -0,0 +1,76 @@ +package com.gdsc.blog.comment.service; + +import com.gdsc.blog.article.entity.Article; +import com.gdsc.blog.comment.dto.CommentUpdateDto; +import com.gdsc.blog.comment.entity.Comment; +import com.gdsc.blog.comment.repository.CommentRepository; +import com.gdsc.blog.user.entity.User; +import jakarta.transaction.Transactional; +import java.util.List; +import java.util.Optional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +@Service +@RequiredArgsConstructor +public class CommentService { + + private final CommentRepository commentRepository; + + /** + * 댓글 생성 + * + * @param article 개시글 객체 + * @param user 로그인 유저 객체 + */ + @Transactional + public Comment create(Article article, User user) { + Comment comment = Comment.builder() + .article(article) + .content(article.getContent()) + .user(user) + .build(); + + return commentRepository.save(comment); + } + + public List getAllCommandByArticle(Article article) { + return commentRepository.findByArticle(article); + } + + /** + * id로 댓글 조회 + * + * @param idx 댓글 id + * @return 댓글 객체 + */ + public Comment getCommentById(Long idx) { + Optional comment = commentRepository.findById(idx); + + return comment.orElseThrow(() -> new NullPointerException("Not found Comment by id")); + } + + /** + * 댓글 수정 + * + * @param id 댓글 id + * @param commentUpdateDto 댓글 수정 DTO + */ + @Transactional + public void updateComment(Long id, CommentUpdateDto commentUpdateDto) { + + Comment comment = getCommentById(id); + comment.setContent(commentUpdateDto.getContent()); + } + + + /** + * 댓글 삭제 + * + * @param comment 댓글 객체 + */ + @Transactional + public void delete(Comment comment) { + commentRepository.delete(comment); + } +} diff --git a/src/main/java/com/gdsc/blog/configuration/SecurityConfig.java b/src/main/java/com/gdsc/blog/configuration/SecurityConfig.java index d194f30..608bd50 100644 --- a/src/main/java/com/gdsc/blog/configuration/SecurityConfig.java +++ b/src/main/java/com/gdsc/blog/configuration/SecurityConfig.java @@ -2,6 +2,7 @@ import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; @@ -26,12 +27,12 @@ public PasswordEncoder passwordEncoder() { public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { // Disable CSRF (cross site request forgery) - http.authorizeRequests() - .requestMatchers("/h2-console/**", "/swagger-ui/**", "/swagger-resources/**", - "/v3/api-docs/**", "/api/user/signin", "/api/user/signup") - .permitAll().anyRequest().authenticated(); +// http.authorizeRequests() +// .requestMatchers("/h2-console/**", "/swagger-ui/**", "/swagger-resources/**", +// "/v3/api-docs/**", "/api/user/signin", "/api/user/signup") +// .permitAll().anyRequest().authenticated(); - http.csrf().disable(); + http.csrf(csrf->csrf.disable()); http.apply(new JwtTokenFilterConfigurer(jwtTokenProvider)); diff --git a/src/main/java/com/gdsc/blog/user/controller/UserController.java b/src/main/java/com/gdsc/blog/user/controller/UserController.java index 463a5d9..a81e789 100644 --- a/src/main/java/com/gdsc/blog/user/controller/UserController.java +++ b/src/main/java/com/gdsc/blog/user/controller/UserController.java @@ -26,42 +26,42 @@ @RequestMapping("/api/user") public class UserController { - UserService userService; + UserService userService; - public UserController(UserService userService) { - this.userService = userService; - } + public UserController(UserService userService) { + this.userService = userService; + } - @PostMapping("/signup") - @Operation(summary = "회원가입") - public User signup(@RequestBody SignUpDto user) { + @PostMapping("/signup") + @Operation(summary = "회원가입") + public User signup(@RequestBody SignUpDto user) { - User user1 = User.builder().email(user.getEmail()).password(user.getPassword()) - .username(user.getUsername()).build(); + User user1 = User.builder().email(user.getEmail()).password(user.getPassword()) + .username(user.getUsername()).build(); - return userService.signup(user1); - } + return userService.signup(user1); + } - @PostMapping("/signin") - @Operation(summary = "로그인") - public Token signin(@RequestBody SignInDto signInDto) { - Token token = Token.builder() - .token(userService.singin(signInDto.getEmail(), signInDto.getPassword())).build(); - return token; + @PostMapping("/signin") + @Operation(summary = "로그인") + public Token signin(@RequestBody SignInDto signInDto) { + Token token = Token.builder() + .token(userService.singin(signInDto.getEmail(), signInDto.getPassword())).build(); + return token; - } + } - @GetMapping("/getUser/{email}") - @PreAuthorize("hasRole('ROLE_ADMIN')") - public User getUser(@PathVariable("email") String email) { - return userService.getUser(email); - } + @GetMapping("/getUser/{email}") + @PreAuthorize("hasRole('ROLE_ADMIN')") + public User getUser(@PathVariable("email") String email) { + return userService.getUser(email); + } - @GetMapping("/whoisme") - @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") - public User whoisme(HttpServletRequest req) { - return userService.whoami(req); - } + @GetMapping("/whoisme") + @PreAuthorize("hasRole('ROLE_ADMIN') or hasRole('ROLE_CLIENT')") + public User whoisme(HttpServletRequest req) { + return userService.whoami(req); + } } diff --git a/src/main/java/com/gdsc/blog/user/entity/User.java b/src/main/java/com/gdsc/blog/user/entity/User.java index 28cddab..aefb45b 100644 --- a/src/main/java/com/gdsc/blog/user/entity/User.java +++ b/src/main/java/com/gdsc/blog/user/entity/User.java @@ -1,9 +1,14 @@ package com.gdsc.blog.user.entity; +import com.gdsc.blog.article.entity.Article; +import jakarta.persistence.CascadeType; +import jakarta.persistence.OneToMany; import java.util.Collection; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import lombok.Getter; +import lombok.Setter; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; @@ -27,20 +32,26 @@ @Builder @AllArgsConstructor @NoArgsConstructor +@Getter +@Setter @Table(name = "users") public class User { - @Id - @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long idx; - @Column(unique = true) - private String email; + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long idx; - private String password; + @Column(unique = true) + private String email; - private String username; + private String password; - @ElementCollection(fetch = FetchType.EAGER) - List roles; + private String username; + @ElementCollection(fetch = FetchType.EAGER) + List roles; + + //set relationship between article and user + @OneToMany(mappedBy = "user", cascade = CascadeType.REMOVE) + private List
articles; } diff --git a/src/main/java/com/gdsc/blog/user/service/UserService.java b/src/main/java/com/gdsc/blog/user/service/UserService.java index 3160448..efb0db6 100644 --- a/src/main/java/com/gdsc/blog/user/service/UserService.java +++ b/src/main/java/com/gdsc/blog/user/service/UserService.java @@ -22,50 +22,63 @@ @RequiredArgsConstructor public class UserService { - private final UserRepository userRepository; - private final PasswordEncoder passwordEncoder; - private final JwtTokenProvider jwtTokenProvider; + private final UserRepository userRepository; + private final PasswordEncoder passwordEncoder; + private final JwtTokenProvider jwtTokenProvider; + public User signup(User user) { + user.setPassword(passwordEncoder.encode(user.getPassword())); + // Client 역할 부여 + if (user.getRoles() == null) { + List set = new ArrayList(); + set.add(UserRole.ROLE_CLIENT); + user.setRoles(set); + } + return userRepository.save(user); + } - public String singin(String email, String password) { - try { - User user = userRepository.findByEmail(email).get(); - log.info("user: {}", user); - log.info("email: {}", email); - log.info("password: {}", password); - if (!passwordEncoder.matches(password, user.getPassword())) { - log.error("Invalid password"); - throw new RuntimeException("Invalid password"); - } - return jwtTokenProvider.createToken(user.getUsername(), user.getRoles()); + public String singin(String email, String password) { + try { + User user = userRepository.findByEmail(email).get(); + log.info("user: {}", user); + log.info("email: {}", email); + log.info("password: {}", password); + if (!passwordEncoder.matches(password, user.getPassword())) { + log.error("Invalid password"); + throw new RuntimeException("Invalid password"); + } + return jwtTokenProvider.createToken(user.getUsername(), user.getRoles()); - } catch (Exception e) { - log.info(e.getMessage()); - log.error("Invalid username/password supplied"); - throw new RuntimeException("Invalid username/password supplied"); - } - } + } catch (Exception e) { + log.info(e.getMessage()); + log.error("Invalid username/password supplied"); + throw new RuntimeException("Invalid username/password supplied"); + } + } - public User signup(User user) { - user.setPassword(passwordEncoder.encode(user.getPassword())); - // Client 역할 부여 - if (user.getRoles() == null) { - List set = new ArrayList(); - set.add(UserRole.ROLE_CLIENT); - user.setRoles(set); - } - return userRepository.save(user); - } + public User getUser(String username) { + return userRepository.findByEmail(username).get(); + } - public User getUser(String username) { - return userRepository.findByEmail(username).get(); - } + public User getUserByName(String username) { + try { + return userRepository.findByUsername(username).get(); + } catch (Exception e) { //예외 처리 + log.error("User not found"); + throw new RuntimeException("User not found"); + } + } - public User whoami(HttpServletRequest req) { - log.info(jwtTokenProvider.resolveToken(req)); - return userRepository - .findByUsername(jwtTokenProvider.getUsername(jwtTokenProvider.resolveToken(req))) - .get(); - } + public User whoami(HttpServletRequest req) { + try{ + log.info(jwtTokenProvider.resolveToken(req)); + return userRepository + .findByUsername(jwtTokenProvider.getUsername(jwtTokenProvider.resolveToken(req))) + .get(); + } catch (Exception e) { //예외 처리 + log.error("User not found"); + throw new RuntimeException("User not found"); + } + } } diff --git a/src/test/java/com/gdsc/blog/BlogApplicationTests.java b/src/test/java/com/gdsc/blog/BlogApplicationTests.java index 533a031..683a77c 100644 --- a/src/test/java/com/gdsc/blog/BlogApplicationTests.java +++ b/src/test/java/com/gdsc/blog/BlogApplicationTests.java @@ -1,6 +1,20 @@ package com.gdsc.blog; +import static org.junit.jupiter.api.Assertions.assertEquals; + +import com.gdsc.blog.article.entity.Article; +import com.gdsc.blog.article.repository.ArticleRepository; +import com.gdsc.blog.article.service.ArticleService; +import com.gdsc.blog.comment.entity.Comment; +import com.gdsc.blog.comment.repository.CommentRepository; +import com.gdsc.blog.comment.service.CommentService; +import com.gdsc.blog.user.controller.UserController; +import com.gdsc.blog.user.entity.User; +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.context.annotation.Profile; @@ -8,7 +22,126 @@ @Profile("test") class BlogApplicationTests { - @Test - void contextLoads() {} + @Test + void contextLoads() {} + + @Autowired + private UserController userController; + + + @Autowired + private ArticleRepository articleRepository; + + @Autowired + private ArticleService articleService; + + + @Test + void testArticle(){ + ////create new post + Article post1 = new Article(); + //set title and content + post1.setTitle("Post1 title"); + post1.setContent("Post1 content"); + post1.setCreateDate(LocalDateTime.now()); + //save post + this.articleRepository.save(post1); + + Article post2 = new Article(); + post2.setTitle("Post2 title"); + post2.setContent("Post2 content"); + post2.setCreateDate(LocalDateTime.now()); + this.articleRepository.save(post2); + + ////find all post + List
posts = this.articleRepository.findAll(); + assertEquals(2, posts.size()); //check post count + + ////check post title and content + Article p1 = posts.get(0); + assertEquals("Post1 title", p1.getTitle()); + assertEquals("Post1 content", p1.getContent()); + Article p2 = posts.get(1); + assertEquals("Post2 title", p2.getTitle()); + assertEquals("Post2 content", p2.getContent()); + + ////get id + Optional
oa1 = this.articleRepository.findById(p1.getIdx()); + Optional
oa2 = this.articleRepository.findById(p2.getIdx()); + if(oa1.isPresent() && oa2.isPresent()){ + Article a1 = oa1.get(); + Article a2 = oa2.get(); + assertEquals("Post1 title", a1.getTitle()); + assertEquals("Post1 content", a1.getContent()); + assertEquals("Post2 title", a2.getTitle()); + assertEquals("Post2 content", a2.getContent()); + } + + //search by title + Article a1 = this.articleRepository.findByTitle("Post1 title"); + assertEquals(1, a1.getIdx()); + Article a2 = this.articleRepository.findByTitle("Post2 title"); + assertEquals(2, a2.getIdx()); + + //search by title or content + List
findPost = this.articleRepository.findByTitleLikeOrContentLike("%Post1%", "%Post1%"); + post1 = findPost.get(0); + assertEquals("Post1 title", post1.getTitle()); + assertEquals("Post1 content", post1.getContent()); + + //update post + post1.setTitle("Post1 title updated"); + post1.setContent("Post1 content updated"); + this.articleRepository.save(post1); //save post + assertEquals("Post1 title updated", post1.getTitle()); + assertEquals("Post1 content updated", post1.getContent()); + + //delete post + assertEquals(2, this.articleRepository.count()); //before delete post + this.articleRepository.delete(post1); //delete one post + assertEquals(1, this.articleRepository.count()); //after delete post + } + + + @Autowired + private CommentRepository commentRepository; + @Autowired + private CommentService commentService; + + @Test + void testComment(){ + //create new post + Article post1 = new Article(); + //set title and content + post1.setTitle("Post1 title"); + post1.setContent("Post1 content"); + post1.setCreateDate(LocalDateTime.now()); + //save post + this.articleRepository.save(post1); + + //create new comment + for(int i = 0; i < 3; i++){ + this.commentService.create(post1, "post1 comment" + String.valueOf(i+1)); + } + + //get comment by article + Long articleIdx = post1.getIdx(); + List comments = this.commentRepository.findByArticleIdx(articleIdx); + assertEquals(3, comments.size()); //check comment count + + //get comment content + Comment c = this.commentService.getComment(Long.valueOf(3)); //get comment by id + String content = c.getContent(); //get comment content + assertEquals("post1 comment3", content); //check comment content + + //update comment + c.setContent("post1 comment3 updated"); + c.setModifyData(LocalDateTime.now()); + this.commentService.update(c); + assertEquals("post1 comment3 updated", c.getContent()); //check updated content + //delete comment + this.commentService.delete(c); + assertEquals(2, this.commentRepository.count()); //check comment count + } }