From 4ff69948b50334bf93256850e170a02c8bfefd7d Mon Sep 17 00:00:00 2001 From: Jiwon Kang <135416359+zwo-n@users.noreply.github.com> Date: Fri, 3 Oct 2025 17:17:55 +0900 Subject: [PATCH 1/5] Update README.md --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 395edc5..d8ca717 100644 --- a/README.md +++ b/README.md @@ -1 +1,2 @@ # backend-study-sns +강지원 From 163fb9ed05b728fbe8570ac58eea478e14c15e04 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=A7=80=EC=9B=90?= Date: Wed, 8 Oct 2025 13:42:37 +0900 Subject: [PATCH 2/5] =?UTF-8?q?post,comment=EA=B8=B0=EB=8A=A5=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 + build.gradle | 18 +++++++- .../devSns/controller/CommentController.java | 31 +++++++++++++ .../devSns/controller/PostController.java | 44 +++++++++++++++++++ .../com/example/devSns/entity/Comment.java | 32 ++++++++++++++ .../java/com/example/devSns/entity/Post.java | 41 +++++++++++++++++ .../devSns/repository/CommentRepository.java | 9 ++++ .../devSns/repository/PostRepository.java | 6 +++ .../devSns/service/CommentService.java | 33 ++++++++++++++ .../example/devSns/service/PostService.java | 44 +++++++++++++++++++ src/main/resources/application.properties | 12 +++++ 11 files changed, 271 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/example/devSns/controller/CommentController.java create mode 100644 src/main/java/com/example/devSns/controller/PostController.java create mode 100644 src/main/java/com/example/devSns/entity/Comment.java create mode 100644 src/main/java/com/example/devSns/entity/Post.java create mode 100644 src/main/java/com/example/devSns/repository/CommentRepository.java create mode 100644 src/main/java/com/example/devSns/repository/PostRepository.java create mode 100644 src/main/java/com/example/devSns/service/CommentService.java create mode 100644 src/main/java/com/example/devSns/service/PostService.java diff --git a/.gitignore b/.gitignore index c2065bc..4c75e42 100644 --- a/.gitignore +++ b/.gitignore @@ -35,3 +35,5 @@ out/ ### VS Code ### .vscode/ + +.env \ No newline at end of file diff --git a/build.gradle b/build.gradle index 610d6a6..cf03320 100644 --- a/build.gradle +++ b/build.gradle @@ -19,11 +19,27 @@ repositories { } dependencies { + implementation 'org.springframework.boot:spring-boot-starter-web' + implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-web' - testImplementation 'org.springframework.boot:spring-boot-starter-test' + implementation 'com.mysql:mysql-connector-j:9.0.0' + implementation 'me.paulschwarz:spring-dotenv:4.0.0' + + testImplementation 'org.springframework.boot:spring-boot-starter-test' testRuntimeOnly 'org.junit.platform:junit-platform-launcher' + + // lombok + compileOnly 'org.projectlombok:lombok' + annotationProcessor 'org.projectlombok:lombok' + testCompileOnly 'org.projectlombok:lombok' + testAnnotationProcessor 'org.projectlombok:lombok' } +configurations { + compileOnly { + extendsFrom annotationProcessor + } +} tasks.named('test') { useJUnitPlatform() } diff --git a/src/main/java/com/example/devSns/controller/CommentController.java b/src/main/java/com/example/devSns/controller/CommentController.java new file mode 100644 index 0000000..a040745 --- /dev/null +++ b/src/main/java/com/example/devSns/controller/CommentController.java @@ -0,0 +1,31 @@ +package com.example.devSns.controller; + +import com.example.devSns.entity.Comment; +import com.example.devSns.service.CommentService; +import org.springframework.web.bind.annotation.*; +import java.util.List; + +@RestController +@RequestMapping("post/{postId}/comment") +public class CommentController { + private CommentService commentService; + + public CommentController(CommentService commentService) { + this.commentService = commentService; + } + + @GetMapping + public List getComments(@PathVariable Long postId) { + return commentService.getCommentByPost(postId); + } + + @PostMapping + public Comment createComment(@PathVariable Long postId, @RequestBody Comment comment) { + return commentService.addComment(postId, comment); + } + + @DeleteMapping("/{commentId}") + public void deleteComment(@PathVariable Long commentId) { + commentService.deleteComment(commentId); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/devSns/controller/PostController.java b/src/main/java/com/example/devSns/controller/PostController.java new file mode 100644 index 0000000..1a2a41f --- /dev/null +++ b/src/main/java/com/example/devSns/controller/PostController.java @@ -0,0 +1,44 @@ +package com.example.devSns.controller; + +import com.example.devSns.entity.Post; +import com.example.devSns.service.PostService; +import org.springframework.web.bind.annotation.*; + +import java.time.LocalDateTime; +import java.util.List; + +@RestController +@RequestMapping("/post") +public class PostController { + private final PostService postService; + + public PostController(PostService postService) { + this.postService = postService; + } + + @GetMapping + public List getAllPosts(){ + return postService.findAll(); + } + + @GetMapping("/{id}") + public Post getPostById(@PathVariable Long id){ + return postService.findById(id).orElseThrow(()-> new RuntimeException("post not found")); + } + + @PostMapping + public Post createPost(@RequestBody Post post){ + return postService.save(post); + } + + @PutMapping("/{id}") + public Post updatePost(@PathVariable Long id, @RequestBody Post updatedPost){ + + return postService.updatePost(id,updatedPost); + } + + @DeleteMapping("/{id}") + public void deletePost(@PathVariable Long id){ + postService.delete(id); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/devSns/entity/Comment.java b/src/main/java/com/example/devSns/entity/Comment.java new file mode 100644 index 0000000..4eb30dc --- /dev/null +++ b/src/main/java/com/example/devSns/entity/Comment.java @@ -0,0 +1,32 @@ +package com.example.devSns.entity; + +import com.fasterxml.jackson.annotation.JsonBackReference; +import jakarta.persistence.*; +import lombok.*; +import java.time.LocalDateTime; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Comment{ + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String content; + private String username; + private LocalDateTime createdAt; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name="post_id") + @JsonBackReference + private Post post; + + @PrePersist + public void onCreate(){ + createdAt = LocalDateTime.now(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/devSns/entity/Post.java b/src/main/java/com/example/devSns/entity/Post.java new file mode 100644 index 0000000..143a980 --- /dev/null +++ b/src/main/java/com/example/devSns/entity/Post.java @@ -0,0 +1,41 @@ +package com.example.devSns.entity; + +import com.fasterxml.jackson.annotation.JsonManagedReference; +import jakarta.persistence.*; +import lombok.*; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@Setter +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class Post { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String content; + private int likes; + private String username; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL,orphanRemoval = true) + @JsonManagedReference + private List comments = new ArrayList<>(); + @PrePersist + public void onCreate(){ + createdAt = LocalDateTime.now(); + updatedAt = LocalDateTime.now(); + } + + @PreUpdate + public void onUpdate(){ + updatedAt = LocalDateTime.now(); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/devSns/repository/CommentRepository.java b/src/main/java/com/example/devSns/repository/CommentRepository.java new file mode 100644 index 0000000..c293959 --- /dev/null +++ b/src/main/java/com/example/devSns/repository/CommentRepository.java @@ -0,0 +1,9 @@ +package com.example.devSns.repository; + +import com.example.devSns.entity.Comment; +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.List; + +public interface CommentRepository extends JpaRepository { + List findByPost_Id(Long postId); +} \ No newline at end of file diff --git a/src/main/java/com/example/devSns/repository/PostRepository.java b/src/main/java/com/example/devSns/repository/PostRepository.java new file mode 100644 index 0000000..4fd8426 --- /dev/null +++ b/src/main/java/com/example/devSns/repository/PostRepository.java @@ -0,0 +1,6 @@ +package com.example.devSns.repository; + +import com.example.devSns.entity.Post; +import org.springframework.data.jpa.repository.JpaRepository; + +public interface PostRepository extends JpaRepository {} \ No newline at end of file diff --git a/src/main/java/com/example/devSns/service/CommentService.java b/src/main/java/com/example/devSns/service/CommentService.java new file mode 100644 index 0000000..c2e8157 --- /dev/null +++ b/src/main/java/com/example/devSns/service/CommentService.java @@ -0,0 +1,33 @@ +package com.example.devSns.service; + +import com.example.devSns.entity.Comment; +import com.example.devSns.entity.Post; +import com.example.devSns.repository.CommentRepository; +import com.example.devSns.repository.PostRepository; +import org.springframework.stereotype.Service; +import java.util.List; + +@Service +public class CommentService { + private final CommentRepository commentRepository; + private final PostRepository postRepository; + + public CommentService(CommentRepository commentRepository, PostRepository postRepository) { + this.commentRepository = commentRepository; + this.postRepository = postRepository; + } + + public List getCommentByPost(Long postId){ + return commentRepository.findByPost_Id(postId); + } + + public Comment addComment(Long postId, Comment comment){ + Post post = postRepository.findById(postId).orElseThrow(()-> new IllegalArgumentException("게시글 없음")); + comment.setPost(post); + return commentRepository.save(comment); + } + + public void deleteComment(Long id){ + commentRepository.deleteById(id); + } +} \ No newline at end of file diff --git a/src/main/java/com/example/devSns/service/PostService.java b/src/main/java/com/example/devSns/service/PostService.java new file mode 100644 index 0000000..e9dce8b --- /dev/null +++ b/src/main/java/com/example/devSns/service/PostService.java @@ -0,0 +1,44 @@ +package com.example.devSns.service; + +import com.example.devSns.entity.Post; +import com.example.devSns.repository.PostRepository; +import org.springframework.stereotype.Service; + +import java.time.LocalDateTime; +import java.util.List; +import java.util.Optional; + +@Service +public class PostService { + private final PostRepository postRepository; + + public PostService(PostRepository postRepository) { + this.postRepository = postRepository; + } + + public List findAll(){ + return postRepository.findAll(); + } + + public Optional findById(Long id){ + return postRepository.findById(id); + } + + public Post save(Post post){ + return postRepository.save(post); + } + public Post updatePost(Long id, Post updatedPost) { + Post existingPost = postRepository.findById(id).orElseThrow(() -> new RuntimeException("Post not found")); + + updatedPost.setCreatedAt(existingPost.getCreatedAt()); + updatedPost.setUpdatedAt(LocalDateTime.now()); + + updatedPost.setId(id); + + return postRepository.save(updatedPost); + } + + public void delete(Long id){ + postRepository.deleteById(id); + } +} \ No newline at end of file diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f3f10af..f3fabed 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -1 +1,13 @@ spring.application.name=devSns + +# DB ???? +spring.datasource.url=${DATASOURCE_URL} +spring.datasource.username=${DATASOURCE_USERNAME} +spring.datasource.password=${DATASOURCE_PASSWORD} +# DB ???? +spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver + +spring.jpa.hibernate.ddl-auto=update +# true? ???? ?? ???? sql?? ???? ??? ? ????. +spring.jpa.show-sql=true +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect \ No newline at end of file From 611ebd14de0c1b5bd8ccb40916dcf1d04c2ee109 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=A7=80=EC=9B=90?= Date: Fri, 7 Nov 2025 17:30:53 +0900 Subject: [PATCH 3/5] feat: week 2 comment update --- .DS_Store | Bin 0 -> 6148 bytes build.gradle | 7 ++ .../devSns/controller/CommentController.java | 14 ++- .../devSns/controller/PostController.java | 7 +- .../com/example/devSns/entity/Comment.java | 15 ++- .../java/com/example/devSns/entity/Post.java | 9 +- .../devSns/service/CommentService.java | 14 ++- .../example/devSns/service/PostService.java | 15 +-- .../example/devSns/CommentControllerTest.java | 107 ++++++++++++++++++ .../example/devSns/PostControllerTest.java | 92 +++++++++++++++ 10 files changed, 261 insertions(+), 19 deletions(-) create mode 100644 .DS_Store create mode 100644 src/test/java/com/example/devSns/CommentControllerTest.java create mode 100644 src/test/java/com/example/devSns/PostControllerTest.java diff --git a/.DS_Store b/.DS_Store new file mode 100644 index 0000000000000000000000000000000000000000..66c495cad1e192f5b59f8080838d661ea3faf602 GIT binary patch literal 6148 zcmeHK%}T>S5Z<+|-BN@c6nb3nTCmnqEM7vaFJMFuDm5VmgE3p$gd9pCXMG``#OHBl zcUuVNETS_o`_0bJX7fRIGmJ6r<HHvmnb?Ua-8!*^N?3xV3}u zHl7zV=kQXcSrDi5r7no0Iix(?#c8ApPvvQ}(zU(`h>qyYoZfmp8lLy%a5CQX<$5w4 z^yTPmyxDZb(ecT}_3R~iP1TzrlH<-%4h-zZ_nk!sF+dCu1H=F^P{)A1K}5GMNIqhK z82Hx=;Q1gy5nYR=L49<F42ZO*J5c9S3$on2c#bXO$ZId Izz;C+1pr<}VgLXD literal 0 HcmV?d00001 diff --git a/build.gradle b/build.gradle index cf03320..0527f19 100644 --- a/build.gradle +++ b/build.gradle @@ -33,6 +33,13 @@ dependencies { annotationProcessor 'org.projectlombok:lombok' testCompileOnly 'org.projectlombok:lombok' testAnnotationProcessor 'org.projectlombok:lombok' + + // test + testImplementation 'org.springframework.boot:spring-boot-starter-test' + +} +test{ + useJUnitPlatform() } configurations { diff --git a/src/main/java/com/example/devSns/controller/CommentController.java b/src/main/java/com/example/devSns/controller/CommentController.java index a040745..71ed3ae 100644 --- a/src/main/java/com/example/devSns/controller/CommentController.java +++ b/src/main/java/com/example/devSns/controller/CommentController.java @@ -2,11 +2,13 @@ import com.example.devSns.entity.Comment; import com.example.devSns.service.CommentService; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; +import java.util.Map; @RestController -@RequestMapping("post/{postId}/comment") +@RequestMapping("/post/{postId}/comment") public class CommentController { private CommentService commentService; @@ -28,4 +30,14 @@ public Comment createComment(@PathVariable Long postId, @RequestBody Comment com public void deleteComment(@PathVariable Long commentId) { commentService.deleteComment(commentId); } + + @PatchMapping("/{commentId}") + public ResponseEntity updateComment( + @PathVariable Long commentId, + @RequestBody Map request + ) { + String newContent = request.get("content"); + Comment updated = commentService.updateComment(commentId, newContent); + return ResponseEntity.ok(updated); + } } \ No newline at end of file diff --git a/src/main/java/com/example/devSns/controller/PostController.java b/src/main/java/com/example/devSns/controller/PostController.java index 1a2a41f..d279035 100644 --- a/src/main/java/com/example/devSns/controller/PostController.java +++ b/src/main/java/com/example/devSns/controller/PostController.java @@ -2,10 +2,12 @@ import com.example.devSns.entity.Post; import com.example.devSns.service.PostService; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.time.LocalDateTime; import java.util.List; +import java.util.Optional; @RestController @RequestMapping("/post") @@ -22,8 +24,9 @@ public List getAllPosts(){ } @GetMapping("/{id}") - public Post getPostById(@PathVariable Long id){ - return postService.findById(id).orElseThrow(()-> new RuntimeException("post not found")); + public ResponseEntity getPostById(@PathVariable Long id){ + Post post = postService.findById(id); + return ResponseEntity.ok(post); } @PostMapping diff --git a/src/main/java/com/example/devSns/entity/Comment.java b/src/main/java/com/example/devSns/entity/Comment.java index 4eb30dc..5ead634 100644 --- a/src/main/java/com/example/devSns/entity/Comment.java +++ b/src/main/java/com/example/devSns/entity/Comment.java @@ -7,7 +7,6 @@ @Entity @Getter -@Setter @NoArgsConstructor @AllArgsConstructor @Builder @@ -20,8 +19,12 @@ public class Comment{ private String username; private LocalDateTime createdAt; - @ManyToOne(fetch = FetchType.LAZY) - @JoinColumn(name="post_id") + @ManyToOne(fetch = FetchType.LAZY, optional = false) + @JoinColumn( + name="post_id", + nullable = false, + foreignKey = @ForeignKey(name = "fk_diary_user_id_ref_user_id") + ) @JsonBackReference private Post post; @@ -29,4 +32,10 @@ public class Comment{ public void onCreate(){ createdAt = LocalDateTime.now(); } + public void update(String content){ + this.content = content; + } + public void assignTo(Post post){ + this.post = post; + } } \ No newline at end of file diff --git a/src/main/java/com/example/devSns/entity/Post.java b/src/main/java/com/example/devSns/entity/Post.java index 143a980..645b299 100644 --- a/src/main/java/com/example/devSns/entity/Post.java +++ b/src/main/java/com/example/devSns/entity/Post.java @@ -10,7 +10,6 @@ @Entity @Getter -@Setter @NoArgsConstructor @AllArgsConstructor @Builder @@ -28,6 +27,7 @@ public class Post { @OneToMany(mappedBy = "post", cascade = CascadeType.ALL,orphanRemoval = true) @JsonManagedReference private List comments = new ArrayList<>(); + @PrePersist public void onCreate(){ createdAt = LocalDateTime.now(); @@ -38,4 +38,11 @@ public void onCreate(){ public void onUpdate(){ updatedAt = LocalDateTime.now(); } + public void update(String content){ + this.content = content; + } + public void addComment(Comment comment){ + comments.add(comment); + comment.assignTo(this); + } } \ No newline at end of file diff --git a/src/main/java/com/example/devSns/service/CommentService.java b/src/main/java/com/example/devSns/service/CommentService.java index c2e8157..4414e49 100644 --- a/src/main/java/com/example/devSns/service/CommentService.java +++ b/src/main/java/com/example/devSns/service/CommentService.java @@ -5,6 +5,8 @@ import com.example.devSns.repository.CommentRepository; import com.example.devSns.repository.PostRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + import java.util.List; @Service @@ -17,13 +19,21 @@ public CommentService(CommentRepository commentRepository, PostRepository postRe this.postRepository = postRepository; } + @Transactional(readOnly = true) public List getCommentByPost(Long postId){ return commentRepository.findByPost_Id(postId); } public Comment addComment(Long postId, Comment comment){ - Post post = postRepository.findById(postId).orElseThrow(()-> new IllegalArgumentException("게시글 없음")); - comment.setPost(post); + Post post = postRepository.findById(postId).orElseThrow(()-> new IllegalArgumentException("post not found")); + + post.addComment(comment); + postRepository.save(post); + return commentRepository.save(comment); + } + public Comment updateComment(Long commentId, String newContent){ + Comment comment = commentRepository.findById(commentId).orElseThrow(() -> new IllegalArgumentException("comment not found")); + comment.update(newContent); return commentRepository.save(comment); } diff --git a/src/main/java/com/example/devSns/service/PostService.java b/src/main/java/com/example/devSns/service/PostService.java index e9dce8b..ddedba3 100644 --- a/src/main/java/com/example/devSns/service/PostService.java +++ b/src/main/java/com/example/devSns/service/PostService.java @@ -20,8 +20,8 @@ public List findAll(){ return postRepository.findAll(); } - public Optional findById(Long id){ - return postRepository.findById(id); + public Post findById(Long id){ + return postRepository.findById(id).orElseThrow(()-> new IllegalArgumentException("Post not found")); } public Post save(Post post){ @@ -29,16 +29,11 @@ public Post save(Post post){ } public Post updatePost(Long id, Post updatedPost) { Post existingPost = postRepository.findById(id).orElseThrow(() -> new RuntimeException("Post not found")); - - updatedPost.setCreatedAt(existingPost.getCreatedAt()); - updatedPost.setUpdatedAt(LocalDateTime.now()); - - updatedPost.setId(id); - - return postRepository.save(updatedPost); + existingPost.update(updatedPost.getContent()); + return postRepository.save(existingPost); } public void delete(Long id){ postRepository.deleteById(id); } -} \ No newline at end of file +} diff --git a/src/test/java/com/example/devSns/CommentControllerTest.java b/src/test/java/com/example/devSns/CommentControllerTest.java new file mode 100644 index 0000000..b648b36 --- /dev/null +++ b/src/test/java/com/example/devSns/CommentControllerTest.java @@ -0,0 +1,107 @@ +package com.example.devSns; + +import com.example.devSns.entity.Comment; +import com.example.devSns.entity.Post; +import com.example.devSns.repository.CommentRepository; +import com.example.devSns.repository.PostRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +class CommentControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private PostRepository postRepository; + + @Autowired + private CommentRepository commentRepository; + + private Post savedPost; + + @BeforeEach + void setUp() { + commentRepository.deleteAll(); + postRepository.deleteAll(); + + savedPost = postRepository.save(Post.builder() + .username("dardar") + .content("댓글 테스트용 게시글") + .build()); + } + + @Test + void 댓글_작성_성공() throws Exception { + String json = """ + { + "username": "tester", + "content": "좋은 글이네요!" + } + """; + + mockMvc.perform(post("/post/" + savedPost.getId() + "/comment") + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.username").value("tester")) + .andExpect(jsonPath("$.content").value("좋은 글이네요!")); + } + + @Test + void 댓글_조회_성공() throws Exception { + Comment comment = commentRepository.save(Comment.builder() + .username("tester") + .content("조회용 댓글") + .post(savedPost) + .build()); + + mockMvc.perform(get("/post/" + savedPost.getId() + "/comment")) + .andExpect(status().isOk()) + .andExpect(jsonPath("$[0].content").value("조회용 댓글")) + .andExpect(jsonPath("$[0].username").value("tester")); + } + + @Test + void 댓글_수정_성공() throws Exception { + Comment comment = commentRepository.save(Comment.builder() + .username("tester") + .content("원본 댓글") + .post(savedPost) + .build()); + + String updateJson = """ + { + "content": "수정된 댓글입니다." + } + """; + + mockMvc.perform(patch("/post/" + savedPost.getId() + "/comment/" + comment.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(updateJson)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.content").value("수정된 댓글입니다.")); + } + + @Test + void 댓글_삭제_성공() throws Exception { + Comment comment = commentRepository.save(Comment.builder() + .username("tester") + .content("삭제용 댓글") + .post(savedPost) + .build()); + + mockMvc.perform(delete("/post/" + savedPost.getId() + "/comment/" + comment.getId())) + .andExpect(status().isOk()); + } +} diff --git a/src/test/java/com/example/devSns/PostControllerTest.java b/src/test/java/com/example/devSns/PostControllerTest.java new file mode 100644 index 0000000..ffe8f6a --- /dev/null +++ b/src/test/java/com/example/devSns/PostControllerTest.java @@ -0,0 +1,92 @@ +package com.example.devSns; + +import com.example.devSns.entity.Post; +import com.example.devSns.repository.PostRepository; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.http.MediaType; +import org.springframework.test.web.servlet.MockMvc; + +import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; +import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; + +@SpringBootTest +@AutoConfigureMockMvc +class PostControllerTest { + + @Autowired + private MockMvc mockMvc; + + @Autowired + private PostRepository postRepository; + + @BeforeEach + void cleanDB() { + postRepository.deleteAll(); + } + + @Test + void 게시글_생성_성공() throws Exception { + String json = """ + { + "username": "dardar", + "content": "테스트 게시글입니다." + } + """; + + mockMvc.perform(post("/post") + .contentType(MediaType.APPLICATION_JSON) + .content(json)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.username").value("dardar")) + .andExpect(jsonPath("$.content").value("테스트 게시글입니다.")); + } + + @Test + void 게시글_조회_성공() throws Exception { + Post post = Post.builder() + .username("tester") + .content("조회용 게시글") + .build(); + postRepository.save(post); + + mockMvc.perform(get("/post/" + post.getId())) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.username").value("tester")) + .andExpect(jsonPath("$.content").value("조회용 게시글")); + } + + @Test + void 게시글_수정_성공() throws Exception { + Post post = postRepository.save(Post.builder() + .username("tester") + .content("원본 내용") + .build()); + + String updateJson = """ + { + "content": "수정된 내용" + } + """; + + mockMvc.perform(put("/post/" + post.getId()) + .contentType(MediaType.APPLICATION_JSON) + .content(updateJson)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.content").value("수정된 내용")); + } + + @Test + void 게시글_삭제_성공() throws Exception { + Post post = postRepository.save(Post.builder() + .username("tester") + .content("삭제 대상 게시글") + .build()); + + mockMvc.perform(delete("/post/" + post.getId())) + .andExpect(status().isOk()); + } +} From 549984927c86e9b43660fe8c881ecb466e3706b9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=A7=80=EC=9B=90?= Date: Fri, 14 Nov 2025 16:11:22 +0900 Subject: [PATCH 4/5] =?UTF-8?q?feat/week-3:=20dto,=20member,=20like=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 6148 -> 6148 bytes .../controller/CommentActionController.java | 31 ++++ .../devSns/controller/CommentController.java | 31 ++-- .../devSns/controller/LikeController.java | 29 ++++ .../devSns/controller/MemberController.java | 52 +++++++ .../devSns/controller/PostController.java | 25 ++-- .../devSns/dto/CommentCreateRequest.java | 12 ++ .../example/devSns/dto/CommentResponse.java | 21 +++ .../devSns/dto/CommentUpdateRequest.java | 10 ++ .../com/example/devSns/dto/LikeResponse.java | 12 ++ .../example/devSns/dto/LikeToggleRequest.java | 11 ++ .../example/devSns/dto/MemberJoinRequest.java | 17 +++ .../example/devSns/dto/MemberResponse.java | 17 +++ .../example/devSns/dto/PostCreateRequest.java | 11 ++ .../com/example/devSns/dto/PostResponse.java | 30 ++++ .../example/devSns/dto/PostUpdateRequest.java | 10 ++ .../com/example/devSns/entity/Comment.java | 20 ++- .../java/com/example/devSns/entity/Like.java | 55 +++++++ .../com/example/devSns/entity/Member.java | 51 +++++++ .../java/com/example/devSns/entity/Post.java | 33 ++++- .../exception/GlobalExceptionHandler.java | 16 ++ .../devSns/repository/LikeRepository.java | 13 ++ .../devSns/repository/MemberRepository.java | 13 ++ .../devSns/service/CommentService.java | 22 ++- .../example/devSns/service/LikeService.java | 44 ++++++ .../example/devSns/service/MemberService.java | 78 ++++++++++ .../example/devSns/service/PostService.java | 34 ++++- .../example/devSns/CommentControllerTest.java | 82 ++++++----- .../com/example/devSns/LikeServiceTest.java | 70 +++++++++ .../com/example/devSns/MemberServiceTest.java | 139 ++++++++++++++++++ .../example/devSns/PostControllerTest.java | 73 +++++---- 31 files changed, 955 insertions(+), 107 deletions(-) create mode 100644 src/main/java/com/example/devSns/controller/CommentActionController.java create mode 100644 src/main/java/com/example/devSns/controller/LikeController.java create mode 100644 src/main/java/com/example/devSns/controller/MemberController.java create mode 100644 src/main/java/com/example/devSns/dto/CommentCreateRequest.java create mode 100644 src/main/java/com/example/devSns/dto/CommentResponse.java create mode 100644 src/main/java/com/example/devSns/dto/CommentUpdateRequest.java create mode 100644 src/main/java/com/example/devSns/dto/LikeResponse.java create mode 100644 src/main/java/com/example/devSns/dto/LikeToggleRequest.java create mode 100644 src/main/java/com/example/devSns/dto/MemberJoinRequest.java create mode 100644 src/main/java/com/example/devSns/dto/MemberResponse.java create mode 100644 src/main/java/com/example/devSns/dto/PostCreateRequest.java create mode 100644 src/main/java/com/example/devSns/dto/PostResponse.java create mode 100644 src/main/java/com/example/devSns/dto/PostUpdateRequest.java create mode 100644 src/main/java/com/example/devSns/entity/Like.java create mode 100644 src/main/java/com/example/devSns/entity/Member.java create mode 100644 src/main/java/com/example/devSns/exception/GlobalExceptionHandler.java create mode 100644 src/main/java/com/example/devSns/repository/LikeRepository.java create mode 100644 src/main/java/com/example/devSns/repository/MemberRepository.java create mode 100644 src/main/java/com/example/devSns/service/LikeService.java create mode 100644 src/main/java/com/example/devSns/service/MemberService.java create mode 100644 src/test/java/com/example/devSns/LikeServiceTest.java create mode 100644 src/test/java/com/example/devSns/MemberServiceTest.java diff --git a/.DS_Store b/.DS_Store index 66c495cad1e192f5b59f8080838d661ea3faf602..e21eb369ee5ed21bccb54abd7effa1711b4d0e64 100644 GIT binary patch delta 21 ccmZoMXffC@i;=_F#8^kc*wSS4BE}>!07mx)5C8xG delta 21 ccmZoMXffC@i;=^`$U;ZK$kJ@{BE}>!07oYV6#xJL diff --git a/src/main/java/com/example/devSns/controller/CommentActionController.java b/src/main/java/com/example/devSns/controller/CommentActionController.java new file mode 100644 index 0000000..dcf2f16 --- /dev/null +++ b/src/main/java/com/example/devSns/controller/CommentActionController.java @@ -0,0 +1,31 @@ +package com.example.devSns.controller; + +import com.example.devSns.dto.CommentResponse; +import com.example.devSns.dto.CommentUpdateRequest; +import com.example.devSns.entity.Comment; +import com.example.devSns.service.CommentService; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/comments") +public class CommentActionController { + private final CommentService commentService; + + public CommentActionController(CommentService commentService){ + this.commentService = commentService; + } + @DeleteMapping("/{commentId}") + public void deleteComment(@PathVariable Long commentId) { + commentService.deleteComment(commentId); + } + + @PatchMapping("/{commentId}") + public ResponseEntity updateComment( + @PathVariable Long commentId, + @RequestBody CommentUpdateRequest request + ) { + Comment updated = commentService.updateComment(commentId, request.getContent()); + return ResponseEntity.ok(new CommentResponse(updated)); + } +} diff --git a/src/main/java/com/example/devSns/controller/CommentController.java b/src/main/java/com/example/devSns/controller/CommentController.java index 71ed3ae..0add7be 100644 --- a/src/main/java/com/example/devSns/controller/CommentController.java +++ b/src/main/java/com/example/devSns/controller/CommentController.java @@ -1,5 +1,8 @@ package com.example.devSns.controller; +import com.example.devSns.dto.CommentCreateRequest; +import com.example.devSns.dto.CommentResponse; +import com.example.devSns.dto.CommentUpdateRequest; import com.example.devSns.entity.Comment; import com.example.devSns.service.CommentService; import org.springframework.http.ResponseEntity; @@ -8,36 +11,26 @@ import java.util.Map; @RestController -@RequestMapping("/post/{postId}/comment") +@RequestMapping("/posts/{postId}/comments") public class CommentController { - private CommentService commentService; + private final CommentService commentService; public CommentController(CommentService commentService) { this.commentService = commentService; } @GetMapping - public List getComments(@PathVariable Long postId) { - return commentService.getCommentByPost(postId); + public List getComments(@PathVariable Long postId) { + return commentService.getCommentByPost(postId).stream() + .map(CommentResponse::new) + .toList(); } @PostMapping - public Comment createComment(@PathVariable Long postId, @RequestBody Comment comment) { - return commentService.addComment(postId, comment); + public CommentResponse createComment(@PathVariable Long postId, @RequestBody CommentCreateRequest request) { + Comment created = commentService.addComment(postId, request); + return new CommentResponse(created); } - @DeleteMapping("/{commentId}") - public void deleteComment(@PathVariable Long commentId) { - commentService.deleteComment(commentId); - } - @PatchMapping("/{commentId}") - public ResponseEntity updateComment( - @PathVariable Long commentId, - @RequestBody Map request - ) { - String newContent = request.get("content"); - Comment updated = commentService.updateComment(commentId, newContent); - return ResponseEntity.ok(updated); - } } \ No newline at end of file diff --git a/src/main/java/com/example/devSns/controller/LikeController.java b/src/main/java/com/example/devSns/controller/LikeController.java new file mode 100644 index 0000000..d12e813 --- /dev/null +++ b/src/main/java/com/example/devSns/controller/LikeController.java @@ -0,0 +1,29 @@ +package com.example.devSns.controller; + +import com.example.devSns.dto.LikeResponse; +import com.example.devSns.dto.LikeToggleRequest; +import com.example.devSns.service.LikeService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/likes") +public class LikeController { + private final LikeService likeService; + + @PostMapping("/toggle") + public ResponseEntity toggleLike(@RequestBody LikeToggleRequest request){ + likeService.toggleLike(request.getMemberId(), request.getPostId()); + long count = likeService.getLikeCount(request.getPostId()); + return ResponseEntity.ok(new LikeResponse(request.getPostId(),count,true)); + } + + @GetMapping("/count/{postId}") + public ResponseEntity getLikeCount(@PathVariable Long postId){ + long count = likeService.getLikeCount(postId); + return ResponseEntity.ok(count); + } + +} diff --git a/src/main/java/com/example/devSns/controller/MemberController.java b/src/main/java/com/example/devSns/controller/MemberController.java new file mode 100644 index 0000000..d7be09f --- /dev/null +++ b/src/main/java/com/example/devSns/controller/MemberController.java @@ -0,0 +1,52 @@ +package com.example.devSns.controller; + +import com.example.devSns.dto.CommentResponse; +import com.example.devSns.dto.MemberJoinRequest; +import com.example.devSns.dto.MemberResponse; +import com.example.devSns.dto.PostResponse; +import com.example.devSns.entity.Member; +import com.example.devSns.service.MemberService; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/members") +public class MemberController { + private final MemberService memberService; + + @GetMapping("/{id}") + public MemberResponse getMember(@PathVariable Long id) { + Member member = memberService.findMemberById(id); + return new MemberResponse(member); + } + + @GetMapping("/search") + public List search(@RequestParam String keyword) { + return memberService.searchMembers(keyword).stream().map(MemberResponse::new).toList(); + } + + @GetMapping("/{id}/posts") + public List getMemberPosts(@PathVariable Long id) { + return memberService.getPostsByMember(id); + } + + @GetMapping("/{id}/comments") + public List getMemberComments(@PathVariable Long id) { + return memberService.getCommentsByMember(id); + } + + @GetMapping("/{id}/likes") + public List getMemberLikes(@PathVariable Long id) { + return memberService.getLikedPosts(id); + } + + @PostMapping + public MemberResponse createMember(@RequestBody MemberJoinRequest request){ + Member member = memberService.join(request.toEntity()); + return new MemberResponse(member); + } + +} diff --git a/src/main/java/com/example/devSns/controller/PostController.java b/src/main/java/com/example/devSns/controller/PostController.java index d279035..81eead8 100644 --- a/src/main/java/com/example/devSns/controller/PostController.java +++ b/src/main/java/com/example/devSns/controller/PostController.java @@ -1,7 +1,11 @@ package com.example.devSns.controller; +import com.example.devSns.dto.PostCreateRequest; +import com.example.devSns.dto.PostResponse; +import com.example.devSns.dto.PostUpdateRequest; import com.example.devSns.entity.Post; import com.example.devSns.service.PostService; +import jakarta.persistence.PostUpdate; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; @@ -19,25 +23,26 @@ public PostController(PostService postService) { } @GetMapping - public List getAllPosts(){ - return postService.findAll(); + public List getAllPosts(){ + return postService.findAll().stream().map(PostResponse::new).toList(); } @GetMapping("/{id}") - public ResponseEntity getPostById(@PathVariable Long id){ + public ResponseEntity getPostById(@PathVariable Long id){ Post post = postService.findById(id); - return ResponseEntity.ok(post); + return ResponseEntity.ok(new PostResponse(post)); } @PostMapping - public Post createPost(@RequestBody Post post){ - return postService.save(post); + public PostResponse createPost(@RequestBody PostCreateRequest request){ + Post created = postService.createPost(request); + return new PostResponse(created); } - @PutMapping("/{id}") - public Post updatePost(@PathVariable Long id, @RequestBody Post updatedPost){ - - return postService.updatePost(id,updatedPost); + @PatchMapping("/{id}") + public PostResponse updatePost(@PathVariable Long id, @RequestBody PostUpdateRequest request){ + Post updated = postService.updatePost(id, request); + return new PostResponse(updated); } @DeleteMapping("/{id}") diff --git a/src/main/java/com/example/devSns/dto/CommentCreateRequest.java b/src/main/java/com/example/devSns/dto/CommentCreateRequest.java new file mode 100644 index 0000000..085e7cc --- /dev/null +++ b/src/main/java/com/example/devSns/dto/CommentCreateRequest.java @@ -0,0 +1,12 @@ +package com.example.devSns.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class CommentCreateRequest { + private String content; + private String username; + private Long memberId; +} diff --git a/src/main/java/com/example/devSns/dto/CommentResponse.java b/src/main/java/com/example/devSns/dto/CommentResponse.java new file mode 100644 index 0000000..c28db4a --- /dev/null +++ b/src/main/java/com/example/devSns/dto/CommentResponse.java @@ -0,0 +1,21 @@ +package com.example.devSns.dto; + +import com.example.devSns.entity.Comment; + +import java.time.LocalDateTime; + +public record CommentResponse( + Long id, + String content, + String username, + LocalDateTime createdAt +){ + public CommentResponse(Comment comment){ + this( + comment.getId(), + comment.getContent(), + comment.getUsername(), + comment.getCreatedAt() + ); + } +} diff --git a/src/main/java/com/example/devSns/dto/CommentUpdateRequest.java b/src/main/java/com/example/devSns/dto/CommentUpdateRequest.java new file mode 100644 index 0000000..cf910c3 --- /dev/null +++ b/src/main/java/com/example/devSns/dto/CommentUpdateRequest.java @@ -0,0 +1,10 @@ +package com.example.devSns.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class CommentUpdateRequest { + private String content; +} diff --git a/src/main/java/com/example/devSns/dto/LikeResponse.java b/src/main/java/com/example/devSns/dto/LikeResponse.java new file mode 100644 index 0000000..00c62b4 --- /dev/null +++ b/src/main/java/com/example/devSns/dto/LikeResponse.java @@ -0,0 +1,12 @@ +package com.example.devSns.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class LikeResponse { + private Long PostId; + private long likeCount; + private boolean liked; +} diff --git a/src/main/java/com/example/devSns/dto/LikeToggleRequest.java b/src/main/java/com/example/devSns/dto/LikeToggleRequest.java new file mode 100644 index 0000000..234ca45 --- /dev/null +++ b/src/main/java/com/example/devSns/dto/LikeToggleRequest.java @@ -0,0 +1,11 @@ +package com.example.devSns.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class LikeToggleRequest { + private Long memberId; + private Long postId; +} diff --git a/src/main/java/com/example/devSns/dto/MemberJoinRequest.java b/src/main/java/com/example/devSns/dto/MemberJoinRequest.java new file mode 100644 index 0000000..83ff098 --- /dev/null +++ b/src/main/java/com/example/devSns/dto/MemberJoinRequest.java @@ -0,0 +1,17 @@ +package com.example.devSns.dto; + +import com.example.devSns.entity.Member; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class MemberJoinRequest { + private String username; + private String email; + private String password; + + public Member toEntity(){ + return Member.create(username, email, password); + } +} diff --git a/src/main/java/com/example/devSns/dto/MemberResponse.java b/src/main/java/com/example/devSns/dto/MemberResponse.java new file mode 100644 index 0000000..308147c --- /dev/null +++ b/src/main/java/com/example/devSns/dto/MemberResponse.java @@ -0,0 +1,17 @@ +package com.example.devSns.dto; + +import com.example.devSns.entity.Member; +import lombok.Getter; + +@Getter +public class MemberResponse { + private Long id; + private String username; + private String email; + + public MemberResponse(Member member){ + this.id = member.getId(); + this.username = member.getUsername(); + this.email = member.getEmail(); + } +} diff --git a/src/main/java/com/example/devSns/dto/PostCreateRequest.java b/src/main/java/com/example/devSns/dto/PostCreateRequest.java new file mode 100644 index 0000000..be9ca00 --- /dev/null +++ b/src/main/java/com/example/devSns/dto/PostCreateRequest.java @@ -0,0 +1,11 @@ +package com.example.devSns.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class PostCreateRequest { + private String content; + private Long memberId; +} diff --git a/src/main/java/com/example/devSns/dto/PostResponse.java b/src/main/java/com/example/devSns/dto/PostResponse.java new file mode 100644 index 0000000..5b6aec1 --- /dev/null +++ b/src/main/java/com/example/devSns/dto/PostResponse.java @@ -0,0 +1,30 @@ +package com.example.devSns.dto; + +import com.example.devSns.entity.Post; +import lombok.Getter; + +import java.time.LocalDateTime; + +@Getter +public class PostResponse { + + private Long id; + private Long memberId; + private String content; + private String username; + private LocalDateTime createdAt; + private LocalDateTime updatedAt; + private int likeCount; + private int commentCount; + + public PostResponse(Post post) { + this.id = post.getId(); + this.memberId = post.getMember() != null ? post.getMember().getId() : null; + this.content = post.getContent(); + this.username = post.getUsername(); + this.createdAt = post.getCreatedAt(); + this.updatedAt = post.getUpdatedAt(); + this.likeCount = post.getLikes().size(); // 리스트 -> size() + this.commentCount = post.getComments().size(); + } +} diff --git a/src/main/java/com/example/devSns/dto/PostUpdateRequest.java b/src/main/java/com/example/devSns/dto/PostUpdateRequest.java new file mode 100644 index 0000000..e9bc144 --- /dev/null +++ b/src/main/java/com/example/devSns/dto/PostUpdateRequest.java @@ -0,0 +1,10 @@ +package com.example.devSns.dto; + +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +public class PostUpdateRequest { + private String content; +} diff --git a/src/main/java/com/example/devSns/entity/Comment.java b/src/main/java/com/example/devSns/entity/Comment.java index 5ead634..11f34d7 100644 --- a/src/main/java/com/example/devSns/entity/Comment.java +++ b/src/main/java/com/example/devSns/entity/Comment.java @@ -22,12 +22,14 @@ public class Comment{ @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn( name="post_id", - nullable = false, - foreignKey = @ForeignKey(name = "fk_diary_user_id_ref_user_id") + nullable = false ) - @JsonBackReference private Post post; + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + @PrePersist public void onCreate(){ createdAt = LocalDateTime.now(); @@ -38,4 +40,16 @@ public void update(String content){ public void assignTo(Post post){ this.post = post; } + public void assignMember(Member member){ + this.member = member; + member.addComment(this); + } + public static Comment create(String content, Member member, Post post) { + return Comment.builder() + .content(content) + .username(member.getUsername()) + .member(member) + .post(post) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/com/example/devSns/entity/Like.java b/src/main/java/com/example/devSns/entity/Like.java new file mode 100644 index 0000000..fa5a0dd --- /dev/null +++ b/src/main/java/com/example/devSns/entity/Like.java @@ -0,0 +1,55 @@ +package com.example.devSns.entity; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Table( + name = "likes", + uniqueConstraints = @UniqueConstraint(columnNames ={"member_id","post_id"} ) +) +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Like { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id",nullable = false) + private Member member; + + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "post_id",nullable = false) + private Post post; + + private LocalDateTime CreatedAt; + + private Like(Member member, Post post){ + this.member = member; + this.post = post; + this.CreatedAt = LocalDateTime.now(); + } + + public static Like create(Member member, Post post){ + Like like = new Like(member, post); + member.addLike(like); + return like; + } + + public void assignMember(Member member) { + this.member = member; + member.addLike(this); + } + + public void assignPost(Post post) { + this.post = post; + post.addLike(this); + + } + +} diff --git a/src/main/java/com/example/devSns/entity/Member.java b/src/main/java/com/example/devSns/entity/Member.java new file mode 100644 index 0000000..37ed019 --- /dev/null +++ b/src/main/java/com/example/devSns/entity/Member.java @@ -0,0 +1,51 @@ +package com.example.devSns.entity; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.util.ArrayList; +import java.util.List; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Member { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String username; + private String email; + private String password; + + @OneToMany(mappedBy = "member") + private List posts = new ArrayList<>(); + + @OneToMany(mappedBy = "member") + private List comments = new ArrayList<>(); + + @OneToMany(mappedBy = "member") + private List likes = new ArrayList<>(); + + private Member (String username, String email, String password){ + this.username = username; + this.email = email; + this.password = password; + } + + public static Member create(String username, String email, String password){ + return new Member(username, email, password); + } + + public void addPost(Post post) { + posts.add(post); + } + public void addComment(Comment comment) { + comments.add(comment); + } + public void addLike(Like like) { + likes.add(like); + } +} diff --git a/src/main/java/com/example/devSns/entity/Post.java b/src/main/java/com/example/devSns/entity/Post.java index 645b299..0be76de 100644 --- a/src/main/java/com/example/devSns/entity/Post.java +++ b/src/main/java/com/example/devSns/entity/Post.java @@ -12,22 +12,26 @@ @Getter @NoArgsConstructor @AllArgsConstructor -@Builder public class Post { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; private String content; - private int likes; private String username; private LocalDateTime createdAt; private LocalDateTime updatedAt; + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL) + private List likes = new ArrayList<>(); + @OneToMany(mappedBy = "post", cascade = CascadeType.ALL,orphanRemoval = true) - @JsonManagedReference private List comments = new ArrayList<>(); + @ManyToOne(fetch = FetchType.LAZY) + @JoinColumn(name = "member_id") + private Member member; + @PrePersist public void onCreate(){ createdAt = LocalDateTime.now(); @@ -45,4 +49,27 @@ public void addComment(Comment comment){ comments.add(comment); comment.assignTo(this); } + + private Post(String content, Member member){ + this.content = content; + this.member = member; + this.createdAt = LocalDateTime.now(); + this.updatedAt = LocalDateTime.now(); + + member.addPost(this); + } + + public static Post create(String content, Member member){ + return new Post(content,member); + } + + public void updateContent(String newContent){ + this.content = newContent; + this.updatedAt = LocalDateTime.now(); + } + + public void addLike(Like like) { + likes.add(like); + } + } \ No newline at end of file diff --git a/src/main/java/com/example/devSns/exception/GlobalExceptionHandler.java b/src/main/java/com/example/devSns/exception/GlobalExceptionHandler.java new file mode 100644 index 0000000..134135b --- /dev/null +++ b/src/main/java/com/example/devSns/exception/GlobalExceptionHandler.java @@ -0,0 +1,16 @@ +package com.example.devSns.exception; + +import org.springframework.http.HttpStatus; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.ControllerAdvice; +import org.springframework.web.bind.annotation.ExceptionHandler; + +@ControllerAdvice +public class GlobalExceptionHandler { + + @ExceptionHandler(IllegalArgumentException.class) + public ResponseEntity handleIllegalArgument(IllegalArgumentException e) { + return ResponseEntity.status(HttpStatus.NOT_FOUND) + .body(e.getMessage()); + } +} diff --git a/src/main/java/com/example/devSns/repository/LikeRepository.java b/src/main/java/com/example/devSns/repository/LikeRepository.java new file mode 100644 index 0000000..7b61102 --- /dev/null +++ b/src/main/java/com/example/devSns/repository/LikeRepository.java @@ -0,0 +1,13 @@ +package com.example.devSns.repository; + +import com.example.devSns.entity.Like; +import com.example.devSns.entity.Member; +import com.example.devSns.entity.Post; +import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + +public interface LikeRepository extends JpaRepository { + Optional findByMemberAndPost(Member member, Post post); + boolean existsByMemberAndPost(Member member, Post post); + long countByPost(Post post); +} diff --git a/src/main/java/com/example/devSns/repository/MemberRepository.java b/src/main/java/com/example/devSns/repository/MemberRepository.java new file mode 100644 index 0000000..430f7d2 --- /dev/null +++ b/src/main/java/com/example/devSns/repository/MemberRepository.java @@ -0,0 +1,13 @@ +package com.example.devSns.repository; + +import com.example.devSns.entity.Member; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; +import java.util.Optional; + +public interface MemberRepository extends JpaRepository { + Optional findByUsername(String username); + List findByUsernameContaining(String keyword); + +} diff --git a/src/main/java/com/example/devSns/service/CommentService.java b/src/main/java/com/example/devSns/service/CommentService.java index 4414e49..5ca0b84 100644 --- a/src/main/java/com/example/devSns/service/CommentService.java +++ b/src/main/java/com/example/devSns/service/CommentService.java @@ -1,9 +1,13 @@ package com.example.devSns.service; +import com.example.devSns.dto.CommentCreateRequest; import com.example.devSns.entity.Comment; +import com.example.devSns.entity.Member; import com.example.devSns.entity.Post; import com.example.devSns.repository.CommentRepository; +import com.example.devSns.repository.MemberRepository; import com.example.devSns.repository.PostRepository; +import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @@ -13,10 +17,12 @@ public class CommentService { private final CommentRepository commentRepository; private final PostRepository postRepository; + private final MemberRepository memberRepository; - public CommentService(CommentRepository commentRepository, PostRepository postRepository) { + public CommentService(CommentRepository commentRepository, PostRepository postRepository, MemberRepository memberRepository) { this.commentRepository = commentRepository; this.postRepository = postRepository; + this.memberRepository = memberRepository; } @Transactional(readOnly = true) @@ -24,11 +30,19 @@ public List getCommentByPost(Long postId){ return commentRepository.findByPost_Id(postId); } - public Comment addComment(Long postId, Comment comment){ + @Transactional + public Comment addComment(Long postId, CommentCreateRequest request){ Post post = postRepository.findById(postId).orElseThrow(()-> new IllegalArgumentException("post not found")); - + Member member = memberRepository.findById(request.getMemberId()) + .orElseThrow(() -> new IllegalArgumentException("member not found")); + + Comment comment = Comment.builder() + .content(request.getContent()) + .username(request.getUsername()) + .post(post) + .build(); post.addComment(comment); - postRepository.save(post); + comment.assignMember(member); return commentRepository.save(comment); } public Comment updateComment(Long commentId, String newContent){ diff --git a/src/main/java/com/example/devSns/service/LikeService.java b/src/main/java/com/example/devSns/service/LikeService.java new file mode 100644 index 0000000..f2fd498 --- /dev/null +++ b/src/main/java/com/example/devSns/service/LikeService.java @@ -0,0 +1,44 @@ +package com.example.devSns.service; + +import com.example.devSns.entity.Like; +import com.example.devSns.entity.Member; +import com.example.devSns.entity.Post; +import com.example.devSns.repository.LikeRepository; +import com.example.devSns.repository.MemberRepository; +import com.example.devSns.repository.PostRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.Optional; + +@Service +@RequiredArgsConstructor +public class LikeService { + private final LikeRepository likeRepository; + private final PostRepository postRepository; + private final MemberRepository memberRepository; + + @Transactional + public void toggleLike(Long memberId, Long postId){ + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new RuntimeException("member not found")); + Post post = postRepository.findById(postId) + .orElseThrow(() -> new RuntimeException("post not found")); + + Optional existingLike = likeRepository.findByMemberAndPost(member, post); + + if(existingLike.isPresent()){ + likeRepository.delete(existingLike.get()); + } else { + Like like = Like.create(member, post); + likeRepository.save(like); + } + } + + public long getLikeCount(Long postId){ + Post post = postRepository.findById(postId) + .orElseThrow(() -> new RuntimeException("post not found")); + return likeRepository.countByPost(post); + } +} diff --git a/src/main/java/com/example/devSns/service/MemberService.java b/src/main/java/com/example/devSns/service/MemberService.java new file mode 100644 index 0000000..be4597b --- /dev/null +++ b/src/main/java/com/example/devSns/service/MemberService.java @@ -0,0 +1,78 @@ +package com.example.devSns.service; + +import com.example.devSns.dto.CommentResponse; +import com.example.devSns.dto.PostResponse; +import com.example.devSns.entity.Comment; +import com.example.devSns.entity.Like; +import com.example.devSns.entity.Member; +import com.example.devSns.entity.Post; +import com.example.devSns.repository.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class MemberService { + private final MemberRepository memberRepository; + + @Transactional + public Member join(Member member) { + validateDuplicateMember(member.getUsername()); + return memberRepository.save(member); + } + + private void validateDuplicateMember(String username) { + boolean exits = memberRepository.findByUsername(username).isPresent(); + if (exits) { + throw new IllegalStateException("Already exists member "+username); + } + } + public Member findMemberById(long id) { + return memberRepository.findById(id) + .orElseThrow(() -> new RuntimeException("member not found")); + } + + public List searchMembers(String keyword){ + return memberRepository.findByUsernameContaining(keyword); + } + + public List getPostsByMember(Long memberId){ + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new IllegalArgumentException("member not found")); + + return member.getPosts().stream() + .map(PostResponse::new) + .toList(); + } + + public List getCommentsByMember(Long memberId) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new IllegalArgumentException("member not found")); + + return member.getComments().stream() + .map(CommentResponse::new) + .toList(); + } + public List getLikedPosts(Long memberId) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new IllegalArgumentException("member not found")); + + return member.getLikes().stream() + .map(like -> new PostResponse(like.getPost())) + .toList(); + } + @Transactional(readOnly = true) + public List getLikesByMember(Long memberId) { + Member member = memberRepository.findById(memberId) + .orElseThrow(() -> new IllegalArgumentException("member not found")); + + return member.getLikes(); + } + + +} + diff --git a/src/main/java/com/example/devSns/service/PostService.java b/src/main/java/com/example/devSns/service/PostService.java index ddedba3..1a4bb4f 100644 --- a/src/main/java/com/example/devSns/service/PostService.java +++ b/src/main/java/com/example/devSns/service/PostService.java @@ -1,19 +1,28 @@ package com.example.devSns.service; +import com.example.devSns.dto.PostCreateRequest; +import com.example.devSns.dto.PostResponse; +import com.example.devSns.dto.PostUpdateRequest; +import com.example.devSns.entity.Member; import com.example.devSns.entity.Post; +import com.example.devSns.repository.MemberRepository; import com.example.devSns.repository.PostRepository; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.time.LocalDateTime; import java.util.List; import java.util.Optional; @Service +@Transactional(readOnly = true) public class PostService { private final PostRepository postRepository; + private final MemberRepository memberRepository; - public PostService(PostRepository postRepository) { + public PostService(PostRepository postRepository,MemberRepository memberRepository) { this.postRepository = postRepository; + this.memberRepository = memberRepository; } public List findAll(){ @@ -24,15 +33,28 @@ public Post findById(Long id){ return postRepository.findById(id).orElseThrow(()-> new IllegalArgumentException("Post not found")); } - public Post save(Post post){ + @Transactional + public Post createPost(PostCreateRequest request){ + Member member = memberRepository.findById(request.getMemberId()) + .orElseThrow(() -> new IllegalArgumentException("member not found")); + + Post post = Post.create( + request.getContent(), + member // ✔ member 연결 + ); + return postRepository.save(post); } - public Post updatePost(Long id, Post updatedPost) { - Post existingPost = postRepository.findById(id).orElseThrow(() -> new RuntimeException("Post not found")); - existingPost.update(updatedPost.getContent()); - return postRepository.save(existingPost); + + @Transactional + public Post updatePost(Long id, PostUpdateRequest request) { + Post existingPost = postRepository.findById(id) + .orElseThrow(()-> new IllegalArgumentException("post not found")); + existingPost.updateContent(request.getContent()); + return existingPost; } + @Transactional public void delete(Long id){ postRepository.deleteById(id); } diff --git a/src/test/java/com/example/devSns/CommentControllerTest.java b/src/test/java/com/example/devSns/CommentControllerTest.java index b648b36..a17f7a5 100644 --- a/src/test/java/com/example/devSns/CommentControllerTest.java +++ b/src/test/java/com/example/devSns/CommentControllerTest.java @@ -1,8 +1,10 @@ package com.example.devSns; import com.example.devSns.entity.Comment; +import com.example.devSns.entity.Member; import com.example.devSns.entity.Post; import com.example.devSns.repository.CommentRepository; +import com.example.devSns.repository.MemberRepository; import com.example.devSns.repository.PostRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -12,6 +14,7 @@ import org.springframework.http.MediaType; import org.springframework.test.web.servlet.MockMvc; +import static org.assertj.core.api.Assertions.assertThat; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.*; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*; @@ -28,65 +31,75 @@ class CommentControllerTest { @Autowired private CommentRepository commentRepository; + @Autowired + private MemberRepository memberRepository; + private Post savedPost; + private Member savedMember; @BeforeEach void setUp() { commentRepository.deleteAll(); postRepository.deleteAll(); + memberRepository.deleteAll(); + + // 1) 멤버 생성 + savedMember = memberRepository.save( + Member.create("writer", "writer@test.com", "1234") + ); - savedPost = postRepository.save(Post.builder() - .username("dardar") - .content("댓글 테스트용 게시글") - .build()); + // 2) 게시글 생성 + savedPost = postRepository.save( + Post.create("댓글 테스트용 게시글", savedMember) + ); } @Test void 댓글_작성_성공() throws Exception { + String json = """ - { - "username": "tester", - "content": "좋은 글이네요!" - } - """; + { + "memberId": %d, + "content": "좋은 글이네요!" + } + """.formatted(savedMember.getId()); - mockMvc.perform(post("/post/" + savedPost.getId() + "/comment") + mockMvc.perform(post("/posts/" + savedPost.getId() + "/comments") .contentType(MediaType.APPLICATION_JSON) .content(json)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.username").value("tester")) - .andExpect(jsonPath("$.content").value("좋은 글이네요!")); + .andExpect(jsonPath("$.content").value("좋은 글이네요!")) + .andExpect(jsonPath("$.memberId").value(savedMember.getId())) + .andExpect(jsonPath("$.postId").value(savedPost.getId())); } @Test void 댓글_조회_성공() throws Exception { - Comment comment = commentRepository.save(Comment.builder() - .username("tester") - .content("조회용 댓글") - .post(savedPost) - .build()); - mockMvc.perform(get("/post/" + savedPost.getId() + "/comment")) + Comment comment = commentRepository.save( + Comment.create("조회용 댓글", savedMember, savedPost) + ); + + mockMvc.perform(get("/posts/" + savedPost.getId() + "/comments")) .andExpect(status().isOk()) .andExpect(jsonPath("$[0].content").value("조회용 댓글")) - .andExpect(jsonPath("$[0].username").value("tester")); + .andExpect(jsonPath("$[0].memberId").value(savedMember.getId())); } @Test void 댓글_수정_성공() throws Exception { - Comment comment = commentRepository.save(Comment.builder() - .username("tester") - .content("원본 댓글") - .post(savedPost) - .build()); + + Comment comment = commentRepository.save( + Comment.create("원본 댓글", savedMember, savedPost) + ); String updateJson = """ - { - "content": "수정된 댓글입니다." - } + { + "content": "수정된 댓글입니다." + } """; - mockMvc.perform(patch("/post/" + savedPost.getId() + "/comment/" + comment.getId()) + mockMvc.perform(patch("/comments/" + comment.getId()) .contentType(MediaType.APPLICATION_JSON) .content(updateJson)) .andExpect(status().isOk()) @@ -95,13 +108,14 @@ void setUp() { @Test void 댓글_삭제_성공() throws Exception { - Comment comment = commentRepository.save(Comment.builder() - .username("tester") - .content("삭제용 댓글") - .post(savedPost) - .build()); - mockMvc.perform(delete("/post/" + savedPost.getId() + "/comment/" + comment.getId())) + Comment comment = commentRepository.save( + Comment.create("삭제용 댓글", savedMember, savedPost) + ); + + mockMvc.perform(delete("/comments/" + comment.getId())) .andExpect(status().isOk()); + + assertThat(commentRepository.existsById(comment.getId())).isFalse(); } } diff --git a/src/test/java/com/example/devSns/LikeServiceTest.java b/src/test/java/com/example/devSns/LikeServiceTest.java new file mode 100644 index 0000000..3ae596b --- /dev/null +++ b/src/test/java/com/example/devSns/LikeServiceTest.java @@ -0,0 +1,70 @@ +package com.example.devSns; + +import com.example.devSns.entity.Member; +import com.example.devSns.entity.Post; +import com.example.devSns.repository.LikeRepository; +import com.example.devSns.repository.MemberRepository; +import com.example.devSns.repository.PostRepository; +import com.example.devSns.service.LikeService; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import static org.assertj.core.api.Assertions.assertThat; + +@SpringBootTest +@Transactional +public class LikeServiceTest { + @Autowired + private LikeService likeService; + + @Autowired + private MemberRepository memberRepository; + + @Autowired + private PostRepository postRepository; + + @Autowired + private LikeRepository likeRepository; + + private Member member; + private Post post; + + @BeforeEach + public void setup() { + member = Member.create("user1","user1@test.com","1234"); + memberRepository.save(member); + + post = Post.create("테스트 게시글",member); + postRepository.save(post); + } + + @Test + @DisplayName("멤버 게시글에 좋아요 가능") + void memberCanLikePost(){ + likeService.toggleLike(member.getId(),post.getId()); + boolean exists = likeRepository.existsByMemberAndPost(member,post); + assertThat(exists).isTrue(); + } + + @Test + @DisplayName("좋아요 두번 누르면 취소 가능") + void likeIsToggled(){ + likeService.toggleLike(member.getId(),post.getId()); + assertThat(likeRepository.countByPost(post)).isEqualTo(1); + + likeService.toggleLike(member.getId(),post.getId()); + assertThat(likeRepository.countByPost(post)).isEqualTo(0); + } + + @Test + @DisplayName("게시글의 좋아요 수 조회 가능") + void canCountLikes(){ + likeService.toggleLike(member.getId(),post.getId()); + long count = likeService.getLikeCount(post.getId()); + assertThat(count).isEqualTo(1); + } +} diff --git a/src/test/java/com/example/devSns/MemberServiceTest.java b/src/test/java/com/example/devSns/MemberServiceTest.java new file mode 100644 index 0000000..de12264 --- /dev/null +++ b/src/test/java/com/example/devSns/MemberServiceTest.java @@ -0,0 +1,139 @@ +package com.example.devSns; + +import com.example.devSns.dto.CommentResponse; +import com.example.devSns.dto.PostResponse; +import com.example.devSns.entity.*; +import com.example.devSns.repository.CommentRepository; +import com.example.devSns.repository.MemberRepository; +import com.example.devSns.repository.PostRepository; +import com.example.devSns.repository.LikeRepository; +import com.example.devSns.service.MemberService; +import com.example.devSns.service.LikeService; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +import static org.assertj.core.api.Assertions.*; + +@SpringBootTest +@Transactional +class MemberServiceTest { + + @Autowired MemberService memberService; + @Autowired MemberRepository memberRepository; + + @Autowired PostRepository postRepository; + @Autowired CommentRepository commentRepository; + @Autowired LikeRepository likeRepository; + + @Autowired LikeService likeService; + + @Test + @DisplayName("회원 가입 성공") + void 회원가입_성공() { + Member member = Member.create("강지원", "test@test.com", "1234"); + + memberService.join(member); + + Member found = memberRepository.findByUsername("강지원") + .orElseThrow(); + + assertThat(found.getEmail()).isEqualTo("test@test.com"); + } + + @Test + @DisplayName("중복 아이디 가입 예외") + void 중복회원_예외() { + Member m1 = Member.create("강지원", "a@test.com", "1111"); + Member m2 = Member.create("강지원", "b@test.com", "2222"); + + memberService.join(m1); + + assertThatThrownBy(() -> memberService.join(m2)) + .isInstanceOf(IllegalStateException.class) + .hasMessageContaining("Already exists member"); + } + + @Test + @DisplayName("이름으로 회원 검색 성공") + void 회원검색_성공() { + Member m1 = Member.create("강지원", "a@test.com", "1111"); + Member m2 = Member.create("먼지", "b@test.com", "2222"); + + memberRepository.saveAll(List.of(m1, m2)); + + List result = memberService.searchMembers("강"); + + assertThat(result).hasSize(1); + assertThat(result.get(0).getUsername()).isEqualTo("강지원"); + } + + @Test + @DisplayName("회원이 작성한 게시글 조회") + void 회원게시글조회() { + + Member member = memberRepository.save(Member.create("강지원", "t@t.com", "1234")); + + Post p1 = postRepository.save(Post.create("post1", member)); + Post p2 = postRepository.save(Post.create("post2", member)); + + List posts = memberService.getPostsByMember(member.getId()); + + assertThat(posts).hasSize(2); + assertThat(posts.get(0).getContent()).isEqualTo("post1"); + assertThat(posts.get(1).getContent()).isEqualTo("post2"); + } + + @Test + @DisplayName("회원이 작성한 댓글 조회") + void 회원댓글조회() { + + Member member = memberRepository.save(Member.create("tester", "t@t.com", "1111")); + Post post = postRepository.save(Post.create("게시글", member)); + + Comment c1 = commentRepository.save(Comment.builder() + .content("댓글1") + .username("tester") + .post(post) + .member(member) + .build()); + c1.assignMember(member); + commentRepository.save(c1); + + Comment c2 = commentRepository.save(Comment.builder() + .content("댓글2") + .username("tester") + .post(post) + .member(member) + .build()); + c1.assignMember(member); + commentRepository.save(c2); + + List comments = memberService.getCommentsByMember(member.getId()); + + assertThat(comments).hasSize(2); + assertThat(comments.get(0).content()).isIn("댓글1", "댓글2"); + } + + @Test + @DisplayName("회원이 좋아요한 게시글 조회") + void 회원좋아요조회() { + + Member member = memberRepository.save(Member.create("user", "u@u.com", "1234")); + Post post1 = postRepository.save(Post.create("좋아요 게시글1", member)); + Post post2 = postRepository.save(Post.create("좋아요 게시글2", member)); + + likeRepository.save(Like.create(member, post1)); + likeRepository.save(Like.create(member, post2)); + + List likedPosts = memberService.getLikedPosts(member.getId()); + + assertThat(likedPosts).hasSize(2); + assertThat(likedPosts.get(0).getContent()).isIn("좋아요 게시글1", "좋아요 게시글2"); + } + +} diff --git a/src/test/java/com/example/devSns/PostControllerTest.java b/src/test/java/com/example/devSns/PostControllerTest.java index ffe8f6a..90e6b9b 100644 --- a/src/test/java/com/example/devSns/PostControllerTest.java +++ b/src/test/java/com/example/devSns/PostControllerTest.java @@ -1,6 +1,8 @@ package com.example.devSns; +import com.example.devSns.entity.Member; import com.example.devSns.entity.Post; +import com.example.devSns.repository.MemberRepository; import com.example.devSns.repository.PostRepository; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -17,54 +19,65 @@ @AutoConfigureMockMvc class PostControllerTest { - @Autowired - private MockMvc mockMvc; - - @Autowired - private PostRepository postRepository; + @Autowired MockMvc mockMvc; + @Autowired PostRepository postRepository; + @Autowired MemberRepository memberRepository; @BeforeEach void cleanDB() { postRepository.deleteAll(); + memberRepository.deleteAll(); } @Test void 게시글_생성_성공() throws Exception { + + Member member = memberRepository.save( + Member.create("강지원", "test@test.com", "1234") + ); + String json = """ - { - "username": "dardar", - "content": "테스트 게시글입니다." - } - """; + { + "memberId": %d, + "content": "테스트 게시글입니다." + } + """.formatted(member.getId()); mockMvc.perform(post("/post") .contentType(MediaType.APPLICATION_JSON) .content(json)) .andExpect(status().isOk()) - .andExpect(jsonPath("$.username").value("dardar")) - .andExpect(jsonPath("$.content").value("테스트 게시글입니다.")); + .andExpect(jsonPath("$.content").value("테스트 게시글입니다.")) + .andExpect(jsonPath("$.memberId").value(member.getId())); } @Test void 게시글_조회_성공() throws Exception { - Post post = Post.builder() - .username("tester") - .content("조회용 게시글") - .build(); - postRepository.save(post); + + Member member = memberRepository.save( + Member.create("tester", "tester@test.com", "1234") + ); + + Post post = postRepository.save( + Post.create("조회용 게시글", member) + ); mockMvc.perform(get("/post/" + post.getId())) .andExpect(status().isOk()) - .andExpect(jsonPath("$.username").value("tester")) - .andExpect(jsonPath("$.content").value("조회용 게시글")); + .andExpect(jsonPath("$.content").value("조회용 게시글")) + .andExpect(jsonPath("$.memberId").value(member.getId())); } @Test void 게시글_수정_성공() throws Exception { - Post post = postRepository.save(Post.builder() - .username("tester") - .content("원본 내용") - .build()); + + Member member = memberRepository.save( + Member.create("tester", "tester@test.com", "1234") + ); + + Post post = postRepository.save( + Post.create("원본 내용", member) + ); String updateJson = """ { @@ -72,7 +85,7 @@ void cleanDB() { } """; - mockMvc.perform(put("/post/" + post.getId()) + mockMvc.perform(patch("/post/" + post.getId()) .contentType(MediaType.APPLICATION_JSON) .content(updateJson)) .andExpect(status().isOk()) @@ -81,10 +94,14 @@ void cleanDB() { @Test void 게시글_삭제_성공() throws Exception { - Post post = postRepository.save(Post.builder() - .username("tester") - .content("삭제 대상 게시글") - .build()); + + Member member = memberRepository.save( + Member.create("tester", "tester@test.com", "1234") + ); + + Post post = postRepository.save( + Post.create("삭제 대상 게시글", member) + ); mockMvc.perform(delete("/post/" + post.getId())) .andExpect(status().isOk()); From 82b40aa4b711fd2e615efdfb66272823782f0f97 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EA=B0=95=EC=A7=80=EC=9B=90?= Date: Fri, 5 Dec 2025 13:15:59 +0900 Subject: [PATCH 5/5] =?UTF-8?q?feat:week-4=20=EC=9D=B8=EC=A6=9D/=EC=9D=B8?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .DS_Store | Bin 6148 -> 6148 bytes build.gradle | 12 +++ .../com/example/devSns/config/WebConfig.java | 22 +++++ .../devSns/config/WebSecurityConfig.java | 36 ++++++++ .../devSns/controller/AuthController.java | 40 +++++++++ .../controller/CommentActionController.java | 8 +- .../devSns/controller/CommentController.java | 8 +- .../devSns/controller/LikeController.java | 8 +- .../devSns/controller/MemberController.java | 30 +++---- .../devSns/controller/PostController.java | 22 +++-- .../devSns/controller/UserController.java | 14 ++++ .../devSns/dto/CommentCreateRequest.java | 9 ++ .../example/devSns/dto/CommentResponse.java | 9 ++ .../devSns/dto/CommentUpdateRequest.java | 3 + .../com/example/devSns/dto/LikeResponse.java | 3 + .../example/devSns/dto/LikeToggleRequest.java | 6 ++ .../com/example/devSns/dto/LoginRequest.java | 19 +++++ .../com/example/devSns/dto/LoginResponse.java | 12 +++ .../example/devSns/dto/MemberJoinRequest.java | 8 ++ .../example/devSns/dto/MemberResponse.java | 8 ++ .../example/devSns/dto/PostCreateRequest.java | 5 ++ .../com/example/devSns/dto/PostResponse.java | 14 +++- .../example/devSns/dto/PostUpdateRequest.java | 3 + .../com/example/devSns/dto/SignUpRequest.java | 23 ++++++ .../com/example/devSns/entity/Comment.java | 14 ++-- .../java/com/example/devSns/entity/Post.java | 24 +++--- .../exception/EntityNotFoundException.java | 11 +++ .../devSns/interceptor/JwtInterceptor.java | 55 +++++++++++++ .../java/com/example/devSns/jwt/JwtUtil.java | 47 +++++++++++ .../devSns/repository/CommentRepository.java | 2 +- .../devSns/repository/LikeRepository.java | 6 +- .../devSns/repository/MemberRepository.java | 6 +- .../devSns/service/CommentService.java | 40 ++++----- .../example/devSns/service/LikeService.java | 27 +++--- .../example/devSns/service/MemberService.java | 77 ++++++++++++++---- .../example/devSns/service/PostService.java | 37 ++++----- src/main/resources/application.properties | 8 +- .../com/example/devSns/LikeServiceTest.java | 6 +- .../java/com/example/devSns/LoginTest.java | 44 ++++++++++ .../com/example/devSns/MemberServiceTest.java | 19 +++-- 40 files changed, 595 insertions(+), 150 deletions(-) create mode 100644 src/main/java/com/example/devSns/config/WebConfig.java create mode 100644 src/main/java/com/example/devSns/config/WebSecurityConfig.java create mode 100644 src/main/java/com/example/devSns/controller/AuthController.java create mode 100644 src/main/java/com/example/devSns/controller/UserController.java create mode 100644 src/main/java/com/example/devSns/dto/LoginRequest.java create mode 100644 src/main/java/com/example/devSns/dto/LoginResponse.java create mode 100644 src/main/java/com/example/devSns/dto/SignUpRequest.java create mode 100644 src/main/java/com/example/devSns/exception/EntityNotFoundException.java create mode 100644 src/main/java/com/example/devSns/interceptor/JwtInterceptor.java create mode 100644 src/main/java/com/example/devSns/jwt/JwtUtil.java create mode 100644 src/test/java/com/example/devSns/LoginTest.java diff --git a/.DS_Store b/.DS_Store index e21eb369ee5ed21bccb54abd7effa1711b4d0e64..d4e4f4df2ac31b5a055a19f7a0d22abf547a0007 100644 GIT binary patch delta 20 bcmZoMXffC@n~~kzNJqig%yjc2#w0NSL9hkg delta 20 bcmZoMXffC@n~~kbSVzIw(q!`@#w0NSL8b-W diff --git a/build.gradle b/build.gradle index 0527f19..6612985 100644 --- a/build.gradle +++ b/build.gradle @@ -37,6 +37,18 @@ dependencies { // test testImplementation 'org.springframework.boot:spring-boot-starter-test' + //BCrypt + implementation 'org.springframework.boot:spring-boot-starter-security' + + //jwt + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.11.5' + runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.11.5' + + //validation + implementation 'javax.validation:validation-api:2.0.1.Final' + implementation 'org.hibernate:hibernate-validator:6.0.13.Final' + implementation 'org.glassfish:javax.el:3.0.0' } test{ useJUnitPlatform() diff --git a/src/main/java/com/example/devSns/config/WebConfig.java b/src/main/java/com/example/devSns/config/WebConfig.java new file mode 100644 index 0000000..79ccdbd --- /dev/null +++ b/src/main/java/com/example/devSns/config/WebConfig.java @@ -0,0 +1,22 @@ +package com.example.devSns.config; + +import com.example.devSns.interceptor.JwtInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class WebConfig implements WebMvcConfigurer { + @Autowired + private JwtInterceptor JwtInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry Registry) { + Registry.addInterceptor(JwtInterceptor) + .addPathPatterns("/api/**") // 인증이 필요한 경로 + .excludePathPatterns("/","/api/auth/signup", + "/api/auth/login", + "/error"); + } +} diff --git a/src/main/java/com/example/devSns/config/WebSecurityConfig.java b/src/main/java/com/example/devSns/config/WebSecurityConfig.java new file mode 100644 index 0000000..34643b2 --- /dev/null +++ b/src/main/java/com/example/devSns/config/WebSecurityConfig.java @@ -0,0 +1,36 @@ +package com.example.devSns.config; + +import com.example.devSns.interceptor.JwtInterceptor; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.config.annotation.web.builders.HttpSecurity; +import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; +import org.springframework.security.config.http.SessionCreationPolicy; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.security.web.SecurityFilterChain; + +@Configuration +@EnableWebSecurity +public class WebSecurityConfig { + @Bean + public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) + .sessionManagement(session -> session + .sessionCreationPolicy(SessionCreationPolicy.STATELESS) + ) + .authorizeHttpRequests(auth -> auth + .requestMatchers("/api/auth/signup", "/api/auth/login").permitAll() + .anyRequest().authenticated() + ); + return http.build(); + } + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } + +} \ No newline at end of file diff --git a/src/main/java/com/example/devSns/controller/AuthController.java b/src/main/java/com/example/devSns/controller/AuthController.java new file mode 100644 index 0000000..e878115 --- /dev/null +++ b/src/main/java/com/example/devSns/controller/AuthController.java @@ -0,0 +1,40 @@ +package com.example.devSns.controller; + +import com.example.devSns.dto.LoginRequest; +import com.example.devSns.dto.LoginResponse; +import com.example.devSns.dto.SignUpRequest; +import com.example.devSns.service.MemberService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequestMapping("/api/auth") +public class AuthController { + + @Autowired + private MemberService MemberService; + + // 회원 가입 + @PostMapping("/signup") + public ResponseEntity signup(@RequestBody SignUpRequest signUpRequest) { + // 이메일 중복 체크 + if (MemberService.isEmailExists(signUpRequest.getEmail())) { + return ResponseEntity.status(400).body("Email already exists"); + } + + // 회원가입 처리 + MemberService.register(signUpRequest); + return ResponseEntity.status(201).body("Sign up successful"); + } + + // 로그인(JWT 발급) + @PostMapping("/login") + public ResponseEntity login(@RequestBody LoginRequest loginRequest) { + String token = MemberService.login(loginRequest); + if (token == null) { + return ResponseEntity.status(401).body(new LoginResponse("Login failed")); // 로그인 실패 시 + } + return ResponseEntity.ok(new LoginResponse(token)); + } +} diff --git a/src/main/java/com/example/devSns/controller/CommentActionController.java b/src/main/java/com/example/devSns/controller/CommentActionController.java index dcf2f16..dcde566 100644 --- a/src/main/java/com/example/devSns/controller/CommentActionController.java +++ b/src/main/java/com/example/devSns/controller/CommentActionController.java @@ -10,14 +10,14 @@ @RestController @RequestMapping("/comments") public class CommentActionController { - private final CommentService commentService; + private final CommentService CommentService; public CommentActionController(CommentService commentService){ - this.commentService = commentService; + this.CommentService = commentService; } @DeleteMapping("/{commentId}") public void deleteComment(@PathVariable Long commentId) { - commentService.deleteComment(commentId); + CommentService.deleteComment(commentId); } @PatchMapping("/{commentId}") @@ -25,7 +25,7 @@ public ResponseEntity updateComment( @PathVariable Long commentId, @RequestBody CommentUpdateRequest request ) { - Comment updated = commentService.updateComment(commentId, request.getContent()); + Comment updated = CommentService.UpdateComment(commentId, request.getContent()); return ResponseEntity.ok(new CommentResponse(updated)); } } diff --git a/src/main/java/com/example/devSns/controller/CommentController.java b/src/main/java/com/example/devSns/controller/CommentController.java index 0add7be..16095a5 100644 --- a/src/main/java/com/example/devSns/controller/CommentController.java +++ b/src/main/java/com/example/devSns/controller/CommentController.java @@ -13,22 +13,22 @@ @RestController @RequestMapping("/posts/{postId}/comments") public class CommentController { - private final CommentService commentService; + private final CommentService CommentService; public CommentController(CommentService commentService) { - this.commentService = commentService; + this.CommentService = commentService; } @GetMapping public List getComments(@PathVariable Long postId) { - return commentService.getCommentByPost(postId).stream() + return CommentService.GetCommentByPost(postId).stream() .map(CommentResponse::new) .toList(); } @PostMapping public CommentResponse createComment(@PathVariable Long postId, @RequestBody CommentCreateRequest request) { - Comment created = commentService.addComment(postId, request); + Comment created = CommentService.AddComment(postId, request); return new CommentResponse(created); } diff --git a/src/main/java/com/example/devSns/controller/LikeController.java b/src/main/java/com/example/devSns/controller/LikeController.java index d12e813..e0056f6 100644 --- a/src/main/java/com/example/devSns/controller/LikeController.java +++ b/src/main/java/com/example/devSns/controller/LikeController.java @@ -11,18 +11,18 @@ @RequiredArgsConstructor @RequestMapping("/likes") public class LikeController { - private final LikeService likeService; + private final LikeService LikeService; @PostMapping("/toggle") public ResponseEntity toggleLike(@RequestBody LikeToggleRequest request){ - likeService.toggleLike(request.getMemberId(), request.getPostId()); - long count = likeService.getLikeCount(request.getPostId()); + LikeService.toggleLike(request.getMemberId(), request.getPostId()); + long count = LikeService.getLikeCount(request.getPostId()); return ResponseEntity.ok(new LikeResponse(request.getPostId(),count,true)); } @GetMapping("/count/{postId}") public ResponseEntity getLikeCount(@PathVariable Long postId){ - long count = likeService.getLikeCount(postId); + long count = LikeService.getLikeCount(postId); return ResponseEntity.ok(count); } diff --git a/src/main/java/com/example/devSns/controller/MemberController.java b/src/main/java/com/example/devSns/controller/MemberController.java index d7be09f..4f4f13e 100644 --- a/src/main/java/com/example/devSns/controller/MemberController.java +++ b/src/main/java/com/example/devSns/controller/MemberController.java @@ -1,12 +1,10 @@ package com.example.devSns.controller; -import com.example.devSns.dto.CommentResponse; -import com.example.devSns.dto.MemberJoinRequest; -import com.example.devSns.dto.MemberResponse; -import com.example.devSns.dto.PostResponse; +import com.example.devSns.dto.*; import com.example.devSns.entity.Member; import com.example.devSns.service.MemberService; import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; import java.util.List; @@ -15,38 +13,36 @@ @RequiredArgsConstructor @RequestMapping("/members") public class MemberController { - private final MemberService memberService; + private final MemberService MemberService; + // 회원 조회 @GetMapping("/{id}") public MemberResponse getMember(@PathVariable Long id) { - Member member = memberService.findMemberById(id); + Member member = MemberService.findMemberById(id); return new MemberResponse(member); } + // 회원 검색 @GetMapping("/search") public List search(@RequestParam String keyword) { - return memberService.searchMembers(keyword).stream().map(MemberResponse::new).toList(); + return MemberService.searchMembers(keyword).stream().map(MemberResponse::new).toList(); } + // 회원 작성 글 조회 @GetMapping("/{id}/posts") public List getMemberPosts(@PathVariable Long id) { - return memberService.getPostsByMember(id); + return MemberService.getPostsByMember(id); } + // 회원 작성 댓글 조회 @GetMapping("/{id}/comments") public List getMemberComments(@PathVariable Long id) { - return memberService.getCommentsByMember(id); + return MemberService.getCommentsByMember(id); } + // 회원이 좋아요한 글 조회 @GetMapping("/{id}/likes") public List getMemberLikes(@PathVariable Long id) { - return memberService.getLikedPosts(id); + return MemberService.getLikedPosts(id); } - - @PostMapping - public MemberResponse createMember(@RequestBody MemberJoinRequest request){ - Member member = memberService.join(request.toEntity()); - return new MemberResponse(member); - } - } diff --git a/src/main/java/com/example/devSns/controller/PostController.java b/src/main/java/com/example/devSns/controller/PostController.java index 81eead8..bb96bf8 100644 --- a/src/main/java/com/example/devSns/controller/PostController.java +++ b/src/main/java/com/example/devSns/controller/PostController.java @@ -5,48 +5,46 @@ import com.example.devSns.dto.PostUpdateRequest; import com.example.devSns.entity.Post; import com.example.devSns.service.PostService; -import jakarta.persistence.PostUpdate; +import org.springframework.data.domain.Page; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; -import java.time.LocalDateTime; import java.util.List; -import java.util.Optional; @RestController -@RequestMapping("/post") +@RequestMapping("/posts") public class PostController { - private final PostService postService; + private final PostService PostService; public PostController(PostService postService) { - this.postService = postService; + this.PostService = postService; } @GetMapping - public List getAllPosts(){ - return postService.findAll().stream().map(PostResponse::new).toList(); + public Page getPosts(@RequestParam int page, @RequestParam int size){ + return PostService.FindAll(page, size); } @GetMapping("/{id}") public ResponseEntity getPostById(@PathVariable Long id){ - Post post = postService.findById(id); + Post post = PostService.FindById(id); return ResponseEntity.ok(new PostResponse(post)); } @PostMapping public PostResponse createPost(@RequestBody PostCreateRequest request){ - Post created = postService.createPost(request); + Post created = PostService.CreatePost(request); return new PostResponse(created); } @PatchMapping("/{id}") public PostResponse updatePost(@PathVariable Long id, @RequestBody PostUpdateRequest request){ - Post updated = postService.updatePost(id, request); + Post updated = PostService.UpdatePost(id, request); return new PostResponse(updated); } @DeleteMapping("/{id}") public void deletePost(@PathVariable Long id){ - postService.delete(id); + PostService.delete(id); } } \ No newline at end of file diff --git a/src/main/java/com/example/devSns/controller/UserController.java b/src/main/java/com/example/devSns/controller/UserController.java new file mode 100644 index 0000000..4e9bc8a --- /dev/null +++ b/src/main/java/com/example/devSns/controller/UserController.java @@ -0,0 +1,14 @@ +package com.example.devSns.controller; + +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestAttribute; +import org.springframework.web.bind.annotation.RestController; + +@RestController +public class UserController { + + @GetMapping("/api/user") + public String getUserInfo(@RequestAttribute("memberId") Long memberId, @RequestAttribute("email") String email) { + return "Member ID: " + memberId + ", Email: " + email; + } +} diff --git a/src/main/java/com/example/devSns/dto/CommentCreateRequest.java b/src/main/java/com/example/devSns/dto/CommentCreateRequest.java index 085e7cc..dd06f1e 100644 --- a/src/main/java/com/example/devSns/dto/CommentCreateRequest.java +++ b/src/main/java/com/example/devSns/dto/CommentCreateRequest.java @@ -2,11 +2,20 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; +import javax.validation.constraints.Size; @Getter @NoArgsConstructor public class CommentCreateRequest { + @NotEmpty(message= "Content cannot be empty") + @Size(max = 500, message="Content cannot exceed 500 characters") private String content; + + @NotEmpty(message="Username cannot be empty") private String username; + + @NotNull private Long memberId; } diff --git a/src/main/java/com/example/devSns/dto/CommentResponse.java b/src/main/java/com/example/devSns/dto/CommentResponse.java index c28db4a..2ead883 100644 --- a/src/main/java/com/example/devSns/dto/CommentResponse.java +++ b/src/main/java/com/example/devSns/dto/CommentResponse.java @@ -2,12 +2,21 @@ import com.example.devSns.entity.Comment; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; import java.time.LocalDateTime; public record CommentResponse( + @NotNull(message="id cannot be null") Long id, + + @NotEmpty(message= "Content cannot be empty") String content, + + @NotEmpty(message= "Username cannot be empty") String username, + + @NotNull LocalDateTime createdAt ){ public CommentResponse(Comment comment){ diff --git a/src/main/java/com/example/devSns/dto/CommentUpdateRequest.java b/src/main/java/com/example/devSns/dto/CommentUpdateRequest.java index cf910c3..95ee6b1 100644 --- a/src/main/java/com/example/devSns/dto/CommentUpdateRequest.java +++ b/src/main/java/com/example/devSns/dto/CommentUpdateRequest.java @@ -3,8 +3,11 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import javax.validation.constraints.NotNull; + @Getter @NoArgsConstructor public class CommentUpdateRequest { + @NotNull(message="Content cannot be null") private String content; } diff --git a/src/main/java/com/example/devSns/dto/LikeResponse.java b/src/main/java/com/example/devSns/dto/LikeResponse.java index 00c62b4..f6a31e4 100644 --- a/src/main/java/com/example/devSns/dto/LikeResponse.java +++ b/src/main/java/com/example/devSns/dto/LikeResponse.java @@ -3,9 +3,12 @@ import lombok.AllArgsConstructor; import lombok.Getter; +import javax.validation.constraints.NotNull; + @Getter @AllArgsConstructor public class LikeResponse { + @NotNull private Long PostId; private long likeCount; private boolean liked; diff --git a/src/main/java/com/example/devSns/dto/LikeToggleRequest.java b/src/main/java/com/example/devSns/dto/LikeToggleRequest.java index 234ca45..c56e3e3 100644 --- a/src/main/java/com/example/devSns/dto/LikeToggleRequest.java +++ b/src/main/java/com/example/devSns/dto/LikeToggleRequest.java @@ -3,9 +3,15 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import javax.validation.constraints.NotNull; + @Getter @NoArgsConstructor public class LikeToggleRequest { + + @NotNull private Long memberId; + + @NotNull private Long postId; } diff --git a/src/main/java/com/example/devSns/dto/LoginRequest.java b/src/main/java/com/example/devSns/dto/LoginRequest.java new file mode 100644 index 0000000..7ffab39 --- /dev/null +++ b/src/main/java/com/example/devSns/dto/LoginRequest.java @@ -0,0 +1,19 @@ +package com.example.devSns.dto; + +import lombok.Getter; + +import javax.validation.constraints.NotEmpty; + +@Getter +public class LoginRequest { + + @NotEmpty(message="email cannot be empty") + private final String email; + @NotEmpty(message="password cannot be empty") + private final String password; + + public LoginRequest(String email, String password) { + this.email = email; + this.password = password; + } +} diff --git a/src/main/java/com/example/devSns/dto/LoginResponse.java b/src/main/java/com/example/devSns/dto/LoginResponse.java new file mode 100644 index 0000000..c3e6a9e --- /dev/null +++ b/src/main/java/com/example/devSns/dto/LoginResponse.java @@ -0,0 +1,12 @@ +package com.example.devSns.dto; + +import lombok.Getter; + +@Getter +public class LoginResponse { + private final String token; + + public LoginResponse(String token) { + this.token = token; + } +} diff --git a/src/main/java/com/example/devSns/dto/MemberJoinRequest.java b/src/main/java/com/example/devSns/dto/MemberJoinRequest.java index 83ff098..9cd1132 100644 --- a/src/main/java/com/example/devSns/dto/MemberJoinRequest.java +++ b/src/main/java/com/example/devSns/dto/MemberJoinRequest.java @@ -4,11 +4,19 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import javax.validation.constraints.NotEmpty; + @Getter @NoArgsConstructor public class MemberJoinRequest { + + @NotEmpty(message="username cannot be empty") private String username; + + @NotEmpty(message="email cannot be empty") private String email; + + @NotEmpty(message="password cannot be empty") private String password; public Member toEntity(){ diff --git a/src/main/java/com/example/devSns/dto/MemberResponse.java b/src/main/java/com/example/devSns/dto/MemberResponse.java index 308147c..b728ab0 100644 --- a/src/main/java/com/example/devSns/dto/MemberResponse.java +++ b/src/main/java/com/example/devSns/dto/MemberResponse.java @@ -3,10 +3,18 @@ import com.example.devSns.entity.Member; import lombok.Getter; +import javax.validation.constraints.NotEmpty; + @Getter public class MemberResponse { + + @NotEmpty(message="id cannot be empty") private Long id; + + @NotEmpty(message="username cannot be empty") private String username; + + @NotEmpty(message="email cannot be empty") private String email; public MemberResponse(Member member){ diff --git a/src/main/java/com/example/devSns/dto/PostCreateRequest.java b/src/main/java/com/example/devSns/dto/PostCreateRequest.java index be9ca00..c156d85 100644 --- a/src/main/java/com/example/devSns/dto/PostCreateRequest.java +++ b/src/main/java/com/example/devSns/dto/PostCreateRequest.java @@ -3,9 +3,14 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import javax.validation.constraints.NotEmpty; + @Getter @NoArgsConstructor public class PostCreateRequest { + @NotEmpty(message="content cannot be empty") private String content; + + @NotEmpty(message="memberId cannot be empty") private Long memberId; } diff --git a/src/main/java/com/example/devSns/dto/PostResponse.java b/src/main/java/com/example/devSns/dto/PostResponse.java index 5b6aec1..5a7cc44 100644 --- a/src/main/java/com/example/devSns/dto/PostResponse.java +++ b/src/main/java/com/example/devSns/dto/PostResponse.java @@ -3,17 +3,29 @@ import com.example.devSns.entity.Post; import lombok.Getter; +import javax.validation.constraints.NotEmpty; +import javax.validation.constraints.NotNull; import java.time.LocalDateTime; @Getter public class PostResponse { - + @NotNull private Long id; + + @NotNull private Long memberId; + + @NotEmpty(message="content cannot be empty") private String content; + + @NotEmpty(message="username cannot be empty") private String username; + + @NotNull private LocalDateTime createdAt; + @NotNull private LocalDateTime updatedAt; + private int likeCount; private int commentCount; diff --git a/src/main/java/com/example/devSns/dto/PostUpdateRequest.java b/src/main/java/com/example/devSns/dto/PostUpdateRequest.java index e9bc144..74f21ad 100644 --- a/src/main/java/com/example/devSns/dto/PostUpdateRequest.java +++ b/src/main/java/com/example/devSns/dto/PostUpdateRequest.java @@ -3,8 +3,11 @@ import lombok.Getter; import lombok.NoArgsConstructor; +import javax.validation.constraints.NotEmpty; + @Getter @NoArgsConstructor public class PostUpdateRequest { + @NotEmpty(message="content cannot be empty") private String content; } diff --git a/src/main/java/com/example/devSns/dto/SignUpRequest.java b/src/main/java/com/example/devSns/dto/SignUpRequest.java new file mode 100644 index 0000000..4fd97d7 --- /dev/null +++ b/src/main/java/com/example/devSns/dto/SignUpRequest.java @@ -0,0 +1,23 @@ +package com.example.devSns.dto; + +import lombok.Getter; + +import javax.validation.constraints.NotEmpty; + +@Getter +public class SignUpRequest { + @NotEmpty(message="username cannot be empty") + private final String username; + + @NotEmpty(message="email cannot be empty") + private final String email; + + @NotEmpty(message="password cannot be empty") + private final String password; + + public SignUpRequest(String username, String email, String password) { + this.username = username; + this.email = email; + this.password = password; + } +} diff --git a/src/main/java/com/example/devSns/entity/Comment.java b/src/main/java/com/example/devSns/entity/Comment.java index 11f34d7..e2de1ae 100644 --- a/src/main/java/com/example/devSns/entity/Comment.java +++ b/src/main/java/com/example/devSns/entity/Comment.java @@ -17,7 +17,7 @@ public class Comment{ private String content; private String username; - private LocalDateTime createdAt; + private LocalDateTime CreatedAt; @ManyToOne(fetch = FetchType.LAZY, optional = false) @JoinColumn( @@ -31,20 +31,20 @@ public class Comment{ private Member member; @PrePersist - public void onCreate(){ - createdAt = LocalDateTime.now(); + public void OnCreate(){ + CreatedAt = LocalDateTime.now(); } - public void update(String content){ + public void Update(String content){ this.content = content; } - public void assignTo(Post post){ + public void AssignTo(Post post){ this.post = post; } - public void assignMember(Member member){ + public void AssignMember(Member member){ this.member = member; member.addComment(this); } - public static Comment create(String content, Member member, Post post) { + public static Comment Create(String content, Member member, Post post) { return Comment.builder() .content(content) .username(member.getUsername()) diff --git a/src/main/java/com/example/devSns/entity/Post.java b/src/main/java/com/example/devSns/entity/Post.java index 0be76de..c1a6f68 100644 --- a/src/main/java/com/example/devSns/entity/Post.java +++ b/src/main/java/com/example/devSns/entity/Post.java @@ -15,17 +15,17 @@ public class Post { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) - private Long id; + private Long Id; private String content; private String username; - private LocalDateTime createdAt; - private LocalDateTime updatedAt; + private LocalDateTime CreatedAt; + private LocalDateTime UpdatedAt; @OneToMany(mappedBy = "post", cascade = CascadeType.ALL) private List likes = new ArrayList<>(); - @OneToMany(mappedBy = "post", cascade = CascadeType.ALL,orphanRemoval = true) + @OneToMany(cascade = CascadeType.ALL,orphanRemoval = true) private List comments = new ArrayList<>(); @ManyToOne(fetch = FetchType.LAZY) @@ -34,27 +34,27 @@ public class Post { @PrePersist public void onCreate(){ - createdAt = LocalDateTime.now(); - updatedAt = LocalDateTime.now(); + CreatedAt = LocalDateTime.now(); + UpdatedAt = LocalDateTime.now(); } @PreUpdate public void onUpdate(){ - updatedAt = LocalDateTime.now(); + UpdatedAt = LocalDateTime.now(); } public void update(String content){ this.content = content; } - public void addComment(Comment comment){ + public void AddComment(Comment comment){ comments.add(comment); - comment.assignTo(this); + comment.AssignTo(this); } private Post(String content, Member member){ this.content = content; this.member = member; - this.createdAt = LocalDateTime.now(); - this.updatedAt = LocalDateTime.now(); + this.CreatedAt = LocalDateTime.now(); + this.UpdatedAt = LocalDateTime.now(); member.addPost(this); } @@ -65,7 +65,7 @@ public static Post create(String content, Member member){ public void updateContent(String newContent){ this.content = newContent; - this.updatedAt = LocalDateTime.now(); + this.UpdatedAt = LocalDateTime.now(); } public void addLike(Like like) { diff --git a/src/main/java/com/example/devSns/exception/EntityNotFoundException.java b/src/main/java/com/example/devSns/exception/EntityNotFoundException.java new file mode 100644 index 0000000..ffbe3ab --- /dev/null +++ b/src/main/java/com/example/devSns/exception/EntityNotFoundException.java @@ -0,0 +1,11 @@ +package com.example.devSns.exception; + +public class EntityNotFoundException extends RuntimeException { + public EntityNotFoundException(String entity, Long id) { + super(entity +" with id " + id + " not found"); + } + + public EntityNotFoundException(String message){ + super(message); + } +} diff --git a/src/main/java/com/example/devSns/interceptor/JwtInterceptor.java b/src/main/java/com/example/devSns/interceptor/JwtInterceptor.java new file mode 100644 index 0000000..ea5f904 --- /dev/null +++ b/src/main/java/com/example/devSns/interceptor/JwtInterceptor.java @@ -0,0 +1,55 @@ +package com.example.devSns.interceptor; + +import com.example.devSns.jwt.JwtUtil; +import io.jsonwebtoken.Claims; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Component +public class JwtInterceptor implements HandlerInterceptor { + + @Autowired + private JwtUtil jwtUtil; + + @Override + public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception { + if ("OPTIONS".equalsIgnoreCase(request.getMethod())) { + return true; + } + + String token = request.getHeader("Authorization"); + + if (token == null || !token.startsWith("Bearer ")) { + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("{\"error\": \"Missing or invalid token\"}"); + return false; + } + + token = token.substring(7); + + try { + // 토큰 파싱 및 검증 + Claims claims = jwtUtil.parseClaims(token); + Long memberId = Long.valueOf(claims.getSubject()); + String email = claims.get("email", String.class); + + // 인증 정보를 request에 저장 + request.setAttribute("memberId", memberId); + request.setAttribute("email", email); + + // 파싱된 토큰 정보 로그 출력 + System.out.println("Token parsed successfully: memberId=" + memberId + ", email=" + email); + return true; + } catch (Exception e) { + // 토큰 파싱 실패 시 + System.out.println("Token parsing failed: " + e.getMessage()); + response.setStatus(HttpServletResponse.SC_UNAUTHORIZED); + response.getWriter().write("{\"error\": \"Invalid token\"}"); + return false; + } + } + +} diff --git a/src/main/java/com/example/devSns/jwt/JwtUtil.java b/src/main/java/com/example/devSns/jwt/JwtUtil.java new file mode 100644 index 0000000..950adef --- /dev/null +++ b/src/main/java/com/example/devSns/jwt/JwtUtil.java @@ -0,0 +1,47 @@ +package com.example.devSns.jwt; + +import io.jsonwebtoken.Claims; +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import io.jsonwebtoken.security.Keys; +import org.springframework.stereotype.Component; + +import java.security.Key; +import java.util.Date; + +@Component +public class JwtUtil { + + // 적절한 비밀키 생성 (HS256에 적합한 256비트 비밀키) + private Key secretKey = Keys.secretKeyFor(SignatureAlgorithm.HS256); // HS256에 적합한 비밀키 생성 + + // 토큰 생성 + public String generateToken(Long memberId, String email) { + return Jwts.builder() + .setSubject(String.valueOf(memberId)) // 사용자 ID + .claim("email", email) // 이메일을 추가 + .setIssuedAt(new Date()) // 발급 시간 + .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) // 만료 시간 (1시간) + .signWith(secretKey) // 서명 (secretKey 사용) + .compact(); + } + + // 토큰 파싱 + public Claims parseClaims(String token) { + return Jwts.parserBuilder() + .setSigningKey(secretKey) // 동일한 비밀키를 사용하여 파싱 + .build() + .parseClaimsJws(token) + .getBody(); + } + + // 토큰 유효성 검사 + public boolean validateToken(String token) { + try { + parseClaims(token); + return true; + } catch (Exception e) { + return false; + } + } +} diff --git a/src/main/java/com/example/devSns/repository/CommentRepository.java b/src/main/java/com/example/devSns/repository/CommentRepository.java index c293959..643e6dc 100644 --- a/src/main/java/com/example/devSns/repository/CommentRepository.java +++ b/src/main/java/com/example/devSns/repository/CommentRepository.java @@ -5,5 +5,5 @@ import java.util.List; public interface CommentRepository extends JpaRepository { - List findByPost_Id(Long postId); + List FindByPostId(Long postId); } \ No newline at end of file diff --git a/src/main/java/com/example/devSns/repository/LikeRepository.java b/src/main/java/com/example/devSns/repository/LikeRepository.java index 7b61102..42e3b5e 100644 --- a/src/main/java/com/example/devSns/repository/LikeRepository.java +++ b/src/main/java/com/example/devSns/repository/LikeRepository.java @@ -7,7 +7,7 @@ import java.util.Optional; public interface LikeRepository extends JpaRepository { - Optional findByMemberAndPost(Member member, Post post); - boolean existsByMemberAndPost(Member member, Post post); - long countByPost(Post post); + Optional FindByMemberAndPost(Member member, Post post); + boolean ExistsByMemberAndPost(Member member, Post post); + long CountByPost(Post post); } diff --git a/src/main/java/com/example/devSns/repository/MemberRepository.java b/src/main/java/com/example/devSns/repository/MemberRepository.java index 430f7d2..6310256 100644 --- a/src/main/java/com/example/devSns/repository/MemberRepository.java +++ b/src/main/java/com/example/devSns/repository/MemberRepository.java @@ -7,7 +7,7 @@ import java.util.Optional; public interface MemberRepository extends JpaRepository { - Optional findByUsername(String username); - List findByUsernameContaining(String keyword); - + Optional FindByUsername(String username); + List FindByUsernameContaining(String keyword); + Optional FindByEmail(String email); } diff --git a/src/main/java/com/example/devSns/service/CommentService.java b/src/main/java/com/example/devSns/service/CommentService.java index 5ca0b84..3a8bcc7 100644 --- a/src/main/java/com/example/devSns/service/CommentService.java +++ b/src/main/java/com/example/devSns/service/CommentService.java @@ -7,33 +7,33 @@ import com.example.devSns.repository.CommentRepository; import com.example.devSns.repository.MemberRepository; import com.example.devSns.repository.PostRepository; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.List; @Service +@Transactional public class CommentService { - private final CommentRepository commentRepository; - private final PostRepository postRepository; - private final MemberRepository memberRepository; + private final CommentRepository CommentRepository; + private final PostRepository PostRepository; + private final MemberRepository MemberRepository; public CommentService(CommentRepository commentRepository, PostRepository postRepository, MemberRepository memberRepository) { - this.commentRepository = commentRepository; - this.postRepository = postRepository; - this.memberRepository = memberRepository; + this.CommentRepository = commentRepository; + this.PostRepository = postRepository; + this.MemberRepository = memberRepository; } @Transactional(readOnly = true) - public List getCommentByPost(Long postId){ - return commentRepository.findByPost_Id(postId); + public List GetCommentByPost(Long postId){ + return CommentRepository.FindByPostId(postId); } @Transactional - public Comment addComment(Long postId, CommentCreateRequest request){ - Post post = postRepository.findById(postId).orElseThrow(()-> new IllegalArgumentException("post not found")); - Member member = memberRepository.findById(request.getMemberId()) + public Comment AddComment(Long postId, CommentCreateRequest request){ + Post post = PostRepository.findById(postId).orElseThrow(()-> new IllegalArgumentException("post not found")); + Member member = MemberRepository.findById(request.getMemberId()) .orElseThrow(() -> new IllegalArgumentException("member not found")); Comment comment = Comment.builder() @@ -41,17 +41,17 @@ public Comment addComment(Long postId, CommentCreateRequest request){ .username(request.getUsername()) .post(post) .build(); - post.addComment(comment); - comment.assignMember(member); - return commentRepository.save(comment); + post.AddComment(comment); + comment.AssignMember(member); + return CommentRepository.save(comment); } - public Comment updateComment(Long commentId, String newContent){ - Comment comment = commentRepository.findById(commentId).orElseThrow(() -> new IllegalArgumentException("comment not found")); - comment.update(newContent); - return commentRepository.save(comment); + public Comment UpdateComment(Long commentId, String newContent){ + Comment comment = CommentRepository.findById(commentId).orElseThrow(() -> new IllegalArgumentException("comment not found")); + comment.Update(newContent); + return CommentRepository.save(comment); } public void deleteComment(Long id){ - commentRepository.deleteById(id); + CommentRepository.deleteById(id); } } \ No newline at end of file diff --git a/src/main/java/com/example/devSns/service/LikeService.java b/src/main/java/com/example/devSns/service/LikeService.java index f2fd498..36d8d45 100644 --- a/src/main/java/com/example/devSns/service/LikeService.java +++ b/src/main/java/com/example/devSns/service/LikeService.java @@ -3,10 +3,11 @@ import com.example.devSns.entity.Like; import com.example.devSns.entity.Member; import com.example.devSns.entity.Post; +import com.example.devSns.exception.EntityNotFoundException; import com.example.devSns.repository.LikeRepository; import com.example.devSns.repository.MemberRepository; import com.example.devSns.repository.PostRepository; -import jakarta.transaction.Transactional; +import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -15,30 +16,30 @@ @Service @RequiredArgsConstructor public class LikeService { - private final LikeRepository likeRepository; - private final PostRepository postRepository; - private final MemberRepository memberRepository; + private final LikeRepository LikeRepository; + private final PostRepository PostRepository; + private final MemberRepository MemberRepository; @Transactional public void toggleLike(Long memberId, Long postId){ - Member member = memberRepository.findById(memberId) - .orElseThrow(() -> new RuntimeException("member not found")); - Post post = postRepository.findById(postId) - .orElseThrow(() -> new RuntimeException("post not found")); + Member member = MemberRepository.findById(memberId) + .orElseThrow(() -> new EntityNotFoundException("Member",memberId)); + Post post = PostRepository.findById(postId) + .orElseThrow(() -> new EntityNotFoundException("Post",postId)); - Optional existingLike = likeRepository.findByMemberAndPost(member, post); + Optional existingLike = LikeRepository.FindByMemberAndPost(member, post); if(existingLike.isPresent()){ - likeRepository.delete(existingLike.get()); + LikeRepository.delete(existingLike.get()); } else { Like like = Like.create(member, post); - likeRepository.save(like); + LikeRepository.save(like); } } public long getLikeCount(Long postId){ - Post post = postRepository.findById(postId) + Post post = PostRepository.findById(postId) .orElseThrow(() -> new RuntimeException("post not found")); - return likeRepository.countByPost(post); + return LikeRepository.CountByPost(post); } } diff --git a/src/main/java/com/example/devSns/service/MemberService.java b/src/main/java/com/example/devSns/service/MemberService.java index be4597b..e55ccc7 100644 --- a/src/main/java/com/example/devSns/service/MemberService.java +++ b/src/main/java/com/example/devSns/service/MemberService.java @@ -1,47 +1,63 @@ package com.example.devSns.service; import com.example.devSns.dto.CommentResponse; +import com.example.devSns.dto.LoginRequest; import com.example.devSns.dto.PostResponse; -import com.example.devSns.entity.Comment; +import com.example.devSns.dto.SignUpRequest; import com.example.devSns.entity.Like; import com.example.devSns.entity.Member; -import com.example.devSns.entity.Post; +import com.example.devSns.jwt.JwtUtil; import com.example.devSns.repository.MemberRepository; import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; - import java.util.List; @Service @RequiredArgsConstructor @Transactional(readOnly = true) public class MemberService { - private final MemberRepository memberRepository; + private final MemberRepository MemberRepository; + private final BCryptPasswordEncoder PasswordEncoder; + private final JwtUtil JwtUtil; @Transactional - public Member join(Member member) { - validateDuplicateMember(member.getUsername()); - return memberRepository.save(member); + public Member join(SignUpRequest signUpRequest) { + validateDuplicateMember(signUpRequest.getUsername(), signUpRequest.getEmail()); // 이메일 중복 체크 + String encodedPassword = PasswordEncoder.encode(signUpRequest.getPassword()); + Member member = Member.create( + signUpRequest.getUsername(), + signUpRequest.getEmail(), + encodedPassword + ); + return MemberRepository.save(member); } - private void validateDuplicateMember(String username) { - boolean exits = memberRepository.findByUsername(username).isPresent(); - if (exits) { - throw new IllegalStateException("Already exists member "+username); + + private void validateDuplicateMember(String username, String email) { + boolean usernameExists = MemberRepository.FindByUsername(username).isPresent(); + boolean emailExists = MemberRepository.FindByEmail(email).isPresent(); + + if (usernameExists) { + throw new IllegalStateException("Already exists member with username " + username); + } + + if (emailExists) { + throw new IllegalStateException("Already exists member with email " + email); } } public Member findMemberById(long id) { - return memberRepository.findById(id) + return MemberRepository.findById(id) .orElseThrow(() -> new RuntimeException("member not found")); } public List searchMembers(String keyword){ - return memberRepository.findByUsernameContaining(keyword); + return MemberRepository.FindByUsernameContaining(keyword); } public List getPostsByMember(Long memberId){ - Member member = memberRepository.findById(memberId) + Member member = MemberRepository.findById(memberId) .orElseThrow(() -> new IllegalArgumentException("member not found")); return member.getPosts().stream() @@ -50,7 +66,7 @@ public List getPostsByMember(Long memberId){ } public List getCommentsByMember(Long memberId) { - Member member = memberRepository.findById(memberId) + Member member = MemberRepository.findById(memberId) .orElseThrow(() -> new IllegalArgumentException("member not found")); return member.getComments().stream() @@ -58,21 +74,48 @@ public List getCommentsByMember(Long memberId) { .toList(); } public List getLikedPosts(Long memberId) { - Member member = memberRepository.findById(memberId) + Member member = MemberRepository.findById(memberId) .orElseThrow(() -> new IllegalArgumentException("member not found")); return member.getLikes().stream() .map(like -> new PostResponse(like.getPost())) .toList(); } + public boolean isEmailExists(String email) { + return MemberRepository.FindByEmail(email).isPresent(); + } + + @Transactional(readOnly = false) + public Member register(SignUpRequest signUpRequest) { + validateDuplicateMember(signUpRequest.getUsername(), signUpRequest.getEmail()); + String encodedPassword = PasswordEncoder.encode(signUpRequest.getPassword()); + Member member = Member.create(signUpRequest.getUsername(), signUpRequest.getEmail(), encodedPassword); + return MemberRepository.save(member); + } @Transactional(readOnly = true) public List getLikesByMember(Long memberId) { - Member member = memberRepository.findById(memberId) + Member member = MemberRepository.findById(memberId) .orElseThrow(() -> new IllegalArgumentException("member not found")); return member.getLikes(); } + @Transactional(readOnly = true) + public String login(LoginRequest loginRequest) { + Member member = MemberRepository.FindByEmail(loginRequest.getEmail()) + .orElseThrow(()-> new RuntimeException("Invalid email or password")); + + boolean passwordMatch = PasswordEncoder.matches(loginRequest.getPassword(), member.getPassword()); + System.out.println("Password Match: " + passwordMatch); + if (!passwordMatch) { + throw new RuntimeException("Invalid email or password"); + } + + String token = JwtUtil.generateToken(member.getId(), member.getEmail()); + System.out.println("Generated Token: " + token); // 토큰 출력하여 확인 + return token; + } + } diff --git a/src/main/java/com/example/devSns/service/PostService.java b/src/main/java/com/example/devSns/service/PostService.java index 1a4bb4f..5a81ffb 100644 --- a/src/main/java/com/example/devSns/service/PostService.java +++ b/src/main/java/com/example/devSns/service/PostService.java @@ -1,41 +1,40 @@ package com.example.devSns.service; import com.example.devSns.dto.PostCreateRequest; -import com.example.devSns.dto.PostResponse; import com.example.devSns.dto.PostUpdateRequest; import com.example.devSns.entity.Member; import com.example.devSns.entity.Post; import com.example.devSns.repository.MemberRepository; import com.example.devSns.repository.PostRepository; +import org.springframework.data.domain.Page; +import org.springframework.data.domain.PageRequest; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.time.LocalDateTime; -import java.util.List; -import java.util.Optional; - @Service @Transactional(readOnly = true) public class PostService { - private final PostRepository postRepository; - private final MemberRepository memberRepository; + private final PostRepository PostRepository; + private final MemberRepository MemberRepository; public PostService(PostRepository postRepository,MemberRepository memberRepository) { - this.postRepository = postRepository; - this.memberRepository = memberRepository; + this.PostRepository = postRepository; + this.MemberRepository = memberRepository; } - public List findAll(){ - return postRepository.findAll(); + public Page FindAll(int page, int size) { + Pageable pageable = PageRequest.of(page, size); + return PostRepository.findAll(pageable); } - public Post findById(Long id){ - return postRepository.findById(id).orElseThrow(()-> new IllegalArgumentException("Post not found")); + public Post FindById(Long id){ + return PostRepository.findById(id).orElseThrow(()-> new IllegalArgumentException("Post not found")); } @Transactional - public Post createPost(PostCreateRequest request){ - Member member = memberRepository.findById(request.getMemberId()) + public Post CreatePost(PostCreateRequest request){ + Member member = MemberRepository.findById(request.getMemberId()) .orElseThrow(() -> new IllegalArgumentException("member not found")); Post post = Post.create( @@ -43,12 +42,12 @@ public Post createPost(PostCreateRequest request){ member // ✔ member 연결 ); - return postRepository.save(post); + return PostRepository.save(post); } @Transactional - public Post updatePost(Long id, PostUpdateRequest request) { - Post existingPost = postRepository.findById(id) + public Post UpdatePost(Long id, PostUpdateRequest request) { + Post existingPost = PostRepository.findById(id) .orElseThrow(()-> new IllegalArgumentException("post not found")); existingPost.updateContent(request.getContent()); return existingPost; @@ -56,6 +55,6 @@ public Post updatePost(Long id, PostUpdateRequest request) { @Transactional public void delete(Long id){ - postRepository.deleteById(id); + PostRepository.deleteById(id); } } diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f3fabed..fb571c8 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -10,4 +10,10 @@ spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver spring.jpa.hibernate.ddl-auto=update # true? ???? ?? ???? sql?? ???? ??? ? ????. spring.jpa.show-sql=true -spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect \ No newline at end of file +spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQL8Dialect + +spring.jpa.properties.hibernate.format_sql=true + +spring.datasource.hikari.read-only=false + +logging.level.org.springframework.security=DEBUG \ No newline at end of file diff --git a/src/test/java/com/example/devSns/LikeServiceTest.java b/src/test/java/com/example/devSns/LikeServiceTest.java index 3ae596b..2047c11 100644 --- a/src/test/java/com/example/devSns/LikeServiceTest.java +++ b/src/test/java/com/example/devSns/LikeServiceTest.java @@ -46,7 +46,7 @@ public void setup() { @DisplayName("멤버 게시글에 좋아요 가능") void memberCanLikePost(){ likeService.toggleLike(member.getId(),post.getId()); - boolean exists = likeRepository.existsByMemberAndPost(member,post); + boolean exists = likeRepository.ExistsByMemberAndPost(member,post); assertThat(exists).isTrue(); } @@ -54,10 +54,10 @@ void memberCanLikePost(){ @DisplayName("좋아요 두번 누르면 취소 가능") void likeIsToggled(){ likeService.toggleLike(member.getId(),post.getId()); - assertThat(likeRepository.countByPost(post)).isEqualTo(1); + assertThat(likeRepository.CountByPost(post)).isEqualTo(1); likeService.toggleLike(member.getId(),post.getId()); - assertThat(likeRepository.countByPost(post)).isEqualTo(0); + assertThat(likeRepository.CountByPost(post)).isEqualTo(0); } @Test diff --git a/src/test/java/com/example/devSns/LoginTest.java b/src/test/java/com/example/devSns/LoginTest.java new file mode 100644 index 0000000..9a28aa1 --- /dev/null +++ b/src/test/java/com/example/devSns/LoginTest.java @@ -0,0 +1,44 @@ +package com.example.devSns; + +import com.example.devSns.dto.LoginRequest; +import com.example.devSns.dto.SignUpRequest; +import com.example.devSns.service.MemberService; +import org.junit.jupiter.api.Test; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.boot.test.context.SpringBootTest; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertTrue; + +@SpringBootTest +public class LoginTest { + + @Autowired + private BCryptPasswordEncoder passwordEncoder; + + @Autowired + private MemberService memberService; + + @Test + void testPasswordEncoderMatches() { + String rawPassword = "password123"; + String encodedPassword = passwordEncoder.encode(rawPassword); + + boolean isMatch = passwordEncoder.matches(rawPassword, encodedPassword); + assertTrue(isMatch); + } + + @Test + void testSignUpAndLogin() { + + SignUpRequest signUpRequest = new SignUpRequest("user2", "user2@example.com", "password123"); + memberService.join(signUpRequest); // 회원가입 호출 + + LoginRequest loginRequest = new LoginRequest("user2@example.com", "password123"); + + String token = memberService.login(loginRequest); + + assertNotNull(token); + } +} diff --git a/src/test/java/com/example/devSns/MemberServiceTest.java b/src/test/java/com/example/devSns/MemberServiceTest.java index de12264..711109a 100644 --- a/src/test/java/com/example/devSns/MemberServiceTest.java +++ b/src/test/java/com/example/devSns/MemberServiceTest.java @@ -2,6 +2,7 @@ import com.example.devSns.dto.CommentResponse; import com.example.devSns.dto.PostResponse; +import com.example.devSns.dto.SignUpRequest; import com.example.devSns.entity.*; import com.example.devSns.repository.CommentRepository; import com.example.devSns.repository.MemberRepository; @@ -35,11 +36,11 @@ class MemberServiceTest { @Test @DisplayName("회원 가입 성공") void 회원가입_성공() { - Member member = Member.create("강지원", "test@test.com", "1234"); + SignUpRequest request = new SignUpRequest("강지원", "test@test.com", "1234"); - memberService.join(member); + memberService.join(request); - Member found = memberRepository.findByUsername("강지원") + Member found = memberRepository.FindByUsername("강지원") .orElseThrow(); assertThat(found.getEmail()).isEqualTo("test@test.com"); @@ -48,12 +49,12 @@ class MemberServiceTest { @Test @DisplayName("중복 아이디 가입 예외") void 중복회원_예외() { - Member m1 = Member.create("강지원", "a@test.com", "1111"); - Member m2 = Member.create("강지원", "b@test.com", "2222"); + SignUpRequest request1 = new SignUpRequest("강지원", "a@test.com", "1111"); + SignUpRequest request2 = new SignUpRequest("강지원", "b@test.com", "2222"); - memberService.join(m1); + memberService.join(request1); - assertThatThrownBy(() -> memberService.join(m2)) + assertThatThrownBy(() -> memberService.join(request2)) .isInstanceOf(IllegalStateException.class) .hasMessageContaining("Already exists member"); } @@ -101,7 +102,7 @@ class MemberServiceTest { .post(post) .member(member) .build()); - c1.assignMember(member); + c1.AssignMember(member); commentRepository.save(c1); Comment c2 = commentRepository.save(Comment.builder() @@ -110,7 +111,7 @@ class MemberServiceTest { .post(post) .member(member) .build()); - c1.assignMember(member); + c1.AssignMember(member); commentRepository.save(c2); List comments = memberService.getCommentsByMember(member.getId());