From 071b8ffc09b8b4293a92d7322003e9bca1eecada Mon Sep 17 00:00:00 2001 From: tomchccom Date: Wed, 26 Nov 2025 17:39:35 +0900 Subject: [PATCH 01/20] =?UTF-8?q?fix=20:=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EB=A1=9C=EC=A7=81=20=EA=B0=9C=EC=84=A0=20-=20=EB=91=90?= =?UTF-8?q?=EA=B0=80=EC=A7=80=20=EC=83=81=ED=83=9C=EB=A7=8C=20=EC=A1=B4?= =?UTF-8?q?=EC=9E=AC=ED=95=98=EB=8A=94=20=EA=B2=BD=EC=9A=B0=20enum?= =?UTF-8?q?=EC=9D=84=20=EC=82=AC=EC=9A=A9=ED=95=98=EC=A7=80=20=EC=95=8A?= =?UTF-8?q?=EA=B3=A0,=20boolean=EC=9C=BC=EB=A1=9C=20=EA=B4=80=EB=A6=AC?= =?UTF-8?q?=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/devSns/Heart/Heart.java | 14 ++++---------- .../com/example/devSns/Heart/HeartRepository.java | 2 +- .../com/example/devSns/Member/MemberService.java | 2 +- .../java/com/example/devSns/Post/PostService.java | 3 +-- .../com/example/devSns/Heart/HeartEntityTest.java | 8 ++++---- .../example/devSns/Member/MemberServiceTest.java | 4 ++-- .../com/example/devSns/Post/PostServiceTest.java | 7 +++---- 7 files changed, 16 insertions(+), 24 deletions(-) diff --git a/src/main/java/com/example/devSns/Heart/Heart.java b/src/main/java/com/example/devSns/Heart/Heart.java index 90ae7ee..4111221 100644 --- a/src/main/java/com/example/devSns/Heart/Heart.java +++ b/src/main/java/com/example/devSns/Heart/Heart.java @@ -29,21 +29,15 @@ public class Heart { // 리포지터리에 메소드로 계산하면 되겠지? @Column(name ="like_status", nullable = false) - @Enumerated(EnumType.STRING) - private LikeStatus like; + private boolean liked; - public Heart(Post post, Member member, LikeStatus heart) { + public Heart(Post post, Member member, boolean like) { this.post = post; this.member = member; - this.like = heart; + this.liked = like; } public void toggleLike() { - - if (like == LikeStatus.LIKE) { - like = LikeStatus.NONE; - }else if(like == LikeStatus.NONE){ - like = LikeStatus.LIKE; - } + this.liked = !this.liked; } } diff --git a/src/main/java/com/example/devSns/Heart/HeartRepository.java b/src/main/java/com/example/devSns/Heart/HeartRepository.java index 43b677f..f41510b 100644 --- a/src/main/java/com/example/devSns/Heart/HeartRepository.java +++ b/src/main/java/com/example/devSns/Heart/HeartRepository.java @@ -8,5 +8,5 @@ public interface HeartRepository extends JpaRepository { Boolean existsByPostIdAndMemberId(Long postId, Long memberId); Optional findByPostIdAndMemberId(Long postId, Long memberId); - long countByPostIdAndLike(Long postId, LikeStatus like); + long countByPostIdAndLiked(Long postId, Boolean Liked); } diff --git a/src/main/java/com/example/devSns/Member/MemberService.java b/src/main/java/com/example/devSns/Member/MemberService.java index 0053d29..d56fc16 100644 --- a/src/main/java/com/example/devSns/Member/MemberService.java +++ b/src/main/java/com/example/devSns/Member/MemberService.java @@ -77,7 +77,7 @@ public void convertLikeStatus(Long postId, Long memberId){ // memberId는 나중 Heart heart = new Heart( post, member, - LikeStatus.NONE + false ); heart.toggleLike(); diff --git a/src/main/java/com/example/devSns/Post/PostService.java b/src/main/java/com/example/devSns/Post/PostService.java index 0ce1ce5..74cc815 100644 --- a/src/main/java/com/example/devSns/Post/PostService.java +++ b/src/main/java/com/example/devSns/Post/PostService.java @@ -2,7 +2,6 @@ import com.example.devSns.Comment.CommentRepository; import com.example.devSns.Heart.HeartRepository; -import com.example.devSns.Heart.LikeStatus; import com.example.devSns.Member.Member; import com.example.devSns.Member.MemberRepository; import com.example.devSns.Post.Dto.AddPostRequestDto; @@ -75,7 +74,7 @@ public void countLikes() { List posts = postRepository.findAll(); for (Post post : posts) { - long likeCount = heartRepository.countByPostIdAndLike(post.getId(), LikeStatus.LIKE); + long likeCount = heartRepository.countByPostIdAndLiked(post.getId(), true); post.updateLikeCount((Long) likeCount); } } diff --git a/src/test/java/com/example/devSns/Heart/HeartEntityTest.java b/src/test/java/com/example/devSns/Heart/HeartEntityTest.java index ba5366c..d55696e 100644 --- a/src/test/java/com/example/devSns/Heart/HeartEntityTest.java +++ b/src/test/java/com/example/devSns/Heart/HeartEntityTest.java @@ -16,11 +16,11 @@ void toggleLike_noneToLike() { Post post = new Post("content", "writer", 0L); Member member = new Member("nick", "email", "pwd", Gender.MALE, 28); - Heart heart = new Heart(post, member, LikeStatus.NONE); + Heart heart = new Heart(post, member, false); heart.toggleLike(); - assertEquals(LikeStatus.LIKE, heart.getLike()); + assertEquals(true, heart.isLiked()); } @Test @@ -29,11 +29,11 @@ void toggleLike_likeToNone() { Post post = new Post("content", "writer", 0L); Member member = new Member("nick", "email", "pwd", Gender.MALE, 28); - Heart heart = new Heart(post, member, LikeStatus.LIKE); + Heart heart = new Heart(post, member, true); heart.toggleLike(); - assertEquals(LikeStatus.NONE, heart.getLike()); + assertEquals(false, heart.isLiked()); } } diff --git a/src/test/java/com/example/devSns/Member/MemberServiceTest.java b/src/test/java/com/example/devSns/Member/MemberServiceTest.java index 99eb64e..20228ed 100644 --- a/src/test/java/com/example/devSns/Member/MemberServiceTest.java +++ b/src/test/java/com/example/devSns/Member/MemberServiceTest.java @@ -146,7 +146,7 @@ void convertLikeStatus_newHeart() { void convertLikeStatus_existingHeart() { Post post = new Post("content", "writer", 0L); Member member = new Member("nickname", "email", "password", Gender.FEMALE, 22); - Heart heart = new Heart(post, member, LikeStatus.NONE); + Heart heart = new Heart(post, member, false); when(postRepository.findById(1L)).thenReturn(Optional.of(post)); when(memberRepository.findById(1L)).thenReturn(Optional.of(member)); @@ -155,7 +155,7 @@ void convertLikeStatus_existingHeart() { memberService.convertLikeStatus(1L, 1L); - assertEquals(LikeStatus.LIKE, heart.getLike()); // 토글 확인 + assertEquals(true, heart.isLiked()); // 토글 확인 verify(heartRepository).save(heart); } diff --git a/src/test/java/com/example/devSns/Post/PostServiceTest.java b/src/test/java/com/example/devSns/Post/PostServiceTest.java index 286322e..4c62adb 100644 --- a/src/test/java/com/example/devSns/Post/PostServiceTest.java +++ b/src/test/java/com/example/devSns/Post/PostServiceTest.java @@ -3,7 +3,6 @@ import com.example.devSns.Comment.Comment; import com.example.devSns.Comment.CommentRepository; import com.example.devSns.Heart.HeartRepository; -import com.example.devSns.Heart.LikeStatus; import com.example.devSns.Member.Member; import com.example.devSns.Member.MemberRepository; import com.example.devSns.Post.Dto.AddPostRequestDto; @@ -162,15 +161,15 @@ void countLikes_success() { List posts = List.of(post1, post2); when(postRepository.findAll()).thenReturn(posts); - when(heartRepository.countByPostIdAndLike(1L, LikeStatus.LIKE)).thenReturn(5L); - when(heartRepository.countByPostIdAndLike(2L, LikeStatus.LIKE)).thenReturn(3L); + when(heartRepository.countByPostIdAndLiked(1L, true)).thenReturn(5L); + when(heartRepository.countByPostIdAndLiked(2L, true)).thenReturn(3L); // when postService.countLikes(); // then verify(postRepository).findAll(); - verify(heartRepository, times(2)).countByPostIdAndLike(anyLong(), eq(LikeStatus.LIKE)); + verify(heartRepository, times(2)).countByPostIdAndLiked(anyLong(), eq(true)); assertEquals(5L, post1.getLikeCount()); assertEquals(3L, post2.getLikeCount()); From 4208540399071c08c2d30383cac3a0b9504f807c Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 20:28:42 +0900 Subject: [PATCH 02/20] =?UTF-8?q?fix=20:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20validation=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/devSns/Member/Dto/SignMemberRequestDto.java | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/example/devSns/Member/Dto/SignMemberRequestDto.java b/src/main/java/com/example/devSns/Member/Dto/SignMemberRequestDto.java index dd8c218..9743747 100644 --- a/src/main/java/com/example/devSns/Member/Dto/SignMemberRequestDto.java +++ b/src/main/java/com/example/devSns/Member/Dto/SignMemberRequestDto.java @@ -2,10 +2,7 @@ import com.example.devSns.Member.Gender; import com.example.devSns.Member.Member; -import jakarta.validation.constraints.Email; -import jakarta.validation.constraints.NotBlank; -import jakarta.validation.constraints.NotNull; -import jakarta.validation.constraints.Size; +import jakarta.validation.constraints.*; public record SignMemberRequestDto( @@ -18,6 +15,10 @@ public record SignMemberRequestDto( @NotBlank(message = "공백 또는 null 값은 허용하지 않습니다") @Size(min = 8, max =16, message = "비밀번호는 8자 이상 16자 이하로 설정해주세요") + @Pattern( + regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]+$", + message = "영어, 숫자, 특수문자를 각각 최소 1개 이상 포함해야 합니다" + ) String password, @NotNull(message = "공백 또는 null 값은 허용하지 않습니다") From 9108967afce6134f324e10ad9bc86c0aeff2d853 Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 20:36:43 +0900 Subject: [PATCH 03/20] =?UTF-8?q?fix=20:=20ResponseEntity=20=ED=95=98?= =?UTF-8?q?=EB=93=9C=EC=BD=94=EB=94=A9=EB=90=9C=20=EC=BD=94=EB=93=9C=20?= =?UTF-8?q?=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../devSns/Comment/CommentController.java | 18 +++++++++--------- .../devSns/Member/MemberController.java | 6 +++--- .../example/devSns/Post/PostController.java | 2 +- 3 files changed, 13 insertions(+), 13 deletions(-) diff --git a/src/main/java/com/example/devSns/Comment/CommentController.java b/src/main/java/com/example/devSns/Comment/CommentController.java index efeb117..419e61b 100644 --- a/src/main/java/com/example/devSns/Comment/CommentController.java +++ b/src/main/java/com/example/devSns/Comment/CommentController.java @@ -30,7 +30,7 @@ public ResponseEntity postComment( commentService.createComment(post_id, dto); return ResponseEntity.status(HttpStatus.CREATED).build(); }catch (EntityNotFoundException e){ - return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + return ResponseEntity.notFound().build(); } @@ -47,13 +47,13 @@ public ResponseEntity postReplyComment( commentService.createReplyComment(post_id, comment_id, dto); return ResponseEntity.status(HttpStatus.CREATED).build(); }catch (EntityNotFoundException e){ - return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + return ResponseEntity.notFound().build(); } } @GetMapping("posts/{post_id}/comments") public ResponseEntity> getComments(@PathVariable("post_id") Long post_id) { - return ResponseEntity.status(HttpStatus.OK).body(commentService.getAllComments(post_id)); + return ResponseEntity.ok().body(commentService.getAllComments(post_id)); } @GetMapping("posts/{post_id}/comments/{comment_id}") @@ -61,10 +61,10 @@ public ResponseEntity getCommentsByPostId( @PathVariable("post_id") Long post_id, @PathVariable("comment_id") Long comment_id) { try{ - return ResponseEntity.status(HttpStatus.OK).body(commentService.getCommentById(post_id, comment_id)); + return ResponseEntity.ok(commentService.getCommentById(post_id, comment_id)); }catch (EntityNotFoundException e){ - return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + return ResponseEntity.notFound().build(); } } @@ -75,9 +75,9 @@ public ResponseEntity updateComment( @Valid @RequestBody UpdateCommentDto dto) { try{ - return ResponseEntity.status(HttpStatus.OK).body(commentService.updateComment(comment_id, dto)); + return ResponseEntity.ok().body(commentService.updateComment(comment_id, dto)); }catch (EntityNotFoundException e){ - return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + return ResponseEntity.notFound().build(); } @@ -86,10 +86,10 @@ public ResponseEntity updateComment( public ResponseEntity deleteComment(@PathVariable("comment_id") Long comment_id) { try{ commentService.deleteCommentById(comment_id); - return ResponseEntity.status(HttpStatus.NO_CONTENT).build(); + return ResponseEntity.noContent().build(); } catch (EntityNotFoundException e){ - return ResponseEntity.status(HttpStatus.NOT_FOUND).build(); + return ResponseEntity.notFound().build(); } } diff --git a/src/main/java/com/example/devSns/Member/MemberController.java b/src/main/java/com/example/devSns/Member/MemberController.java index 4a037ae..e4dcfa7 100644 --- a/src/main/java/com/example/devSns/Member/MemberController.java +++ b/src/main/java/com/example/devSns/Member/MemberController.java @@ -22,7 +22,7 @@ public ResponseEntity createMember( @Valid @RequestBody SignMemberRequestDto dto) { - return ResponseEntity.status(HttpStatus.CREATED).body(memberService.createMember(dto)); + return ResponseEntity.status(201).body(memberService.createMember(dto)); } // 특정 멤버 검색하기 @@ -30,7 +30,7 @@ public ResponseEntity createMember( public ResponseEntity getMember( @PathVariable(name ="member_id") Long memberId ) { - return ResponseEntity.status(200).body(memberService.getMemberById(memberId)); + return ResponseEntity.ok().body(memberService.getMemberById(memberId)); } // like_status toggle @@ -52,7 +52,7 @@ public ResponseEntity convertLike( public ResponseEntity getMemberPostAndComment( @PathVariable(name = "member_id") Long memberId ){ - return ResponseEntity.status(200).body(memberService.getMemberPostAndComment(memberId)); + return ResponseEntity.ok().body(memberService.getMemberPostAndComment(memberId)); } } diff --git a/src/main/java/com/example/devSns/Post/PostController.java b/src/main/java/com/example/devSns/Post/PostController.java index 9862eb3..b5fe8cc 100644 --- a/src/main/java/com/example/devSns/Post/PostController.java +++ b/src/main/java/com/example/devSns/Post/PostController.java @@ -24,7 +24,7 @@ public ResponseEntity createPost( @PathVariable("member_id") Long member_id) { postService.createPost(Dto,member_id); - return ResponseEntity.status(HttpStatus.CREATED).build(); + return ResponseEntity.status(201).build(); } @GetMapping("/posts") From e9ef3a4f052d70ca59b1bddba8149e8ecef8d7de Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 20:41:41 +0900 Subject: [PATCH 04/20] =?UTF-8?q?fix=20:=20=EC=83=9D=EC=84=B1=EC=9E=90=20?= =?UTF-8?q?=EC=9D=B8=EC=9E=90=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/devSns/Post/Post.java | 4 ++-- src/main/java/com/example/devSns/Post/PostService.java | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/main/java/com/example/devSns/Post/Post.java b/src/main/java/com/example/devSns/Post/Post.java index 2dda81f..f7dabc5 100644 --- a/src/main/java/com/example/devSns/Post/Post.java +++ b/src/main/java/com/example/devSns/Post/Post.java @@ -48,10 +48,10 @@ public void Update(UpdatePostRequestDto Dto){ this.userName = Dto.username(); this.updatedAt = LocalDateTime.now(); } - public Post(String content, String userName, Long likeCount) { + public Post(String content, String userName) { this.content = content; this.userName = userName; - this.likeCount = likeCount; + this.likeCount = 0L; } @PrePersist diff --git a/src/main/java/com/example/devSns/Post/PostService.java b/src/main/java/com/example/devSns/Post/PostService.java index 74cc815..3391b25 100644 --- a/src/main/java/com/example/devSns/Post/PostService.java +++ b/src/main/java/com/example/devSns/Post/PostService.java @@ -29,8 +29,7 @@ public class PostService { public void createPost(AddPostRequestDto Dto, Long memberId) { Post post = new Post( Dto.content(), - Dto.username(), - 0L + Dto.username() ); Member member = memberRepository.findById(memberId) .orElseThrow(()->new EntityNotFoundException("Member not found")); From 812551bb25271b8cbd17be425d4b0d73add0b79f Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 20:42:06 +0900 Subject: [PATCH 05/20] =?UTF-8?q?fix=20:=20=ED=8A=B8=EB=9E=9C=EC=9E=AD?= =?UTF-8?q?=EC=85=98=20=EC=A0=81=EC=9A=A9=EC=9C=BC=EB=A1=9C=20=EC=9E=90?= =?UTF-8?q?=EB=8F=99=20flush=EB=A1=9C=20=EB=B3=80=EA=B2=BD=EA=B0=90?= =?UTF-8?q?=EC=A7=80=20=ED=95=98=EB=8F=84=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/devSns/Member/MemberService.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/com/example/devSns/Member/MemberService.java b/src/main/java/com/example/devSns/Member/MemberService.java index d56fc16..54175ac 100644 --- a/src/main/java/com/example/devSns/Member/MemberService.java +++ b/src/main/java/com/example/devSns/Member/MemberService.java @@ -81,14 +81,12 @@ public void convertLikeStatus(Long postId, Long memberId){ // memberId는 나중 ); heart.toggleLike(); - heartRepository.save(heart); } else{ Heart heart = heartRepository.findByPostIdAndMemberId(postId, memberId) .orElseThrow(() -> new EntityNotFoundException("게시글 없음")); heart.toggleLike(); - heartRepository.save(heart); } } From 58dec04cdba391e68e8e9e57c6a39a639046f005 Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 20:43:03 +0900 Subject: [PATCH 06/20] =?UTF-8?q?chore:=20import=20=EC=B5=9C=EC=A0=81?= =?UTF-8?q?=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/devSns/Comment/CommentController.java | 1 - src/main/java/com/example/devSns/Member/MemberController.java | 1 - src/main/java/com/example/devSns/Post/PostController.java | 1 - 3 files changed, 3 deletions(-) diff --git a/src/main/java/com/example/devSns/Comment/CommentController.java b/src/main/java/com/example/devSns/Comment/CommentController.java index 419e61b..3cf764f 100644 --- a/src/main/java/com/example/devSns/Comment/CommentController.java +++ b/src/main/java/com/example/devSns/Comment/CommentController.java @@ -2,7 +2,6 @@ import com.example.devSns.Comment.Dto.CreateCommentDto; import com.example.devSns.Comment.Dto.UpdateCommentDto; - import jakarta.persistence.EntityNotFoundException; import jakarta.validation.Valid; import org.springframework.http.HttpStatus; diff --git a/src/main/java/com/example/devSns/Member/MemberController.java b/src/main/java/com/example/devSns/Member/MemberController.java index e4dcfa7..6ace248 100644 --- a/src/main/java/com/example/devSns/Member/MemberController.java +++ b/src/main/java/com/example/devSns/Member/MemberController.java @@ -6,7 +6,6 @@ import jakarta.persistence.EntityNotFoundException; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; diff --git a/src/main/java/com/example/devSns/Post/PostController.java b/src/main/java/com/example/devSns/Post/PostController.java index b5fe8cc..b2cc2b2 100644 --- a/src/main/java/com/example/devSns/Post/PostController.java +++ b/src/main/java/com/example/devSns/Post/PostController.java @@ -5,7 +5,6 @@ import com.example.devSns.Post.Dto.UpdatePostRequestDto; import jakarta.validation.Valid; import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.*; From 1f89a4739e608ca2a5458a466b43568413a25e29 Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 20:50:00 +0900 Subject: [PATCH 07/20] =?UTF-8?q?feat:=20post=5Fid,=20liked=20=EC=97=90=20?= =?UTF-8?q?=EC=9D=B8=EB=8D=B1=EC=8A=A4=20=EC=B6=94=EA=B0=80=20-=20?= =?UTF-8?q?=EC=96=B4=EB=96=A4=20=EA=B2=8C=EC=8B=9C=EB=AC=BC=EC=97=90=20?= =?UTF-8?q?=EC=A2=8B=EC=95=84=EC=9A=94=EA=B0=80=20=EB=8B=AC=EB=A0=B8?= =?UTF-8?q?=EB=8A=94=EC=A7=80=20=EA=B3=84=EC=82=B0=ED=95=98=EB=8A=94=20?= =?UTF-8?q?=EC=8A=A4=EC=BC=80=EC=A4=84=EB=9F=AC=EC=9D=98=20=EA=B2=8C?= =?UTF-8?q?=EC=8B=9C=EA=B8=80=20=EC=A1=B0=ED=9A=8C=20=EC=86=8D=EB=8F=84?= =?UTF-8?q?=EB=A5=BC=20=ED=96=A5=EC=83=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/devSns/Heart/Heart.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/com/example/devSns/Heart/Heart.java b/src/main/java/com/example/devSns/Heart/Heart.java index 4111221..6670d94 100644 --- a/src/main/java/com/example/devSns/Heart/Heart.java +++ b/src/main/java/com/example/devSns/Heart/Heart.java @@ -11,6 +11,12 @@ @Getter @NoArgsConstructor @AllArgsConstructor +@Table( + name = "heart", + indexes = { + @Index(name ="idx_heart_post_liked", columnList = "post_id , like_status") + } +) public class Heart { @Id From 115e38d83ab7b2d0498665c29e2cd9db21ef58c4 Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 20:58:15 +0900 Subject: [PATCH 08/20] =?UTF-8?q?feat:=20spring=20security=20=EC=9D=98?= =?UTF-8?q?=EC=A1=B4=EC=84=B1=20=EC=B6=94=EA=B0=80=20-=20BCrypt=20?= =?UTF-8?q?=EC=82=AC=EC=9A=A9=20=EB=AA=A9=EC=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- build.gradle | 2 ++ 1 file changed, 2 insertions(+) diff --git a/build.gradle b/build.gradle index 9189b81..4ad8ac9 100644 --- a/build.gradle +++ b/build.gradle @@ -28,6 +28,8 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-jdbc' implementation 'org.springframework.boot:spring-boot-starter-data-jpa' implementation 'org.springframework.boot:spring-boot-starter-validation' + implementation 'org.springframework.boot:spring-boot-starter-security' + } tasks.named('test') { From ca4b4992c445aa226408a8568d02d38be16838cf Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 20:58:45 +0900 Subject: [PATCH 09/20] =?UTF-8?q?feat:=20Bcrypt=20=EB=B9=88=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 --- .../example/devSns/global/config/AppConfig.java | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 src/main/java/com/example/devSns/global/config/AppConfig.java diff --git a/src/main/java/com/example/devSns/global/config/AppConfig.java b/src/main/java/com/example/devSns/global/config/AppConfig.java new file mode 100644 index 0000000..f05c3fc --- /dev/null +++ b/src/main/java/com/example/devSns/global/config/AppConfig.java @@ -0,0 +1,14 @@ +package com.example.devSns.global.config; + +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; + +@Configuration +public class AppConfig { + + @Bean + public BCryptPasswordEncoder passwordEncoder() { + return new BCryptPasswordEncoder(); + } +} From 063bc257aff9f3332be3dac674e961c59e0ee1c9 Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 21:03:36 +0900 Subject: [PATCH 10/20] =?UTF-8?q?feat:=20=EB=B9=84=EB=B0=80=EB=B2=88?= =?UTF-8?q?=ED=98=B8=20Bcrypt=EB=A1=9C=20=EC=95=94=ED=98=B8=ED=99=94?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/devSns/Member/MemberService.java | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/devSns/Member/MemberService.java b/src/main/java/com/example/devSns/Member/MemberService.java index 54175ac..afb4306 100644 --- a/src/main/java/com/example/devSns/Member/MemberService.java +++ b/src/main/java/com/example/devSns/Member/MemberService.java @@ -2,7 +2,6 @@ import com.example.devSns.Heart.Heart; import com.example.devSns.Heart.HeartRepository; -import com.example.devSns.Heart.LikeStatus; import com.example.devSns.Member.Dto.GetMemberPostAndCommentResponseDto; import com.example.devSns.Member.Dto.GetMemberResponseDto; import com.example.devSns.Member.Dto.SignMemberRequestDto; @@ -12,6 +11,7 @@ import com.example.devSns.Post.PostRepository; import com.example.devSns.Post.PostService; import com.example.devSns.global.EntityNotFoundException; +import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder; import org.springframework.transaction.annotation.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -27,6 +27,8 @@ public class MemberService { private final PostService postService; private final HeartRepository heartRepository; private final PostRepository postRepository; + private final BCryptPasswordEncoder passwordEncoder; + // (선택) 팔로우 기능 구현 (닉네임으로 친구 추가 보내기) @@ -34,7 +36,17 @@ public class MemberService { // 멤버 객체 생성 (회원가입_느낌으로다가) @Transactional public GetMemberResponseDto createMember(SignMemberRequestDto dto) { - Member saved = memberRepository.save(dto.toEntity()); + String encryptedPw = passwordEncoder.encode(dto.password()); + + Member member = new Member( + dto.nickname(), + dto.email(), + encryptedPw, + dto.gender(), + dto.age() + ); + + Member saved = memberRepository.save(member); return new GetMemberResponseDto(saved); } From d085569ffb1f9f21aa5fbc13f2e0784063fb0916 Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 21:07:33 +0900 Subject: [PATCH 11/20] build: add jwt dependency to build.gradle --- build.gradle | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/build.gradle b/build.gradle index 4ad8ac9..42d29ca 100644 --- a/build.gradle +++ b/build.gradle @@ -30,6 +30,10 @@ dependencies { implementation 'org.springframework.boot:spring-boot-starter-validation' implementation 'org.springframework.boot:spring-boot-starter-security' + implementation 'io.jsonwebtoken:jjwt-api:0.11.5' + implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' + implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' // JSON 파싱용 + } tasks.named('test') { From f82ab5c6c87b3fc4f7d5de7febb043a7d7cb69da Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 21:08:21 +0900 Subject: [PATCH 12/20] feat: add jwt util for token generation and validation --- .../java/com/example/devSns/Jwt/JwtUtil.java | 48 +++++++++++++++++++ 1 file changed, 48 insertions(+) create mode 100644 src/main/java/com/example/devSns/Jwt/JwtUtil.java 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..f990ad4 --- /dev/null +++ b/src/main/java/com/example/devSns/Jwt/JwtUtil.java @@ -0,0 +1,48 @@ +package com.example.devSns.Jwt; + +import io.jsonwebtoken.Jwts; +import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.stereotype.Component; + +import java.util.Date; + +@Component +public class JwtUtil { + + private final String secret = "mySecretKeyForJwtToken"; // 실제 프로젝트에서는 환경변수로 관리 + private final long EXPIRATION = 1000L * 60 * 60; // 1시간 + + public String createToken(Long memberId) { + Date now = new Date(); + Date expireTime = new Date(now.getTime() + EXPIRATION); + + return Jwts.builder() + .setSubject(String.valueOf(memberId)) + .setIssuedAt(now) + .setExpiration(expireTime) + .signWith(SignatureAlgorithm.HS256, secret) + .compact(); + } + + public boolean validateToken(String token) { + try { + Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token); + return true; + } catch (Exception e) { + return false; + } + } + + public Long extractMemberId(String token) { + return Long.valueOf( + Jwts.parser() + .setSigningKey(secret) + .parseClaimsJws(token) + .getBody() + .getSubject() + ); + } +} + From 2bd85da4ed96cd2c0936a9663301d3661539f4a4 Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 21:10:36 +0900 Subject: [PATCH 13/20] feat: add login service logic and repository query method --- .../example/devSns/Member/MemberRepository.java | 4 +++- .../com/example/devSns/Member/MemberService.java | 16 +++++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/example/devSns/Member/MemberRepository.java b/src/main/java/com/example/devSns/Member/MemberRepository.java index 160b4d0..f84b93b 100644 --- a/src/main/java/com/example/devSns/Member/MemberRepository.java +++ b/src/main/java/com/example/devSns/Member/MemberRepository.java @@ -2,6 +2,8 @@ import org.springframework.data.jpa.repository.JpaRepository; -public interface MemberRepository extends JpaRepository { +import java.util.Optional; +public interface MemberRepository extends JpaRepository { + Optional findByEmail(String email); } diff --git a/src/main/java/com/example/devSns/Member/MemberService.java b/src/main/java/com/example/devSns/Member/MemberService.java index afb4306..4a16bed 100644 --- a/src/main/java/com/example/devSns/Member/MemberService.java +++ b/src/main/java/com/example/devSns/Member/MemberService.java @@ -2,6 +2,7 @@ import com.example.devSns.Heart.Heart; import com.example.devSns.Heart.HeartRepository; +import com.example.devSns.Jwt.JwtUtil; import com.example.devSns.Member.Dto.GetMemberPostAndCommentResponseDto; import com.example.devSns.Member.Dto.GetMemberResponseDto; import com.example.devSns.Member.Dto.SignMemberRequestDto; @@ -28,7 +29,7 @@ public class MemberService { private final HeartRepository heartRepository; private final PostRepository postRepository; private final BCryptPasswordEncoder passwordEncoder; - + private final JwtUtil jwtUtil; // (선택) 팔로우 기능 구현 (닉네임으로 친구 추가 보내기) @@ -103,6 +104,19 @@ public void convertLikeStatus(Long postId, Long memberId){ // memberId는 나중 } + public String login(String email, String rawPassword) { + + Member member = memberRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 이메일입니다.")); + + if (!passwordEncoder.matches(rawPassword, member.getPassword())) { + throw new IllegalArgumentException("비밀번호가 일치하지 않습니다."); + } + + // JWT 발급 + return jwtUtil.createToken(member.getId()); + } + } From b0a3c0ff594e28b4bd0006668af8ccd7dfde5282 Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 21:19:17 +0900 Subject: [PATCH 14/20] refactor: separate login controller, service for clearer auth flow --- .../example/devSns/Login/LoginController.java | 25 +++++++++++++++ .../example/devSns/Login/LoginService.java | 32 +++++++++++++++++++ .../example/devSns/Member/MemberService.java | 13 -------- 3 files changed, 57 insertions(+), 13 deletions(-) create mode 100644 src/main/java/com/example/devSns/Login/LoginController.java create mode 100644 src/main/java/com/example/devSns/Login/LoginService.java diff --git a/src/main/java/com/example/devSns/Login/LoginController.java b/src/main/java/com/example/devSns/Login/LoginController.java new file mode 100644 index 0000000..56f2c5f --- /dev/null +++ b/src/main/java/com/example/devSns/Login/LoginController.java @@ -0,0 +1,25 @@ +package com.example.devSns.Login; + +import lombok.Data; +import lombok.RequiredArgsConstructor; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RestController; + +@RestController +@RequiredArgsConstructor +public class LoginController { + + private final LoginService loginService; + + @PostMapping("/login") + public String login(@RequestBody LoginRequest request) { + return loginService.login(request.getEmail(), request.getPassword()); + } + + @Data + static class LoginRequest { + private String email; + private String password; + } +} diff --git a/src/main/java/com/example/devSns/Login/LoginService.java b/src/main/java/com/example/devSns/Login/LoginService.java new file mode 100644 index 0000000..8d491ae --- /dev/null +++ b/src/main/java/com/example/devSns/Login/LoginService.java @@ -0,0 +1,32 @@ +package com.example.devSns.Login; + +import com.example.devSns.Jwt.JwtUtil; +import com.example.devSns.Member.Member; +import com.example.devSns.Member.MemberRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.security.crypto.password.PasswordEncoder; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@Transactional +@RequiredArgsConstructor +public class LoginService { + + private final MemberRepository memberRepository; + private final PasswordEncoder passwordEncoder; + private final JwtUtil jwtUtil; + + public String login(String email, String rawPassword) { + + Member member = memberRepository.findByEmail(email) + .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 이메일입니다.")); + + if (!passwordEncoder.matches(rawPassword, member.getPassword())) { + throw new IllegalArgumentException("비밀번호가 일치하지 않습니다."); + } + + // JWT 발급 + return jwtUtil.createToken(member.getId()); + } +} diff --git a/src/main/java/com/example/devSns/Member/MemberService.java b/src/main/java/com/example/devSns/Member/MemberService.java index 4a16bed..d4e5a26 100644 --- a/src/main/java/com/example/devSns/Member/MemberService.java +++ b/src/main/java/com/example/devSns/Member/MemberService.java @@ -2,7 +2,6 @@ import com.example.devSns.Heart.Heart; import com.example.devSns.Heart.HeartRepository; -import com.example.devSns.Jwt.JwtUtil; import com.example.devSns.Member.Dto.GetMemberPostAndCommentResponseDto; import com.example.devSns.Member.Dto.GetMemberResponseDto; import com.example.devSns.Member.Dto.SignMemberRequestDto; @@ -29,7 +28,6 @@ public class MemberService { private final HeartRepository heartRepository; private final PostRepository postRepository; private final BCryptPasswordEncoder passwordEncoder; - private final JwtUtil jwtUtil; // (선택) 팔로우 기능 구현 (닉네임으로 친구 추가 보내기) @@ -104,18 +102,7 @@ public void convertLikeStatus(Long postId, Long memberId){ // memberId는 나중 } - public String login(String email, String rawPassword) { - Member member = memberRepository.findByEmail(email) - .orElseThrow(() -> new IllegalArgumentException("존재하지 않는 이메일입니다.")); - - if (!passwordEncoder.matches(rawPassword, member.getPassword())) { - throw new IllegalArgumentException("비밀번호가 일치하지 않습니다."); - } - - // JWT 발급 - return jwtUtil.createToken(member.getId()); - } From 878e021f2988b72312fb1afa22864cbc23b88b86 Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 21:19:26 +0900 Subject: [PATCH 15/20] feat: register authentication interceptor and configure web settings --- .../devSns/global/AuthInterceptor.java | 43 +++++++++++++++++++ .../devSns/global/config/WebConfig.java | 22 ++++++++++ 2 files changed, 65 insertions(+) create mode 100644 src/main/java/com/example/devSns/global/AuthInterceptor.java create mode 100644 src/main/java/com/example/devSns/global/config/WebConfig.java diff --git a/src/main/java/com/example/devSns/global/AuthInterceptor.java b/src/main/java/com/example/devSns/global/AuthInterceptor.java new file mode 100644 index 0000000..9823068 --- /dev/null +++ b/src/main/java/com/example/devSns/global/AuthInterceptor.java @@ -0,0 +1,43 @@ +package com.example.devSns.global; + +import com.example.devSns.Jwt.JwtUtil; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.HandlerInterceptor; + +@Component +public class AuthInterceptor implements HandlerInterceptor { + + private final JwtUtil jwtUtil; + + public AuthInterceptor(JwtUtil jwtUtil) { + this.jwtUtil = jwtUtil; + } + + @Override + public boolean preHandle(HttpServletRequest request, + HttpServletResponse response, + Object handler) throws Exception { + + String auth = request.getHeader("Authorization"); + + if (auth == null || !auth.startsWith("Bearer ")) { + response.sendError(401, "Missing token"); + return false; + } + + String token = auth.substring(7); + + if (!jwtUtil.validateToken(token)) { + response.sendError(401, "Invalid token"); + return false; + } + + Long memberId = jwtUtil.extractMemberId(token); + request.setAttribute("memberId", memberId); + + return true; + } +} + diff --git a/src/main/java/com/example/devSns/global/config/WebConfig.java b/src/main/java/com/example/devSns/global/config/WebConfig.java new file mode 100644 index 0000000..46d0c4f --- /dev/null +++ b/src/main/java/com/example/devSns/global/config/WebConfig.java @@ -0,0 +1,22 @@ +package com.example.devSns.global.config; + +import com.example.devSns.global.AuthInterceptor; +import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Configuration; +import org.springframework.web.servlet.config.annotation.InterceptorRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +@RequiredArgsConstructor +public class WebConfig implements WebMvcConfigurer { + + private final AuthInterceptor authInterceptor; + + @Override + public void addInterceptors(InterceptorRegistry registry) { + registry.addInterceptor(authInterceptor) + .addPathPatterns("/api/**") + .excludePathPatterns("/auth/**"); + } +} + From 3a157895de7c77234902ca91f3cf7899f440ec00 Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 21:21:54 +0900 Subject: [PATCH 16/20] refactor: reorganize API routes by adding /api prefix and moving login to /auth --- .../com/example/devSns/Comment/CommentController.java | 11 ++++++----- .../com/example/devSns/Login/LoginController.java | 2 ++ .../com/example/devSns/Member/MemberController.java | 1 + .../java/com/example/devSns/Post/PostController.java | 3 ++- 4 files changed, 11 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/example/devSns/Comment/CommentController.java b/src/main/java/com/example/devSns/Comment/CommentController.java index 3cf764f..1617a85 100644 --- a/src/main/java/com/example/devSns/Comment/CommentController.java +++ b/src/main/java/com/example/devSns/Comment/CommentController.java @@ -11,6 +11,7 @@ import java.util.List; @RestController +@RequestMapping("/api") public class CommentController { private final CommentService commentService; @@ -19,7 +20,7 @@ public CommentController(CommentService commentService) { this.commentService = commentService; } - @PostMapping("posts/{post_id}/comments") + @PostMapping("/posts/{post_id}/comments") public ResponseEntity postComment( @Valid @RequestBody CreateCommentDto dto, @@ -35,7 +36,7 @@ public ResponseEntity postComment( } - @PostMapping("posts/{post_id}/comments/{comment_id}") + @PostMapping("/posts/{post_id}/comments/{comment_id}") public ResponseEntity postReplyComment( @Valid @RequestBody CreateCommentDto dto, @@ -50,12 +51,12 @@ public ResponseEntity postReplyComment( } } - @GetMapping("posts/{post_id}/comments") + @GetMapping("/posts/{post_id}/comments") public ResponseEntity> getComments(@PathVariable("post_id") Long post_id) { return ResponseEntity.ok().body(commentService.getAllComments(post_id)); } - @GetMapping("posts/{post_id}/comments/{comment_id}") + @GetMapping("/posts/{post_id}/comments/{comment_id}") public ResponseEntity getCommentsByPostId( @PathVariable("post_id") Long post_id, @PathVariable("comment_id") Long comment_id) { @@ -81,7 +82,7 @@ public ResponseEntity updateComment( } - @DeleteMapping("comments/{comment_id}") + @DeleteMapping("/comments/{comment_id}") public ResponseEntity deleteComment(@PathVariable("comment_id") Long comment_id) { try{ commentService.deleteCommentById(comment_id); diff --git a/src/main/java/com/example/devSns/Login/LoginController.java b/src/main/java/com/example/devSns/Login/LoginController.java index 56f2c5f..34f47ec 100644 --- a/src/main/java/com/example/devSns/Login/LoginController.java +++ b/src/main/java/com/example/devSns/Login/LoginController.java @@ -4,10 +4,12 @@ import lombok.RequiredArgsConstructor; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; @RestController @RequiredArgsConstructor +@RequestMapping("/auth") public class LoginController { private final LoginService loginService; diff --git a/src/main/java/com/example/devSns/Member/MemberController.java b/src/main/java/com/example/devSns/Member/MemberController.java index 6ace248..1dcdec2 100644 --- a/src/main/java/com/example/devSns/Member/MemberController.java +++ b/src/main/java/com/example/devSns/Member/MemberController.java @@ -11,6 +11,7 @@ @RestController @RequiredArgsConstructor +@RequestMapping("/api") public class MemberController { private final MemberService memberService; diff --git a/src/main/java/com/example/devSns/Post/PostController.java b/src/main/java/com/example/devSns/Post/PostController.java index b2cc2b2..3ab4657 100644 --- a/src/main/java/com/example/devSns/Post/PostController.java +++ b/src/main/java/com/example/devSns/Post/PostController.java @@ -12,11 +12,12 @@ @RestController @RequiredArgsConstructor +@RequestMapping("/api") public class PostController { private final PostService postService; - @PostMapping("members/{member_id}/posts") + @PostMapping("/members/{member_id}/posts") public ResponseEntity createPost( @Valid @RequestBody AddPostRequestDto Dto, From a7392c7e47e71387830481c790fe7442591fff3d Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 21:50:45 +0900 Subject: [PATCH 17/20] =?UTF-8?q?feat:=20=ED=9A=8C=EC=9B=90=EA=B0=80?= =?UTF-8?q?=EC=9E=85=20URL=20=EC=9D=B8=ED=84=B0=EC=85=89=ED=84=B0=20?= =?UTF-8?q?=EC=9D=B8=EC=A6=9D=20=EC=A0=9C=EC=99=B8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/main/java/com/example/devSns/global/config/WebConfig.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/com/example/devSns/global/config/WebConfig.java b/src/main/java/com/example/devSns/global/config/WebConfig.java index 46d0c4f..beb0012 100644 --- a/src/main/java/com/example/devSns/global/config/WebConfig.java +++ b/src/main/java/com/example/devSns/global/config/WebConfig.java @@ -16,7 +16,7 @@ public class WebConfig implements WebMvcConfigurer { public void addInterceptors(InterceptorRegistry registry) { registry.addInterceptor(authInterceptor) .addPathPatterns("/api/**") - .excludePathPatterns("/auth/**"); + .excludePathPatterns("/auth/**","/api/members"); } } From a6ad9dc3b70079e496efd58a0f7c3e04984ce4d2 Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 21:51:07 +0900 Subject: [PATCH 18/20] =?UTF-8?q?feat:=20=EC=8B=9C=ED=81=90=EB=A6=AC?= =?UTF-8?q?=ED=8B=B0=20login=20=EB=A6=AC=EB=8B=A4=EC=9D=B4=EB=A0=89?= =?UTF-8?q?=EC=85=98=20=EB=B0=A9=EC=A7=80=EC=9A=A9=20=EC=84=A4=EC=A0=95=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 --- .../devSns/global/config/SecurityConfig.java | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 src/main/java/com/example/devSns/global/config/SecurityConfig.java diff --git a/src/main/java/com/example/devSns/global/config/SecurityConfig.java b/src/main/java/com/example/devSns/global/config/SecurityConfig.java new file mode 100644 index 0000000..6adefc9 --- /dev/null +++ b/src/main/java/com/example/devSns/global/config/SecurityConfig.java @@ -0,0 +1,23 @@ +package com.example.devSns.global.config; + +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.web.SecurityFilterChain; + +@Configuration +public class SecurityConfig { + + @Bean + public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + http + .csrf(csrf -> csrf.disable()) // CSRF 비활성화 + .authorizeHttpRequests(auth -> auth + .anyRequest().permitAll() // 모든 요청 허용 (인터셉터로 인증 처리) + ) + .httpBasic(httpBasic -> httpBasic.disable()); // 기본 로그인 비활성화 + + return http.build(); + } +} + From a84fe64a53b79d16d51425d9eb323ac3bfa27671 Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 21:51:35 +0900 Subject: [PATCH 19/20] =?UTF-8?q?fix:=20validation=20=EC=97=90=EB=9F=AC=20?= =?UTF-8?q?=EB=A9=94=EC=8B=9C=EC=A7=80=20=EA=B5=AC=EC=B2=B4=ED=99=94(?= =?UTF-8?q?=EB=94=94=EB=B2=84=EA=B9=85=EC=9A=A9=5F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Member/Dto/SignMemberRequestDto.java | 10 +++++----- .../devSns/global/GlobalExceptionHandler.java | 18 +++++++++++++++--- 2 files changed, 20 insertions(+), 8 deletions(-) diff --git a/src/main/java/com/example/devSns/Member/Dto/SignMemberRequestDto.java b/src/main/java/com/example/devSns/Member/Dto/SignMemberRequestDto.java index 9743747..dc06145 100644 --- a/src/main/java/com/example/devSns/Member/Dto/SignMemberRequestDto.java +++ b/src/main/java/com/example/devSns/Member/Dto/SignMemberRequestDto.java @@ -6,14 +6,14 @@ public record SignMemberRequestDto( - @NotBlank(message = "공백 또는 null 값은 허용하지 않습니다") + @NotBlank(message = "이름은 공백 또는 null 값은 허용하지 않습니다") String nickname, - @NotBlank(message = "공백 또는 null 값은 허용하지 않습니다") + @NotBlank(message = "이메일은 공백 또는 null 값은 허용하지 않습니다") @Email(message ="올바른 이메일 양식이 아닙니다") String email, - @NotBlank(message = "공백 또는 null 값은 허용하지 않습니다") + @NotBlank(message = "비밀번호는 공백 또는 null 값은 허용하지 않습니다") @Size(min = 8, max =16, message = "비밀번호는 8자 이상 16자 이하로 설정해주세요") @Pattern( regexp = "^(?=.*[A-Za-z])(?=.*\\d)(?=.*[@$!%*#?&])[A-Za-z\\d@$!%*#?&]+$", @@ -21,10 +21,10 @@ public record SignMemberRequestDto( ) String password, - @NotNull(message = "공백 또는 null 값은 허용하지 않습니다") + @NotNull(message = "성별은 공백 또는 null 값은 허용하지 않습니다") Gender gender, - @NotNull(message = "공백 또는 null 값은 허용하지 않습니다") + @NotNull(message = "나이는 공백 또는 null 값은 허용하지 않습니다") Integer age ) { diff --git a/src/main/java/com/example/devSns/global/GlobalExceptionHandler.java b/src/main/java/com/example/devSns/global/GlobalExceptionHandler.java index 7b0ecf4..e82f43b 100644 --- a/src/main/java/com/example/devSns/global/GlobalExceptionHandler.java +++ b/src/main/java/com/example/devSns/global/GlobalExceptionHandler.java @@ -7,16 +7,28 @@ import org.springframework.web.bind.annotation.RestControllerAdvice; import java.util.Map; +import java.util.stream.Collectors; @RestControllerAdvice public class GlobalExceptionHandler { @ExceptionHandler(MethodArgumentNotValidException.class) - public ResponseEntity handleValidation() { - String message = "공백 또는 null 값은 혀용하지 않습니다"; + public ResponseEntity> handleValidation(MethodArgumentNotValidException e) { + + // 필드별 검증 메시지를 Map으로 변환 + Map errors = e.getBindingResult() + .getFieldErrors() + .stream() + .collect(Collectors.toMap( + fe -> fe.getField(), // 필드 이름 + fe -> fe.getDefaultMessage(), // 메시지 + (existing, replacement) -> existing // 중복 필드 처리 + )); + return ResponseEntity .status(HttpStatus.BAD_REQUEST) - .body(Map.of("error", message)); + .body(errors); } + } From 65901fe42e2c1c8ee85c24d3737c80ac01435b9b Mon Sep 17 00:00:00 2001 From: tomchccom Date: Thu, 27 Nov 2025 22:15:27 +0900 Subject: [PATCH 20/20] =?UTF-8?q?feat:=20JWT=20token=20.env=20=ED=8C=8C?= =?UTF-8?q?=EC=9D=BC=EB=A1=9C=20=EC=9D=BD=EC=96=B4=EC=98=A4=EB=8F=84?= =?UTF-8?q?=EB=A1=9D=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .gitignore | 2 ++ build.gradle | 4 ++++ .../java/com/example/devSns/Jwt/JwtUtil.java | 7 ++++++- .../example/devSns/global/config/JwtConfig.java | 17 +++++++++++++++++ src/main/resources/application.properties | 4 +++- 5 files changed, 32 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/example/devSns/global/config/JwtConfig.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 42d29ca..b4ef0fc 100644 --- a/build.gradle +++ b/build.gradle @@ -34,6 +34,10 @@ dependencies { implementation 'io.jsonwebtoken:jjwt-impl:0.11.5' implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5' // JSON 파싱용 + implementation 'io.github.cdimascio:dotenv-java:3.2.0' + + + } tasks.named('test') { diff --git a/src/main/java/com/example/devSns/Jwt/JwtUtil.java b/src/main/java/com/example/devSns/Jwt/JwtUtil.java index f990ad4..991da63 100644 --- a/src/main/java/com/example/devSns/Jwt/JwtUtil.java +++ b/src/main/java/com/example/devSns/Jwt/JwtUtil.java @@ -2,6 +2,7 @@ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; +import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Date; @@ -9,9 +10,13 @@ @Component public class JwtUtil { - private final String secret = "mySecretKeyForJwtToken"; // 실제 프로젝트에서는 환경변수로 관리 + + private final String secret;// 실제 프로젝트에서는 환경변수로 관리 private final long EXPIRATION = 1000L * 60 * 60; // 1시간 + public JwtUtil(@Value("${jwt.secret}") String secret) { + this.secret = secret; + } public String createToken(Long memberId) { Date now = new Date(); Date expireTime = new Date(now.getTime() + EXPIRATION); diff --git a/src/main/java/com/example/devSns/global/config/JwtConfig.java b/src/main/java/com/example/devSns/global/config/JwtConfig.java new file mode 100644 index 0000000..5feb225 --- /dev/null +++ b/src/main/java/com/example/devSns/global/config/JwtConfig.java @@ -0,0 +1,17 @@ +package com.example.devSns.global.config; + +import com.example.devSns.Jwt.JwtUtil; + +import io.github.cdimascio.dotenv.Dotenv; +import org.springframework.context.annotation.Bean; +import org.springframework.context.annotation.Configuration; + +@Configuration +public class JwtConfig { + @Bean + public JwtUtil jwtUtil() { + Dotenv dotenv = Dotenv.load(); + String secret = dotenv.get("JWT_SECRET"); + return new JwtUtil(secret); + } +} diff --git a/src/main/resources/application.properties b/src/main/resources/application.properties index f78a5f7..d88d463 100644 --- a/src/main/resources/application.properties +++ b/src/main/resources/application.properties @@ -9,4 +9,6 @@ spring.datasource.password= # H2 Console (Optional, for development) spring.h2.console.enabled=true -spring.h2.console.path=/h2-console \ No newline at end of file +spring.h2.console.path=/h2-console +#jwt.secret=7cb89b6399df27ebc401cb0286e60871a2d5bdd7445a0072065d3d5268ffd3cecf11f602961d6673f4c1ddb2295974f33dba3a8ab7168e2d5cb44924ae3b22a8 +jwt.secret=${JWT_SECRET} \ No newline at end of file