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
+ }
}