From a24be2a7f2af21d9a4390c3abc627b7f799e4365 Mon Sep 17 00:00:00 2001
From: JO HYODONG <160843817+hyodongg@users.noreply.github.com>
Date: Sat, 6 Dec 2025 01:45:09 +0900
Subject: [PATCH 01/25] =?UTF-8?q?feat:=20comment=20=ED=8F=B4=EB=8D=94=20?=
=?UTF-8?q?=EA=B5=AC=EC=A1=B0=20=EC=83=9D=EC=84=B1?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/dataSources.xml | 7 +++++++
.idea/data_source_mapping.xml | 6 ------
.../org/sopt/comment/controller/CommentController.java | 9 +++++++++
.../org/sopt/comment/repository/CommentRepository.java | 7 +++++++
.../java/org/sopt/comment/service/CommentService.java | 4 ++++
5 files changed, 27 insertions(+), 6 deletions(-)
delete mode 100644 .idea/data_source_mapping.xml
create mode 100644 src/main/java/org/sopt/comment/controller/CommentController.java
create mode 100644 src/main/java/org/sopt/comment/repository/CommentRepository.java
create mode 100644 src/main/java/org/sopt/comment/service/CommentService.java
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
index 45ece12..a89bfa3 100644
--- a/.idea/dataSources.xml
+++ b/.idea/dataSources.xml
@@ -13,5 +13,12 @@
$ProjectFileDir$
+
+ mysql.8
+ true
+ com.mysql.cj.jdbc.Driver
+ jdbc:mysql://localhost:3306
+ $ProjectFileDir$
+
\ No newline at end of file
diff --git a/.idea/data_source_mapping.xml b/.idea/data_source_mapping.xml
deleted file mode 100644
index 2ad1ee4..0000000
--- a/.idea/data_source_mapping.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/main/java/org/sopt/comment/controller/CommentController.java b/src/main/java/org/sopt/comment/controller/CommentController.java
new file mode 100644
index 0000000..ffb2c3f
--- /dev/null
+++ b/src/main/java/org/sopt/comment/controller/CommentController.java
@@ -0,0 +1,9 @@
+package org.sopt.comment.controller;
+
+import org.springframework.web.bind.annotation.RequestMapping;
+import org.springframework.web.bind.annotation.RestController;
+
+@RestController
+@RequestMapping("/comment")
+public class CommentController {
+}
diff --git a/src/main/java/org/sopt/comment/repository/CommentRepository.java b/src/main/java/org/sopt/comment/repository/CommentRepository.java
new file mode 100644
index 0000000..e923fea
--- /dev/null
+++ b/src/main/java/org/sopt/comment/repository/CommentRepository.java
@@ -0,0 +1,7 @@
+package org.sopt.comment.repository;
+
+import org.sopt.comment.entity.Comment;
+import org.springframework.data.jpa.repository.JpaRepository;
+
+public interface CommentRepository extends JpaRepository {
+}
diff --git a/src/main/java/org/sopt/comment/service/CommentService.java b/src/main/java/org/sopt/comment/service/CommentService.java
new file mode 100644
index 0000000..bc609f1
--- /dev/null
+++ b/src/main/java/org/sopt/comment/service/CommentService.java
@@ -0,0 +1,4 @@
+package org.sopt.comment.service;
+
+public class CommentService {
+}
From 66ae7673ad86f2c6f742a544579deb9bcd9b01d9 Mon Sep 17 00:00:00 2001
From: JO HYODONG <160843817+hyodongg@users.noreply.github.com>
Date: Sat, 6 Dec 2025 01:45:32 +0900
Subject: [PATCH 02/25] =?UTF-8?q?feat:=20Comment=20=EC=97=94=ED=8B=B0?=
=?UTF-8?q?=ED=8B=B0=20=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EA=B7=B8=EC=97=90?=
=?UTF-8?q?=20=EB=94=B0=EB=A5=B8=20=EC=97=94=ED=8B=B0=ED=8B=B0=20=EC=88=98?=
=?UTF-8?q?=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/org/sopt/article/entity/Article.java | 6 ++++
.../java/org/sopt/comment/entity/Comment.java | 34 +++++++++++++++++++
.../java/org/sopt/member/entity/Member.java | 4 +++
3 files changed, 44 insertions(+)
create mode 100644 src/main/java/org/sopt/comment/entity/Comment.java
diff --git a/src/main/java/org/sopt/article/entity/Article.java b/src/main/java/org/sopt/article/entity/Article.java
index 4e6ad32..ac3a601 100644
--- a/src/main/java/org/sopt/article/entity/Article.java
+++ b/src/main/java/org/sopt/article/entity/Article.java
@@ -5,9 +5,12 @@
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
+import org.sopt.comment.entity.Comment;
import org.sopt.member.entity.Member;
import java.time.LocalDate;
+import java.util.ArrayList;
+import java.util.List;
@Entity
@Getter
@@ -35,6 +38,9 @@ public class Article {
@JoinColumn(name = "member_id")
private Member member;
+ @OneToMany(mappedBy = "article")
+ private List comments = new ArrayList<>();
+
public static Article create(String title,String content,LocalDate date,Tag tag,Member member) {
Article article = Article.builder()
.title(title)
diff --git a/src/main/java/org/sopt/comment/entity/Comment.java b/src/main/java/org/sopt/comment/entity/Comment.java
new file mode 100644
index 0000000..32c840e
--- /dev/null
+++ b/src/main/java/org/sopt/comment/entity/Comment.java
@@ -0,0 +1,34 @@
+package org.sopt.comment.entity;
+
+
+import jakarta.persistence.*;
+import lombok.AllArgsConstructor;
+import lombok.Builder;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import org.sopt.article.entity.Article;
+import org.sopt.member.entity.Member;
+
+@Entity
+@Builder
+@NoArgsConstructor
+@AllArgsConstructor
+@Getter
+public class Comment {
+
+ @Id
+ @GeneratedValue(strategy = GenerationType.IDENTITY)
+ @Column(name = "comment_id")
+ private Long id;
+
+ @Column(length = 300)
+ private String content;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "article_id")
+ private Article article;
+
+ @ManyToOne(fetch = FetchType.LAZY)
+ @JoinColumn(name = "member_id")
+ private Member member;
+}
diff --git a/src/main/java/org/sopt/member/entity/Member.java b/src/main/java/org/sopt/member/entity/Member.java
index fac7b55..e40fccd 100644
--- a/src/main/java/org/sopt/member/entity/Member.java
+++ b/src/main/java/org/sopt/member/entity/Member.java
@@ -6,6 +6,7 @@
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.sopt.article.entity.Article;
+import org.sopt.comment.entity.Comment;
import org.sopt.member.exception.MemberException;
import org.sopt.member.exception.MemberErrorCode;
@@ -49,6 +50,9 @@ public class Member {
@Column(name = "provider_id")
private String providerId; // 카카오 회원번호를 위한 필드
+ @OneToMany(mappedBy = "member")
+ private List comments = new ArrayList<>();
+
public static Member create(String name, String birth, String email, Gender gender, String password) {
validateAge(birth);
From 676ec56281ce319726ab21a1ed179e5b4b2e47ec Mon Sep 17 00:00:00 2001
From: JO HYODONG <160843817+hyodongg@users.noreply.github.com>
Date: Mon, 15 Dec 2025 03:14:21 +0900
Subject: [PATCH 03/25] =?UTF-8?q?refactor:=20=EC=96=B4=EB=85=B8=ED=85=8C?=
=?UTF-8?q?=EC=9D=B4=EC=85=98=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
src/main/java/org/sopt/article/entity/Article.java | 1 +
src/main/java/org/sopt/member/entity/Member.java | 2 ++
2 files changed, 3 insertions(+)
diff --git a/src/main/java/org/sopt/article/entity/Article.java b/src/main/java/org/sopt/article/entity/Article.java
index ac3a601..3f5629a 100644
--- a/src/main/java/org/sopt/article/entity/Article.java
+++ b/src/main/java/org/sopt/article/entity/Article.java
@@ -39,6 +39,7 @@ public class Article {
private Member member;
@OneToMany(mappedBy = "article")
+ @Builder.Default
private List comments = new ArrayList<>();
public static Article create(String title,String content,LocalDate date,Tag tag,Member member) {
diff --git a/src/main/java/org/sopt/member/entity/Member.java b/src/main/java/org/sopt/member/entity/Member.java
index e40fccd..04d54a9 100644
--- a/src/main/java/org/sopt/member/entity/Member.java
+++ b/src/main/java/org/sopt/member/entity/Member.java
@@ -39,6 +39,7 @@ public class Member {
private Gender gender;
@OneToMany(mappedBy = "member")
+ @Builder.Default
private List articles = new ArrayList<>();
@Column(nullable = true)
@@ -51,6 +52,7 @@ public class Member {
private String providerId; // 카카오 회원번호를 위한 필드
@OneToMany(mappedBy = "member")
+ @Builder.Default
private List comments = new ArrayList<>();
From ad82146de03171c36b3e8a4ff75e572638f18cfa Mon Sep 17 00:00:00 2001
From: JO HYODONG <160843817+hyodongg@users.noreply.github.com>
Date: Mon, 15 Dec 2025 03:15:13 +0900
Subject: [PATCH 04/25] =?UTF-8?q?feat:=20DTO=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../dto/request/CommentCreateRequest.java | 11 +++++++
.../dto/response/CommentListResponse.java | 16 ++++++++++
.../comment/dto/response/CommentResponse.java | 32 +++++++++++++++++++
3 files changed, 59 insertions(+)
create mode 100644 src/main/java/org/sopt/comment/dto/request/CommentCreateRequest.java
create mode 100644 src/main/java/org/sopt/comment/dto/response/CommentListResponse.java
create mode 100644 src/main/java/org/sopt/comment/dto/response/CommentResponse.java
diff --git a/src/main/java/org/sopt/comment/dto/request/CommentCreateRequest.java b/src/main/java/org/sopt/comment/dto/request/CommentCreateRequest.java
new file mode 100644
index 0000000..1fb6889
--- /dev/null
+++ b/src/main/java/org/sopt/comment/dto/request/CommentCreateRequest.java
@@ -0,0 +1,11 @@
+package org.sopt.comment.dto.request;
+
+import jakarta.validation.constraints.NotBlank;
+
+public record CommentCreateRequest(
+
+ @NotBlank(message = "내용을 빈칸으로 둘 수 없습니다.")
+ String content
+
+) {
+}
diff --git a/src/main/java/org/sopt/comment/dto/response/CommentListResponse.java b/src/main/java/org/sopt/comment/dto/response/CommentListResponse.java
new file mode 100644
index 0000000..a71a17f
--- /dev/null
+++ b/src/main/java/org/sopt/comment/dto/response/CommentListResponse.java
@@ -0,0 +1,16 @@
+package org.sopt.comment.dto.response;
+
+import org.sopt.comment.entity.Comment;
+
+import java.util.List;
+
+public record CommentListResponse(List comments) {
+
+ public static CommentListResponse from(List comments){
+ List commentResponses = comments.stream().
+ map(CommentResponse::from)
+ .toList();
+
+ return new CommentListResponse(commentResponses);
+ }
+}
diff --git a/src/main/java/org/sopt/comment/dto/response/CommentResponse.java b/src/main/java/org/sopt/comment/dto/response/CommentResponse.java
new file mode 100644
index 0000000..449614e
--- /dev/null
+++ b/src/main/java/org/sopt/comment/dto/response/CommentResponse.java
@@ -0,0 +1,32 @@
+package org.sopt.comment.dto.response;
+
+import org.sopt.comment.entity.Comment;
+
+public record CommentResponse(
+
+ // 댓글 아이디
+ Long id,
+
+ // 댓글 내용
+ String content,
+
+ // 댓글 단 아티클 제목
+ String articleTitle,
+
+ // 댓글 작성자 멤버 id
+ Long memberId,
+
+ // 댓글 작성자 멤버 이름
+ String memberName
+) {
+
+ public static CommentResponse from(Comment comment) {
+ return new CommentResponse(
+ comment.getId(),
+ comment.getContent(),
+ comment.getArticle().getTitle(),
+ comment.getMember().getId(),
+ comment.getMember().getName()
+ );
+ }
+}
From 7c235ab31e44520f50f7f1605b474a5b6f98e76c Mon Sep 17 00:00:00 2001
From: JO HYODONG <160843817+hyodongg@users.noreply.github.com>
Date: Mon, 15 Dec 2025 03:16:03 +0900
Subject: [PATCH 05/25] =?UTF-8?q?feat:=20comment=20=EC=9E=91=EC=84=B1,?=
=?UTF-8?q?=EC=A1=B0=ED=9A=8C=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../comment/controller/CommentController.java | 40 ++++++++++++++-
.../java/org/sopt/comment/entity/Comment.java | 24 ++++++---
.../comment/exception/CommentErrorCode.java | 30 +++++++++++
.../comment/repository/CommentRepository.java | 3 ++
.../sopt/comment/service/CommentService.java | 51 +++++++++++++++++++
5 files changed, 139 insertions(+), 9 deletions(-)
create mode 100644 src/main/java/org/sopt/comment/exception/CommentErrorCode.java
diff --git a/src/main/java/org/sopt/comment/controller/CommentController.java b/src/main/java/org/sopt/comment/controller/CommentController.java
index ffb2c3f..545e3ad 100644
--- a/src/main/java/org/sopt/comment/controller/CommentController.java
+++ b/src/main/java/org/sopt/comment/controller/CommentController.java
@@ -1,9 +1,45 @@
package org.sopt.comment.controller;
-import org.springframework.web.bind.annotation.RequestMapping;
-import org.springframework.web.bind.annotation.RestController;
+import io.swagger.v3.oas.annotations.Operation;
+import io.swagger.v3.oas.annotations.security.SecurityRequirement;
+import io.swagger.v3.oas.annotations.tags.Tag;
+import lombok.RequiredArgsConstructor;
+import org.sopt.comment.dto.request.CommentCreateRequest;
+import org.sopt.comment.dto.response.CommentListResponse;
+import org.sopt.comment.dto.response.CommentResponse;
+import org.sopt.comment.service.CommentService;
+import org.sopt.global.annotation.BusinessExceptionDescription;
+import org.sopt.global.annotation.LoginMemberId;
+import org.sopt.global.config.swagger.SwaggerResponseDescription;
+import org.sopt.global.response.ApiResponse;
+import org.springframework.http.HttpStatus;
+import org.springframework.http.ResponseEntity;
+import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/comment")
+@RequiredArgsConstructor
+@Tag(name = "댓글", description = "댓글 작성 / 조회 등 관리 API")
public class CommentController {
+
+ private final CommentService commentService;
+
+ @Operation(summary = "댓글 작성", description = "로그인한 회원이 아티클에 댓글을 작성합니다")
+ @PostMapping("/{articleId}")
+ @BusinessExceptionDescription(SwaggerResponseDescription.CREATE_COMMENT)
+ @SecurityRequirement(name = "JWT")
+ public ResponseEntity> createComment(@LoginMemberId Long memberId,
+ @PathVariable Long articleId,
+ CommentCreateRequest request) {
+ CommentResponse response = commentService.createComment(memberId,articleId,request);
+ return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(response));
+ }
+
+ @Operation(summary = "댓글 조회", description = "특정 아티클의 댓글을 조회합니다.")
+ @GetMapping("{articleId}")
+ @BusinessExceptionDescription(SwaggerResponseDescription.GET_COMMENT)
+ public ResponseEntity> findComment(@PathVariable Long articleId) {
+ CommentListResponse responses = commentService.findComment(articleId);
+ return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(responses));
+ }
}
diff --git a/src/main/java/org/sopt/comment/entity/Comment.java b/src/main/java/org/sopt/comment/entity/Comment.java
index 32c840e..3a59bcc 100644
--- a/src/main/java/org/sopt/comment/entity/Comment.java
+++ b/src/main/java/org/sopt/comment/entity/Comment.java
@@ -2,17 +2,14 @@
import jakarta.persistence.*;
-import lombok.AllArgsConstructor;
-import lombok.Builder;
-import lombok.Getter;
-import lombok.NoArgsConstructor;
+import lombok.*;
import org.sopt.article.entity.Article;
import org.sopt.member.entity.Member;
@Entity
-@Builder
-@NoArgsConstructor
-@AllArgsConstructor
+@Builder(access = AccessLevel.PRIVATE)
+@NoArgsConstructor(access = AccessLevel.PROTECTED)
+@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Getter
public class Comment {
@@ -31,4 +28,17 @@ public class Comment {
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;
+
+ public static Comment create(String content, Article article, Member member) {
+ Comment comment = Comment.builder()
+ .content(content)
+ .article(article)
+ .member(member)
+ .build();
+
+ article.getComments().add(comment);
+ member.getComments().add(comment);
+
+ return comment;
+ }
}
diff --git a/src/main/java/org/sopt/comment/exception/CommentErrorCode.java b/src/main/java/org/sopt/comment/exception/CommentErrorCode.java
new file mode 100644
index 0000000..c801529
--- /dev/null
+++ b/src/main/java/org/sopt/comment/exception/CommentErrorCode.java
@@ -0,0 +1,30 @@
+package org.sopt.comment.exception;
+
+import org.sopt.global.exception.errorcode.ErrorCode;
+import org.springframework.http.HttpStatus;
+
+public enum CommentErrorCode implements ErrorCode {
+ COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND,"C001","해당 댓글을 찾을 수 없습니다.");
+
+ private final HttpStatus status;
+ private final String code;
+ private final String message;
+
+ CommentErrorCode(HttpStatus status, String code, String message) {
+ this.status = status;
+ this.code = code;
+ this.message = message;
+ }
+
+ public HttpStatus getStatus() {
+ return status;
+ }
+
+ public String getCode() {
+ return code;
+ }
+
+ public String getMessage() {
+ return message;
+ }
+}
diff --git a/src/main/java/org/sopt/comment/repository/CommentRepository.java b/src/main/java/org/sopt/comment/repository/CommentRepository.java
index e923fea..25efc68 100644
--- a/src/main/java/org/sopt/comment/repository/CommentRepository.java
+++ b/src/main/java/org/sopt/comment/repository/CommentRepository.java
@@ -3,5 +3,8 @@
import org.sopt.comment.entity.Comment;
import org.springframework.data.jpa.repository.JpaRepository;
+import java.util.List;
+
public interface CommentRepository extends JpaRepository {
+ List findByArticleId(Long articleId);
}
diff --git a/src/main/java/org/sopt/comment/service/CommentService.java b/src/main/java/org/sopt/comment/service/CommentService.java
index bc609f1..abfe039 100644
--- a/src/main/java/org/sopt/comment/service/CommentService.java
+++ b/src/main/java/org/sopt/comment/service/CommentService.java
@@ -1,4 +1,55 @@
package org.sopt.comment.service;
+import lombok.RequiredArgsConstructor;
+import org.sopt.article.entity.Article;
+import org.sopt.article.exception.ArticleErrorCode;
+import org.sopt.article.exception.ArticleException;
+import org.sopt.article.repository.ArticleRepository;
+import org.sopt.comment.dto.request.CommentCreateRequest;
+import org.sopt.comment.dto.response.CommentListResponse;
+import org.sopt.comment.dto.response.CommentResponse;
+import org.sopt.comment.entity.Comment;
+import org.sopt.comment.repository.CommentRepository;
+import org.sopt.member.entity.Member;
+import org.sopt.member.exception.MemberErrorCode;
+import org.sopt.member.exception.MemberException;
+import org.sopt.member.repository.MemberRepository;
+import org.springframework.stereotype.Service;
+import org.springframework.transaction.annotation.Transactional;
+import java.util.List;
+
+@Transactional
+@RequiredArgsConstructor
+@Service
public class CommentService {
+
+ private final CommentRepository commentRepository;
+ private final MemberRepository memberRepository;
+ private final ArticleRepository articleRepository;
+
+ public CommentResponse createComment(Long memberId, Long articleId, CommentCreateRequest request) {
+
+ Member member = memberRepository.findById(memberId)
+ .orElseThrow(() -> new MemberException(MemberErrorCode.MEMBER_NOT_FOUND));
+
+ Article article = articleRepository.findById(articleId)
+ .orElseThrow(() -> new ArticleException(ArticleErrorCode.ARTICLE_NOT_FOUND));
+
+ Comment comment = Comment.create(request.content(),article,member);
+
+ Comment savedComment = commentRepository.save(comment);
+
+ return CommentResponse.from(savedComment);
+ }
+
+ @Transactional(readOnly = true)
+ public CommentListResponse findComment(Long articleId){
+
+ Article article = articleRepository.findById(articleId)
+ .orElseThrow(() -> new ArticleException(ArticleErrorCode.ARTICLE_NOT_FOUND));
+
+ List comments = commentRepository.findByArticleId(articleId);
+
+ return CommentListResponse.from(comments);
+ }
}
From 7859abe9ddcf569aaf5532e36d2637bfaea16073 Mon Sep 17 00:00:00 2001
From: JO HYODONG <160843817+hyodongg@users.noreply.github.com>
Date: Mon, 15 Dec 2025 03:16:28 +0900
Subject: [PATCH 06/25] =?UTF-8?q?refactor:=20=EC=97=90=EB=9F=AC=EC=BD=94?=
=?UTF-8?q?=EB=93=9C=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../org/sopt/global/exception/errorcode/GlobalErrorCode.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/org/sopt/global/exception/errorcode/GlobalErrorCode.java b/src/main/java/org/sopt/global/exception/errorcode/GlobalErrorCode.java
index bcd20ac..0f9c692 100644
--- a/src/main/java/org/sopt/global/exception/errorcode/GlobalErrorCode.java
+++ b/src/main/java/org/sopt/global/exception/errorcode/GlobalErrorCode.java
@@ -11,7 +11,7 @@ public enum GlobalErrorCode implements ErrorCode {
INVALID_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "J002", "유효하지 않은 액세스 토큰입니다."),
INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "J003", "유효하지 않은 리프레쉬 토큰입니다."),
EXPIRED_TOKEN(HttpStatus.UNAUTHORIZED, "J004", "만료된 토큰입니다."),
- EMPTY_TOKEN(HttpStatus.UNAUTHORIZED, "J005", "토큰이 비어있습니다."),
+ EMPTY_TOKEN(HttpStatus.UNAUTHORIZED, "J005", "토큰이 비어있습니다. 로그인이 필요합니다."),
FORBIDDEN_ACCESS(HttpStatus.FORBIDDEN,"J006","접근 권한이 없습니다.");
;
From 6c298bbfe5a68f5c7b71591565e80356a8587c12 Mon Sep 17 00:00:00 2001
From: JO HYODONG <160843817+hyodongg@users.noreply.github.com>
Date: Mon, 15 Dec 2025 03:17:34 +0900
Subject: [PATCH 07/25] =?UTF-8?q?feat:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=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
---
.../config/swagger/SwaggerResponseDescription.java | 12 ++++++++++++
1 file changed, 12 insertions(+)
diff --git a/src/main/java/org/sopt/global/config/swagger/SwaggerResponseDescription.java b/src/main/java/org/sopt/global/config/swagger/SwaggerResponseDescription.java
index 24606e6..7e6f83b 100644
--- a/src/main/java/org/sopt/global/config/swagger/SwaggerResponseDescription.java
+++ b/src/main/java/org/sopt/global/config/swagger/SwaggerResponseDescription.java
@@ -3,11 +3,13 @@
import lombok.Getter;
import org.sopt.article.exception.ArticleErrorCode;
import org.sopt.auth.exception.AuthErrorCode;
+import org.sopt.comment.exception.CommentErrorCode;
import org.sopt.global.exception.errorcode.ErrorCode;
import org.sopt.global.exception.errorcode.GlobalErrorCode;
import org.sopt.member.exception.MemberErrorCode;
import java.util.LinkedHashSet;
+import java.util.LinkedList;
import java.util.Set;
@Getter
@@ -38,6 +40,16 @@ public enum SwaggerResponseDescription {
REQUEST_LOGIN(new LinkedHashSet<>(Set.of(
AuthErrorCode.INVALID_PASSWORD
+ ))),
+
+ CREATE_COMMENT(new LinkedHashSet<>(Set.of(
+ MemberErrorCode.MEMBER_NOT_FOUND,
+ ArticleErrorCode.ARTICLE_NOT_FOUND,
+ CommentErrorCode.COMMENT_NOT_FOUND
+ ))),
+
+ GET_COMMENT(new LinkedHashSet<>(Set.of(
+ ArticleErrorCode.ARTICLE_NOT_FOUND
)));
private final Set errorCodeList;
From 3e656a5f46b22711468326ed78e03a5fc3a2945a Mon Sep 17 00:00:00 2001
From: JO HYODONG <160843817+hyodongg@users.noreply.github.com>
Date: Mon, 15 Dec 2025 03:17:46 +0900
Subject: [PATCH 08/25] =?UTF-8?q?refactor:=20security=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.idea/dataSources.xml | 12 ----------
.idea/vcs.xml | 6 -----
.../config/security/SecurityConfig.java | 2 ++
.../global/jwt/JwtAuthenticationFilter.java | 24 +++++++++++--------
4 files changed, 16 insertions(+), 28 deletions(-)
delete mode 100644 .idea/vcs.xml
diff --git a/.idea/dataSources.xml b/.idea/dataSources.xml
index a89bfa3..48290b7 100644
--- a/.idea/dataSources.xml
+++ b/.idea/dataSources.xml
@@ -1,18 +1,6 @@
-
- mysql.8
- true
- com.mysql.cj.jdbc.Driver
- jdbc:mysql://localhost:3306
-
-
-
-
-
- $ProjectFileDir$
-
mysql.8
true
diff --git a/.idea/vcs.xml b/.idea/vcs.xml
deleted file mode 100644
index 94a25f7..0000000
--- a/.idea/vcs.xml
+++ /dev/null
@@ -1,6 +0,0 @@
-
-
-
-
-
-
\ No newline at end of file
diff --git a/src/main/java/org/sopt/global/config/security/SecurityConfig.java b/src/main/java/org/sopt/global/config/security/SecurityConfig.java
index 6fb2aa0..d24cf08 100644
--- a/src/main/java/org/sopt/global/config/security/SecurityConfig.java
+++ b/src/main/java/org/sopt/global/config/security/SecurityConfig.java
@@ -4,6 +4,7 @@
import org.sopt.global.jwt.JwtAuthenticationFilter;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
+import org.springframework.http.HttpMethod;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
@@ -34,6 +35,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers(ALLOWED_PATH).permitAll()
+ .requestMatchers(HttpMethod.GET,"/comment/**").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
diff --git a/src/main/java/org/sopt/global/jwt/JwtAuthenticationFilter.java b/src/main/java/org/sopt/global/jwt/JwtAuthenticationFilter.java
index 0a8da8b..8b6e097 100644
--- a/src/main/java/org/sopt/global/jwt/JwtAuthenticationFilter.java
+++ b/src/main/java/org/sopt/global/jwt/JwtAuthenticationFilter.java
@@ -33,24 +33,28 @@ protected void doFilterInternal(
try {
String token = jwtTokenValidator.extractTokenFromHeader(request);
- if (token != null) {
- Long memberId = jwtTokenValidator.getMemberIdFromAccessToken(token);
+ if (token == null || token.isEmpty()) {
+ request.setAttribute("exception", GlobalErrorCode.EMPTY_TOKEN);
+ filterChain.doFilter(request, response);
+ return;
+ }
+
+ Long memberId = jwtTokenValidator.getMemberIdFromAccessToken(token);
- Authentication authentication = new UsernamePasswordAuthenticationToken(
- memberId,
- null,
- List.of(new SimpleGrantedAuthority("ROLE_USER"))
- );
+ Authentication authentication = new UsernamePasswordAuthenticationToken(
+ memberId,
+ null,
+ List.of(new SimpleGrantedAuthority("ROLE_USER"))
+ );
SecurityContextHolder.getContext().setAuthentication(authentication);
- }
} catch (GlobalException e) {
// JWT 관련 예외
request.setAttribute("exception", e.getErrorCode());
- throw e;
+
} catch (Exception e) {
request.setAttribute("exception", GlobalErrorCode.INVALID_TOKEN);
- throw new GlobalException(GlobalErrorCode.INVALID_TOKEN);
+
}
filterChain.doFilter(request, response);
}
From 413608f2aabb73bd7d0635b027b5c9c277d4e36d Mon Sep 17 00:00:00 2001
From: JO HYODONG <160843817+hyodongg@users.noreply.github.com>
Date: Mon, 15 Dec 2025 19:14:39 +0900
Subject: [PATCH 09/25] =?UTF-8?q?refactor:=20dto=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../article/dto/response/ArticleResponse.java | 15 +++++++++++++--
.../comment/dto/request/CommentCreateRequest.java | 2 ++
.../comment/dto/response/CommentListResponse.java | 2 ++
.../comment/dto/response/CommentResponse.java | 4 ----
4 files changed, 17 insertions(+), 6 deletions(-)
diff --git a/src/main/java/org/sopt/article/dto/response/ArticleResponse.java b/src/main/java/org/sopt/article/dto/response/ArticleResponse.java
index 8bd23be..21c7582 100644
--- a/src/main/java/org/sopt/article/dto/response/ArticleResponse.java
+++ b/src/main/java/org/sopt/article/dto/response/ArticleResponse.java
@@ -3,8 +3,10 @@
import io.swagger.v3.oas.annotations.media.Schema;
import org.sopt.article.entity.Article;
import org.sopt.article.entity.Tag;
+import org.sopt.comment.dto.response.CommentResponse;
import java.time.LocalDate;
+import java.util.List;
public record ArticleResponse(
@@ -27,9 +29,17 @@ public record ArticleResponse(
Long memberId,
@Schema(description = "작성자 이름", example = "조효동")
- String memberName
+ String memberName,
+
+ @Schema(description = "댓글 목록", example = "1등")
+ List comments
) {
public static ArticleResponse from(Article article) {
+
+ List comments = article.getComments().stream()
+ .map(CommentResponse::from)
+ .toList();
+
return new ArticleResponse(
article.getId(),
article.getTitle(),
@@ -37,7 +47,8 @@ public static ArticleResponse from(Article article) {
article.getTag(),
article.getDate(),
article.getMember().getId(),
- article.getMember().getName()
+ article.getMember().getName(),
+ comments
);
}
}
\ No newline at end of file
diff --git a/src/main/java/org/sopt/comment/dto/request/CommentCreateRequest.java b/src/main/java/org/sopt/comment/dto/request/CommentCreateRequest.java
index 1fb6889..cdc3910 100644
--- a/src/main/java/org/sopt/comment/dto/request/CommentCreateRequest.java
+++ b/src/main/java/org/sopt/comment/dto/request/CommentCreateRequest.java
@@ -1,10 +1,12 @@
package org.sopt.comment.dto.request;
import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
public record CommentCreateRequest(
@NotBlank(message = "내용을 빈칸으로 둘 수 없습니다.")
+ @Size(max = 300, message = "댓글은 300자를 초과할 수 없습니다.")
String content
) {
diff --git a/src/main/java/org/sopt/comment/dto/response/CommentListResponse.java b/src/main/java/org/sopt/comment/dto/response/CommentListResponse.java
index a71a17f..c8e164f 100644
--- a/src/main/java/org/sopt/comment/dto/response/CommentListResponse.java
+++ b/src/main/java/org/sopt/comment/dto/response/CommentListResponse.java
@@ -13,4 +13,6 @@ public static CommentListResponse from(List comments){
return new CommentListResponse(commentResponses);
}
+
+
}
diff --git a/src/main/java/org/sopt/comment/dto/response/CommentResponse.java b/src/main/java/org/sopt/comment/dto/response/CommentResponse.java
index 449614e..41f2b47 100644
--- a/src/main/java/org/sopt/comment/dto/response/CommentResponse.java
+++ b/src/main/java/org/sopt/comment/dto/response/CommentResponse.java
@@ -10,9 +10,6 @@ public record CommentResponse(
// 댓글 내용
String content,
- // 댓글 단 아티클 제목
- String articleTitle,
-
// 댓글 작성자 멤버 id
Long memberId,
@@ -24,7 +21,6 @@ public static CommentResponse from(Comment comment) {
return new CommentResponse(
comment.getId(),
comment.getContent(),
- comment.getArticle().getTitle(),
comment.getMember().getId(),
comment.getMember().getName()
);
From d5a09c0cb14affb0883e4ded63496e22dcbaa24a Mon Sep 17 00:00:00 2001
From: JO HYODONG <160843817+hyodongg@users.noreply.github.com>
Date: Mon, 15 Dec 2025 20:35:15 +0900
Subject: [PATCH 10/25] =?UTF-8?q?feat:=20comment=20=EC=88=98=EC=A0=95,=20?=
=?UTF-8?q?=EC=82=AD=EC=A0=9C=20=EA=B5=AC=ED=98=84?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../comment/controller/CommentController.java | 35 ++++++++++++--
.../dto/request/CommentUpdateRequest.java | 13 ++++++
.../java/org/sopt/comment/entity/Comment.java | 4 ++
.../comment/exception/CommentErrorCode.java | 4 +-
.../comment/exception/CommentException.java | 10 ++++
.../sopt/comment/service/CommentService.java | 46 ++++++++++++++++++-
6 files changed, 105 insertions(+), 7 deletions(-)
create mode 100644 src/main/java/org/sopt/comment/dto/request/CommentUpdateRequest.java
create mode 100644 src/main/java/org/sopt/comment/exception/CommentException.java
diff --git a/src/main/java/org/sopt/comment/controller/CommentController.java b/src/main/java/org/sopt/comment/controller/CommentController.java
index 545e3ad..180b6e0 100644
--- a/src/main/java/org/sopt/comment/controller/CommentController.java
+++ b/src/main/java/org/sopt/comment/controller/CommentController.java
@@ -3,8 +3,10 @@
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.security.SecurityRequirement;
import io.swagger.v3.oas.annotations.tags.Tag;
+import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.sopt.comment.dto.request.CommentCreateRequest;
+import org.sopt.comment.dto.request.CommentUpdateRequest;
import org.sopt.comment.dto.response.CommentListResponse;
import org.sopt.comment.dto.response.CommentResponse;
import org.sopt.comment.service.CommentService;
@@ -17,7 +19,7 @@
import org.springframework.web.bind.annotation.*;
@RestController
-@RequestMapping("/comment")
+@RequestMapping("/articles")
@RequiredArgsConstructor
@Tag(name = "댓글", description = "댓글 작성 / 조회 등 관리 API")
public class CommentController {
@@ -25,21 +27,46 @@ public class CommentController {
private final CommentService commentService;
@Operation(summary = "댓글 작성", description = "로그인한 회원이 아티클에 댓글을 작성합니다")
- @PostMapping("/{articleId}")
+ @PostMapping("/{articleId}/comments")
@BusinessExceptionDescription(SwaggerResponseDescription.CREATE_COMMENT)
@SecurityRequirement(name = "JWT")
public ResponseEntity> createComment(@LoginMemberId Long memberId,
@PathVariable Long articleId,
- CommentCreateRequest request) {
+ @Valid @RequestBody CommentCreateRequest request) {
CommentResponse response = commentService.createComment(memberId,articleId,request);
return ResponseEntity.status(HttpStatus.CREATED).body(ApiResponse.success(response));
}
@Operation(summary = "댓글 조회", description = "특정 아티클의 댓글을 조회합니다.")
- @GetMapping("{articleId}")
+ @GetMapping("/{articleId}/comments")
@BusinessExceptionDescription(SwaggerResponseDescription.GET_COMMENT)
public ResponseEntity> findComment(@PathVariable Long articleId) {
CommentListResponse responses = commentService.findComment(articleId);
return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(responses));
}
+
+ @Operation(summary = "댓글 수정", description = "특정 아티클의 댓글을 수정합니다.")
+ @PatchMapping("/{articleId}/comments/{commentId}")
+ @SecurityRequirement(name = "JWT")
+ @BusinessExceptionDescription(SwaggerResponseDescription.UPDATE_COMMENT)
+ public ResponseEntity> updateComment(@LoginMemberId Long memberId,
+ @PathVariable Long articleId,
+ @PathVariable Long commentId,
+ @Valid @RequestBody CommentUpdateRequest request
+ ){
+ CommentResponse response = commentService.updateComment(memberId,articleId,commentId,request);
+ return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(response));
+ }
+
+ @Operation(summary = "댓글 삭제", description = "특정 아티클의 댓글을 삭제합니다.")
+ @DeleteMapping("/{articleId}/comments/{commentId}")
+ @SecurityRequirement(name = "JWT")
+ @BusinessExceptionDescription(SwaggerResponseDescription.DELETE_COMMENT)
+ public ResponseEntity> deleteComment(@LoginMemberId Long memberId,
+ @PathVariable Long articleId,
+ @PathVariable Long commentId) {
+ commentService.deleteComment(memberId,articleId,commentId);
+
+ return ResponseEntity.status(HttpStatus.OK).body(ApiResponse.success(null));
+ }
}
diff --git a/src/main/java/org/sopt/comment/dto/request/CommentUpdateRequest.java b/src/main/java/org/sopt/comment/dto/request/CommentUpdateRequest.java
new file mode 100644
index 0000000..6d47c21
--- /dev/null
+++ b/src/main/java/org/sopt/comment/dto/request/CommentUpdateRequest.java
@@ -0,0 +1,13 @@
+package org.sopt.comment.dto.request;
+
+import jakarta.validation.constraints.NotBlank;
+import jakarta.validation.constraints.Size;
+
+public record CommentUpdateRequest(
+
+ @NotBlank(message = "댓글 내용은 필수입니다.")
+ @Size(max = 300, message = "댓글은 300자를 초과할 수 없습니다.")
+ String content
+
+) {
+}
diff --git a/src/main/java/org/sopt/comment/entity/Comment.java b/src/main/java/org/sopt/comment/entity/Comment.java
index 3a59bcc..c4cbdae 100644
--- a/src/main/java/org/sopt/comment/entity/Comment.java
+++ b/src/main/java/org/sopt/comment/entity/Comment.java
@@ -41,4 +41,8 @@ public static Comment create(String content, Article article, Member member) {
return comment;
}
+
+ public void updateContent(String content) {
+ this.content = content;
+ }
}
diff --git a/src/main/java/org/sopt/comment/exception/CommentErrorCode.java b/src/main/java/org/sopt/comment/exception/CommentErrorCode.java
index c801529..6c6a160 100644
--- a/src/main/java/org/sopt/comment/exception/CommentErrorCode.java
+++ b/src/main/java/org/sopt/comment/exception/CommentErrorCode.java
@@ -4,7 +4,9 @@
import org.springframework.http.HttpStatus;
public enum CommentErrorCode implements ErrorCode {
- COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND,"C001","해당 댓글을 찾을 수 없습니다.");
+ COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND,"C001","해당 댓글을 찾을 수 없습니다."),
+ COMMENT_NOT_MATCH_ARTICLE(HttpStatus.NOT_FOUND,"C002","해당 아티클의 댓글이 아닙니다."),
+ NOT_COMMENT_OWNER(HttpStatus.NOT_FOUND,"C003","해당 아티클의 작성자가 아닙니다.");
private final HttpStatus status;
private final String code;
diff --git a/src/main/java/org/sopt/comment/exception/CommentException.java b/src/main/java/org/sopt/comment/exception/CommentException.java
new file mode 100644
index 0000000..988d1f4
--- /dev/null
+++ b/src/main/java/org/sopt/comment/exception/CommentException.java
@@ -0,0 +1,10 @@
+package org.sopt.comment.exception;
+
+import org.sopt.global.exception.BusinessException;
+import org.sopt.global.exception.errorcode.ErrorCode;
+
+public class CommentException extends BusinessException {
+ public CommentException(ErrorCode errorCode) {
+ super(errorCode);
+ }
+}
diff --git a/src/main/java/org/sopt/comment/service/CommentService.java b/src/main/java/org/sopt/comment/service/CommentService.java
index abfe039..5b148e3 100644
--- a/src/main/java/org/sopt/comment/service/CommentService.java
+++ b/src/main/java/org/sopt/comment/service/CommentService.java
@@ -6,9 +6,12 @@
import org.sopt.article.exception.ArticleException;
import org.sopt.article.repository.ArticleRepository;
import org.sopt.comment.dto.request.CommentCreateRequest;
+import org.sopt.comment.dto.request.CommentUpdateRequest;
import org.sopt.comment.dto.response.CommentListResponse;
import org.sopt.comment.dto.response.CommentResponse;
import org.sopt.comment.entity.Comment;
+import org.sopt.comment.exception.CommentErrorCode;
+import org.sopt.comment.exception.CommentException;
import org.sopt.comment.repository.CommentRepository;
import org.sopt.member.entity.Member;
import org.sopt.member.exception.MemberErrorCode;
@@ -35,7 +38,7 @@ public CommentResponse createComment(Long memberId, Long articleId, CommentCreat
Article article = articleRepository.findById(articleId)
.orElseThrow(() -> new ArticleException(ArticleErrorCode.ARTICLE_NOT_FOUND));
- Comment comment = Comment.create(request.content(),article,member);
+ Comment comment = Comment.create(request.content(), article, member);
Comment savedComment = commentRepository.save(comment);
@@ -43,7 +46,7 @@ public CommentResponse createComment(Long memberId, Long articleId, CommentCreat
}
@Transactional(readOnly = true)
- public CommentListResponse findComment(Long articleId){
+ public CommentListResponse findComment(Long articleId) {
Article article = articleRepository.findById(articleId)
.orElseThrow(() -> new ArticleException(ArticleErrorCode.ARTICLE_NOT_FOUND));
@@ -52,4 +55,43 @@ public CommentListResponse findComment(Long articleId){
return CommentListResponse.from(comments);
}
+
+ public CommentResponse updateComment(Long memberId, Long articleId, Long commentId, CommentUpdateRequest request) {
+
+ Comment comment = commentRepository.findById(commentId)
+ .orElseThrow(() -> new CommentException(CommentErrorCode.COMMENT_NOT_FOUND));
+
+ // 아티클 일치 확인
+ if (!comment.getArticle().getId().equals(articleId)) {
+ throw new CommentException(CommentErrorCode.COMMENT_NOT_MATCH_ARTICLE);
+ }
+
+ // 작성자 확인
+ if (!comment.getMember().getId().equals(memberId)) {
+ throw new CommentException(CommentErrorCode.NOT_COMMENT_OWNER);
+ }
+
+ comment.updateContent(request.content());
+
+ return CommentResponse.from(comment);
+
+ }
+
+ public void deleteComment(Long memberId, Long articleId, Long commentId) {
+
+ Comment comment = commentRepository.findById(commentId)
+ .orElseThrow(() -> new CommentException(CommentErrorCode.COMMENT_NOT_FOUND));
+
+ // 게시글 일치 확인
+ if (!comment.getArticle().getId().equals(articleId)) {
+ throw new CommentException(CommentErrorCode.COMMENT_NOT_MATCH_ARTICLE);
+ }
+
+ // 작성자 확인
+ if (!comment.getMember().getId().equals(memberId)) {
+ throw new CommentException(CommentErrorCode.NOT_COMMENT_OWNER);
+ }
+
+ commentRepository.delete(comment);
+ }
}
From 17b9f84b309cb296068d2e4d0ea8a5ae65deb26e Mon Sep 17 00:00:00 2001
From: JO HYODONG <160843817+hyodongg@users.noreply.github.com>
Date: Mon, 15 Dec 2025 20:35:23 +0900
Subject: [PATCH 11/25] =?UTF-8?q?feat:=20=EC=8A=A4=EC=9B=A8=EA=B1=B0=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
---
.../config/swagger/SwaggerResponseDescription.java | 13 +++++++++++++
1 file changed, 13 insertions(+)
diff --git a/src/main/java/org/sopt/global/config/swagger/SwaggerResponseDescription.java b/src/main/java/org/sopt/global/config/swagger/SwaggerResponseDescription.java
index 7e6f83b..b951ab6 100644
--- a/src/main/java/org/sopt/global/config/swagger/SwaggerResponseDescription.java
+++ b/src/main/java/org/sopt/global/config/swagger/SwaggerResponseDescription.java
@@ -3,6 +3,7 @@
import lombok.Getter;
import org.sopt.article.exception.ArticleErrorCode;
import org.sopt.auth.exception.AuthErrorCode;
+import org.sopt.comment.entity.Comment;
import org.sopt.comment.exception.CommentErrorCode;
import org.sopt.global.exception.errorcode.ErrorCode;
import org.sopt.global.exception.errorcode.GlobalErrorCode;
@@ -50,6 +51,18 @@ public enum SwaggerResponseDescription {
GET_COMMENT(new LinkedHashSet<>(Set.of(
ArticleErrorCode.ARTICLE_NOT_FOUND
+ ))),
+
+ UPDATE_COMMENT(new LinkedHashSet<>(Set.of(
+ CommentErrorCode.COMMENT_NOT_FOUND,
+ CommentErrorCode.COMMENT_NOT_MATCH_ARTICLE,
+ CommentErrorCode.NOT_COMMENT_OWNER
+ ))),
+
+ DELETE_COMMENT(new LinkedHashSet<>(Set.of(
+ CommentErrorCode.COMMENT_NOT_FOUND,
+ CommentErrorCode.COMMENT_NOT_MATCH_ARTICLE,
+ CommentErrorCode.NOT_COMMENT_OWNER
)));
private final Set errorCodeList;
From da5c76f252dee9f17dada33feaf3c5034fd7faa5 Mon Sep 17 00:00:00 2001
From: JO HYODONG <160843817+hyodongg@users.noreply.github.com>
Date: Mon, 15 Dec 2025 20:35:32 +0900
Subject: [PATCH 12/25] =?UTF-8?q?refactor:=20security=20=EC=88=98=EC=A0=95?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../java/org/sopt/global/config/security/SecurityConfig.java | 2 +-
1 file changed, 1 insertion(+), 1 deletion(-)
diff --git a/src/main/java/org/sopt/global/config/security/SecurityConfig.java b/src/main/java/org/sopt/global/config/security/SecurityConfig.java
index d24cf08..a13b57f 100644
--- a/src/main/java/org/sopt/global/config/security/SecurityConfig.java
+++ b/src/main/java/org/sopt/global/config/security/SecurityConfig.java
@@ -35,7 +35,7 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authorizeHttpRequests(auth -> auth
.requestMatchers(ALLOWED_PATH).permitAll()
- .requestMatchers(HttpMethod.GET,"/comment/**").permitAll()
+ .requestMatchers(HttpMethod.GET,"/articles/{articleId}/comments").permitAll()
.anyRequest().authenticated()
)
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
From 0c8fc93b6b4868f22a234f137128c245533f8776 Mon Sep 17 00:00:00 2001
From: JO HYODONG <160843817+hyodongg@users.noreply.github.com>
Date: Mon, 15 Dec 2025 20:50:31 +0900
Subject: [PATCH 13/25] =?UTF-8?q?feat:=20validation=20=EC=9D=98=EC=A1=B4?=
=?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
build.gradle | 3 +++
1 file changed, 3 insertions(+)
diff --git a/build.gradle b/build.gradle
index 97804b1..170704e 100644
--- a/build.gradle
+++ b/build.gradle
@@ -32,6 +32,9 @@ dependencies {
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'
+ // validation 의존성
+ implementation 'org.springframework.boot:spring-boot-starter-validation'
+
// mysql
runtimeOnly 'com.mysql:mysql-connector-j'
From fa33fc128caffdfad816cad1f6431aa7d4213aba Mon Sep 17 00:00:00 2001
From: JO HYODONG <160843817+hyodongg@users.noreply.github.com>
Date: Tue, 16 Dec 2025 04:14:25 +0900
Subject: [PATCH 14/25] =?UTF-8?q?feat:=20Redis=20=EC=9D=98=EC=A1=B4?=
=?UTF-8?q?=EC=84=B1=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
build.gradle | 4 ++++
1 file changed, 4 insertions(+)
diff --git a/build.gradle b/build.gradle
index 170704e..91f619d 100644
--- a/build.gradle
+++ b/build.gradle
@@ -40,4 +40,8 @@ dependencies {
// jwt 의존성
implementation 'com.auth0:java-jwt:4.4.0'
+
+ // Redis 관련 의존성
+ implementation 'org.springframework.boot:spring-boot-starter-data-redis'
+ implementation 'org.springframework.boot:spring-boot-starter-cache'
}
\ No newline at end of file
From aa79eed1eaaa37c2f85ac13a62d2a3d7de0d12a8 Mon Sep 17 00:00:00 2001
From: JO HYODONG <160843817+hyodongg@users.noreply.github.com>
Date: Tue, 16 Dec 2025 04:15:10 +0900
Subject: [PATCH 15/25] =?UTF-8?q?feat:=20RedisConfig=20=EC=B6=94=EA=B0=80?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
.../config/redis/CacheTtlProperties.java | 14 +++
.../sopt/global/config/redis/RedisConfig.java | 93 +++++++++++++++++++
src/main/resources/application.yml | 23 +++++
3 files changed, 130 insertions(+)
create mode 100644 src/main/java/org/sopt/global/config/redis/CacheTtlProperties.java
create mode 100644 src/main/java/org/sopt/global/config/redis/RedisConfig.java
diff --git a/src/main/java/org/sopt/global/config/redis/CacheTtlProperties.java b/src/main/java/org/sopt/global/config/redis/CacheTtlProperties.java
new file mode 100644
index 0000000..3f859a8
--- /dev/null
+++ b/src/main/java/org/sopt/global/config/redis/CacheTtlProperties.java
@@ -0,0 +1,14 @@
+package org.sopt.global.config.redis;
+
+import org.springframework.boot.context.properties.ConfigurationProperties;
+
+@ConfigurationProperties(prefix = "cache.ttl")
+public record CacheTtlProperties(
+
+ long defaultTtl,
+
+ long articleDetail,
+
+ long articleList
+) {
+}
\ No newline at end of file
diff --git a/src/main/java/org/sopt/global/config/redis/RedisConfig.java b/src/main/java/org/sopt/global/config/redis/RedisConfig.java
new file mode 100644
index 0000000..a954e1c
--- /dev/null
+++ b/src/main/java/org/sopt/global/config/redis/RedisConfig.java
@@ -0,0 +1,93 @@
+package org.sopt.global.config.redis;
+
+import com.fasterxml.jackson.databind.DeserializationFeature;
+import com.fasterxml.jackson.databind.ObjectMapper;
+import com.fasterxml.jackson.databind.SerializationFeature;
+import com.fasterxml.jackson.databind.jsontype.BasicPolymorphicTypeValidator;
+import com.fasterxml.jackson.databind.jsontype.PolymorphicTypeValidator;
+import com.fasterxml.jackson.datatype.jsr310.JavaTimeModule;
+import lombok.RequiredArgsConstructor;
+import org.springframework.beans.factory.annotation.Qualifier;
+import org.springframework.cache.CacheManager;
+import org.springframework.cache.annotation.EnableCaching;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.redis.cache.RedisCacheConfiguration;
+import org.springframework.data.redis.cache.RedisCacheManager;
+import org.springframework.data.redis.connection.RedisConnectionFactory;
+import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
+import org.springframework.data.redis.serializer.RedisSerializationContext;
+import org.springframework.data.redis.serializer.RedisSerializer;
+import org.springframework.data.redis.serializer.StringRedisSerializer;
+
+import java.time.Duration;
+import java.util.HashMap;
+import java.util.Map;
+
+@Configuration
+@EnableCaching
+@RequiredArgsConstructor
+public class RedisConfig {
+
+ private final CacheTtlProperties cacheTtlProperties;
+
+ @Bean
+ public ObjectMapper redisObjectMapper() {
+ ObjectMapper mapper = new ObjectMapper();
+ mapper.findAndRegisterModules();
+
+ BasicPolymorphicTypeValidator ptv = BasicPolymorphicTypeValidator.builder()
+ .allowIfBaseType(Object.class)
+ .build();
+ mapper.activateDefaultTyping(ptv, ObjectMapper.DefaultTyping.NON_FINAL);
+
+ mapper.disable(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS);
+ mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
+
+ return mapper;
+ }
+
+ @Bean
+ public RedisSerializer