diff --git a/.idea/misc.xml b/.idea/misc.xml index 43b1638..f6589e3 100644 --- a/.idea/misc.xml +++ b/.idea/misc.xml @@ -1,6 +1,9 @@ + + + diff --git a/src/main/java/org/sopt/common/ErrorResponse.java b/src/main/java/org/sopt/common/ErrorResponse.java index 9b20ced..7a2a4dc 100644 --- a/src/main/java/org/sopt/common/ErrorResponse.java +++ b/src/main/java/org/sopt/common/ErrorResponse.java @@ -1,11 +1,25 @@ package org.sopt.common; +import org.sopt.exception.ErrorStatus; + public record ErrorResponse( String status, String code, String message ) { - public static ErrorResponse of(String code, String message) { - return new ErrorResponse("FAILURE", code, message); + public static ErrorResponse of(ErrorStatus errorStatus) { + return new ErrorResponse( + "FAILURE", + errorStatus.getCode(), + errorStatus.getMessage() + ); + } + + public static ErrorResponse of(ErrorStatus errorStatus, String message) { + return new ErrorResponse( + "FAILURE", + errorStatus.getCode(), + message + ); } } diff --git a/src/main/java/org/sopt/common/GlobalExceptionHandler.java b/src/main/java/org/sopt/common/GlobalExceptionHandler.java index 3920916..6b08316 100644 --- a/src/main/java/org/sopt/common/GlobalExceptionHandler.java +++ b/src/main/java/org/sopt/common/GlobalExceptionHandler.java @@ -1,6 +1,6 @@ package org.sopt.common; -import org.sopt.dto.response.ApiCode; +import org.sopt.exception.ErrorStatus; import org.springframework.http.ResponseEntity; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; @@ -10,22 +10,28 @@ public class GlobalExceptionHandler { @ExceptionHandler(IllegalArgumentException.class) public ResponseEntity handleIllegalArgument(IllegalArgumentException e) { + ErrorStatus errorStatus = ErrorStatus.NOT_FOUND_MEMBER_EXCEPTION; + return ResponseEntity - .status(ApiCode.FAILURE_MEMBER_NOT_FOUND.status()) - .body(ErrorResponse.of(ApiCode.FAILURE_MEMBER_NOT_FOUND.code(), e.getMessage())); + .status(errorStatus.getHttpStatus()) + .body(ErrorResponse.of(errorStatus, e.getMessage())); } @ExceptionHandler(IllegalStateException.class) public ResponseEntity handleIllegalState(IllegalStateException e) { + ErrorStatus errorStatus = ErrorStatus.VALIDATION_REQUEST_MISSING_EXCEPTION; + return ResponseEntity - .badRequest() - .body(ErrorResponse.of(ApiCode.FAILURE_INVALID_REQUEST.code(), e.getMessage())); + .status(errorStatus.getHttpStatus()) + .body(ErrorResponse.of(errorStatus, e.getMessage())); } @ExceptionHandler(Exception.class) public ResponseEntity handleException(Exception e) { + ErrorStatus errorStatus = ErrorStatus.INTERNAL_SERVER_ERROR; + return ResponseEntity - .internalServerError() - .body(ErrorResponse.of("f5000", e.getMessage())); + .status(errorStatus.getHttpStatus()) + .body(ErrorResponse.of(errorStatus, e.getMessage())); } } diff --git a/src/main/java/org/sopt/common/SuccessResponse.java b/src/main/java/org/sopt/common/SuccessResponse.java index 8e8b886..48cab0d 100644 --- a/src/main/java/org/sopt/common/SuccessResponse.java +++ b/src/main/java/org/sopt/common/SuccessResponse.java @@ -1,6 +1,7 @@ package org.sopt.common; -import org.sopt.dto.response.ApiCode; +import org.sopt.common.code.BaseCode; +import org.sopt.controller.common.SuccessStatus; public record SuccessResponse( String status, @@ -8,16 +9,21 @@ public record SuccessResponse( String message, T data ) { - public static SuccessResponse of(ApiCode apiCode, T data) { + public static SuccessResponse of(SuccessStatus successStatus, T data) { return new SuccessResponse<>( "SUCCESS", - apiCode.code(), - apiCode.message(), + successStatus.getCode(), + successStatus.getMessage(), data ); } - public static SuccessResponse of(String message, T data) { - return new SuccessResponse<>("SUCCESS", "s2000", message, data); + public static SuccessResponse of(BaseCode code, T data) { + return new SuccessResponse<>( + "SUCCESS", + code.getCode(), + code.getMessage(), + data + ); } } diff --git a/src/main/java/org/sopt/common/code/BaseCode.java b/src/main/java/org/sopt/common/code/BaseCode.java new file mode 100644 index 0000000..d774621 --- /dev/null +++ b/src/main/java/org/sopt/common/code/BaseCode.java @@ -0,0 +1,9 @@ +package org.sopt.common.code; + +import org.springframework.http.HttpStatus; + +public interface BaseCode { + HttpStatus getHttpStatus(); + String getCode(); + String getMessage(); +} diff --git a/src/main/java/org/sopt/common/ArticleValidator.java b/src/main/java/org/sopt/common/validator/ArticleValidator.java similarity index 94% rename from src/main/java/org/sopt/common/ArticleValidator.java rename to src/main/java/org/sopt/common/validator/ArticleValidator.java index a9981c0..2f2ff80 100644 --- a/src/main/java/org/sopt/common/ArticleValidator.java +++ b/src/main/java/org/sopt/common/validator/ArticleValidator.java @@ -1,4 +1,4 @@ -package org.sopt.common; +package org.sopt.common.validator; import org.sopt.domain.ArticleTag; diff --git a/src/main/java/org/sopt/common/validator/CommentValidator.java b/src/main/java/org/sopt/common/validator/CommentValidator.java new file mode 100644 index 0000000..d32d56f --- /dev/null +++ b/src/main/java/org/sopt/common/validator/CommentValidator.java @@ -0,0 +1,20 @@ +package org.sopt.common.validator; + +import org.sopt.domain.Comment; +import org.springframework.stereotype.Component; + +@Component +public class CommentValidator { + + public void validateLength(String content) { + if (content == null || content.length() > 300) { + throw new IllegalArgumentException("댓글은 300자 이내여야 합니다."); + } + } + + public void validateWriter(Comment comment, Long requestMemberId) { + if (!comment.getMember().getId().equals(requestMemberId)) { + throw new IllegalArgumentException("작성자만 댓글을 수정/삭제할 수 있습니다."); + } + } +} diff --git a/src/main/java/org/sopt/common/MemberValidator.java b/src/main/java/org/sopt/common/validator/MemberValidator.java similarity index 96% rename from src/main/java/org/sopt/common/MemberValidator.java rename to src/main/java/org/sopt/common/validator/MemberValidator.java index 394da43..998a527 100644 --- a/src/main/java/org/sopt/common/MemberValidator.java +++ b/src/main/java/org/sopt/common/validator/MemberValidator.java @@ -1,4 +1,4 @@ -package org.sopt.common; +package org.sopt.common.validator; import java.time.LocalDate; import java.time.Period; diff --git a/src/main/java/org/sopt/controller/ArticleController.java b/src/main/java/org/sopt/controller/ArticleController.java index 8416a35..008c9c1 100644 --- a/src/main/java/org/sopt/controller/ArticleController.java +++ b/src/main/java/org/sopt/controller/ArticleController.java @@ -1,11 +1,11 @@ package org.sopt.controller; import org.sopt.common.SuccessResponse; +import org.sopt.controller.common.SuccessStatus; import org.sopt.dto.request.ArticleCreateRequest; import org.sopt.dto.response.ArticleCreateResponse; import org.sopt.dto.response.ArticleListResponse; import org.sopt.dto.response.ArticleResponse; -import org.sopt.dto.response.ApiCode; import org.sopt.service.ArticleService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; @@ -28,8 +28,8 @@ public ResponseEntity> createArticle( ) { ArticleCreateResponse response = articleService.createArticle(request); return ResponseEntity - .status(ApiCode.SUCCESS_CREATE_ARTICLE.status()) - .body(SuccessResponse.of(ApiCode.SUCCESS_CREATE_ARTICLE, response)); + .status(SuccessStatus.SUCCESS_CREATE_ARTICLE.getHttpStatus()) + .body(SuccessResponse.of(SuccessStatus.SUCCESS_CREATE_ARTICLE, response)); } @GetMapping("/{id}") @@ -38,13 +38,15 @@ public ResponseEntity> getArticle( ) { ArticleResponse response = articleService.getArticle(id); return ResponseEntity - .ok(SuccessResponse.of(ApiCode.SUCCESS_FIND_ARTICLE, response)); + .status(SuccessStatus.SUCCESS_FIND_ARTICLE.getHttpStatus()) + .body(SuccessResponse.of(SuccessStatus.SUCCESS_FIND_ARTICLE, response)); } @GetMapping public ResponseEntity> getAllArticles() { ArticleListResponse response = articleService.getAllArticles(); return ResponseEntity - .ok(SuccessResponse.of(ApiCode.SUCCESS_FIND_ARTICLE, response)); + .status(SuccessStatus.SUCCESS_FIND_ALL_ARTICLES.getHttpStatus()) + .body(SuccessResponse.of(SuccessStatus.SUCCESS_FIND_ALL_ARTICLES, response)); } } diff --git a/src/main/java/org/sopt/controller/CommentController.java b/src/main/java/org/sopt/controller/CommentController.java new file mode 100644 index 0000000..991085b --- /dev/null +++ b/src/main/java/org/sopt/controller/CommentController.java @@ -0,0 +1,66 @@ +package org.sopt.controller; + +import org.sopt.common.SuccessResponse; +import org.sopt.controller.common.SuccessStatus; +import org.sopt.dto.request.CommentRequest; +import org.sopt.dto.response.CommentResponse; +import org.sopt.service.CommentService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/v1") +public class CommentController { + + private final CommentService commentService; + + @Autowired + public CommentController(CommentService commentService) { + this.commentService = commentService; + } + + @PostMapping("/articles/{articleId}/comments") + public ResponseEntity> createComment( + @PathVariable Long articleId, + @RequestBody CommentRequest request + ) { + Long commentId = commentService.createComment(articleId, request); + return ResponseEntity + .status(SuccessStatus.SUCCESS_CREATE_COMMENT.getHttpStatus()) + .body(SuccessResponse.of(SuccessStatus.SUCCESS_CREATE_COMMENT, commentId)); + } + + @PatchMapping("/comments/{commentId}") + public ResponseEntity> updateComment( + @PathVariable Long commentId, + @RequestBody CommentRequest request + ) { + commentService.updateComment(commentId, request); + return ResponseEntity + .status(SuccessStatus.SUCCESS_UPDATE_COMMENT.getHttpStatus()) + .body(SuccessResponse.of(SuccessStatus.SUCCESS_UPDATE_COMMENT, commentId)); + } + + @DeleteMapping("/comments/{commentId}") + public ResponseEntity> deleteComment( + @PathVariable Long commentId + ) { + commentService.deleteComment(commentId); + return ResponseEntity + .status(SuccessStatus.SUCCESS_DELETE_COMMENT.getHttpStatus()) + .body(SuccessResponse.of(SuccessStatus.SUCCESS_DELETE_COMMENT, commentId)); + } + + @GetMapping("/articles/{articleId}/comments") + public ResponseEntity>> getComments( + @PathVariable Long articleId + ) { + List response = commentService.getComments(articleId); + return ResponseEntity + .status(SuccessStatus.SUCCESS_FIND_COMMENT.getHttpStatus()) + .body(SuccessResponse.of(SuccessStatus.SUCCESS_FIND_COMMENT, response)); + } +} diff --git a/src/main/java/org/sopt/controller/MemberController.java b/src/main/java/org/sopt/controller/MemberController.java index e08611d..2d58fde 100644 --- a/src/main/java/org/sopt/controller/MemberController.java +++ b/src/main/java/org/sopt/controller/MemberController.java @@ -1,8 +1,8 @@ package org.sopt.controller; import org.sopt.common.SuccessResponse; +import org.sopt.controller.common.SuccessStatus; import org.sopt.dto.request.MemberCreateRequest; -import org.sopt.dto.response.ApiCode; import org.sopt.dto.response.MemberCreateResponse; import org.sopt.dto.response.MemberListResponse; import org.sopt.dto.response.MemberResponse; @@ -22,42 +22,41 @@ public MemberController(MemberService memberService) { this.memberService = memberService; } - @PostMapping public ResponseEntity> createMember( @RequestBody MemberCreateRequest request ) { MemberCreateResponse response = memberService.join(request); return ResponseEntity - .status(ApiCode.SUCCESS_CREATE_MEMBER.status()) - .body(SuccessResponse.of(ApiCode.SUCCESS_CREATE_MEMBER, response)); + .status(SuccessStatus.SUCCESS_CREATE_MEMBER.getHttpStatus()) + .body(SuccessResponse.of(SuccessStatus.SUCCESS_CREATE_MEMBER, response)); } - @DeleteMapping("/{id}") public ResponseEntity> deleteMember( @PathVariable Long id ) { memberService.deleteMember(id); return ResponseEntity - .ok(SuccessResponse.of(ApiCode.SUCCESS_DELETE_MEMBER, null)); + .status(SuccessStatus.SUCCESS_DELETE_MEMBER.getHttpStatus()) + .body(SuccessResponse.of(SuccessStatus.SUCCESS_DELETE_MEMBER, null)); } - @GetMapping("/{id}") public ResponseEntity> getMember( @PathVariable Long id ) { MemberResponse response = memberService.findOne(id); return ResponseEntity - .ok(SuccessResponse.of(ApiCode.SUCCESS_FIND_MEMBER, response)); + .status(SuccessStatus.SUCCESS_FIND_MEMBER.getHttpStatus()) + .body(SuccessResponse.of(SuccessStatus.SUCCESS_FIND_MEMBER, response)); } - @GetMapping public ResponseEntity> getAllMembers() { MemberListResponse response = memberService.findAllMembers(); return ResponseEntity - .ok(SuccessResponse.of(ApiCode.SUCCESS_FIND_MEMBER, response)); + .status(SuccessStatus.SUCCESS_FIND_MEMBER.getHttpStatus()) + .body(SuccessResponse.of(SuccessStatus.SUCCESS_FIND_MEMBER, response)); } } diff --git a/src/main/java/org/sopt/controller/common/SuccessStatus.java b/src/main/java/org/sopt/controller/common/SuccessStatus.java new file mode 100644 index 0000000..59324a2 --- /dev/null +++ b/src/main/java/org/sopt/controller/common/SuccessStatus.java @@ -0,0 +1,30 @@ +package org.sopt.controller.common; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.common.code.BaseCode; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public enum SuccessStatus implements BaseCode { + // 200 OK + SUCCESS_FIND_MEMBER(HttpStatus.OK, "s2002", "회원 조회를 성공하였습니다."), + SUCCESS_FIND_ARTICLE(HttpStatus.OK, "s3002", "게시글 조회를 성공하였습니다."), + SUCCESS_FIND_ALL_ARTICLES(HttpStatus.OK, "s3003", "전체 게시글 조회를 성공하였습니다."), + SUCCESS_UPDATE_COMMENT(HttpStatus.OK, "s2000", "댓글 수정 성공"), + SUCCESS_DELETE_COMMENT(HttpStatus.OK, "s2000", "댓글 삭제 성공"), + SUCCESS_FIND_COMMENT(HttpStatus.OK, "s2000", "댓글 조회 성공"), + SUCCESS_DELETE_MEMBER(HttpStatus.OK, "s2001", "회원이 성공적으로 삭제되었습니다."), + + // 201 CREATED + SUCCESS_CREATE_MEMBER(HttpStatus.CREATED, "s2010", "회원이 성공적으로 생성되었습니다."), + SUCCESS_CREATE_ARTICLE(HttpStatus.CREATED, "s3010", "게시글이 성공적으로 생성되었습니다."), + SUCCESS_CREATE_COMMENT(HttpStatus.CREATED, "s2010", "댓글 생성 성공"), + ; + + private final HttpStatus httpStatus; + private final String code; + private final String message; +} diff --git a/src/main/java/org/sopt/domain/Article.java b/src/main/java/org/sopt/domain/Article.java index 4d785c6..6e62ed1 100644 --- a/src/main/java/org/sopt/domain/Article.java +++ b/src/main/java/org/sopt/domain/Article.java @@ -6,6 +6,8 @@ import org.springframework.data.jpa.domain.support.AuditingEntityListener; import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.List; @Entity @EntityListeners(AuditingEntityListener.class) @@ -36,6 +38,9 @@ public class Article { @CreatedDate private LocalDateTime createdAt; + @OneToMany(mappedBy = "article", cascade = CascadeType.REMOVE) + private List comments = new ArrayList<>(); + public Article(Member member, String title, String content, ArticleTag tag) { this.member = member; this.title = title; diff --git a/src/main/java/org/sopt/domain/Comment.java b/src/main/java/org/sopt/domain/Comment.java new file mode 100644 index 0000000..61bb015 --- /dev/null +++ b/src/main/java/org/sopt/domain/Comment.java @@ -0,0 +1,39 @@ +package org.sopt.domain; + +import jakarta.persistence.*; +import lombok.AccessLevel; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Comment { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @Column(nullable = false, 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; + + @Builder + public Comment(String content, Article article, Member member) { + this.content = content; + this.article = article; + this.member = member; + } + + public void updateContent(String content) { + this.content = content; + } +} diff --git a/src/main/java/org/sopt/dto/request/CommentRequest.java b/src/main/java/org/sopt/dto/request/CommentRequest.java new file mode 100644 index 0000000..99af52a --- /dev/null +++ b/src/main/java/org/sopt/dto/request/CommentRequest.java @@ -0,0 +1,7 @@ +package org.sopt.dto.request; + +public record CommentRequest( + Long memberId, + String content +) { +} diff --git a/src/main/java/org/sopt/dto/response/ApiCode.java b/src/main/java/org/sopt/dto/response/ApiCode.java deleted file mode 100644 index 5557f19..0000000 --- a/src/main/java/org/sopt/dto/response/ApiCode.java +++ /dev/null @@ -1,31 +0,0 @@ -package org.sopt.dto.response; - -public enum ApiCode { - // 회원 관련 - SUCCESS_CREATE_MEMBER(201, "s2010", "회원이 성공적으로 생성되었습니다."), - SUCCESS_DELETE_MEMBER(200, "s2001", "회원이 성공적으로 삭제되었습니다."), - SUCCESS_FIND_MEMBER(200, "s2002", "회원 조회를 성공하였습니다."), - FAILURE_MEMBER_NOT_FOUND(404, "f4041", "존재하지 않는 회원입니다."), - FAILURE_INVALID_REQUEST(400, "f4001", "잘못된 요청입니다."), - - // 게시글 관련 - SUCCESS_CREATE_ARTICLE(201, "s3010", "게시글이 성공적으로 생성되었습니다."), - SUCCESS_FIND_ARTICLE(200, "s3002", "게시글 조회를 성공하였습니다."), - SUCCESS_FIND_ALL_ARTICLES(200, "s3003", "전체 게시글 조회를 성공하였습니다."), - FAILURE_ARTICLE_NOT_FOUND(404, "f4042", "존재하지 않는 게시글입니다."), - FAILURE_INVALID_ARTICLE_REQUEST(400, "f4002", "잘못된 게시글 요청입니다."); - - private final int status; - private final String code; - private final String message; - - ApiCode(int status, String code, String message) { - this.status = status; - this.code = code; - this.message = message; - } - - public int status() { return status; } - public String code() { return code; } - public String message() { return message; } -} diff --git a/src/main/java/org/sopt/dto/response/ArticleResponse.java b/src/main/java/org/sopt/dto/response/ArticleResponse.java index 4e24be3..621a526 100644 --- a/src/main/java/org/sopt/dto/response/ArticleResponse.java +++ b/src/main/java/org/sopt/dto/response/ArticleResponse.java @@ -3,6 +3,7 @@ import org.sopt.domain.Article; import java.time.LocalDateTime; +import java.util.List; public record ArticleResponse( Long id, @@ -10,7 +11,8 @@ public record ArticleResponse( String title, String content, String tag, - LocalDateTime createdAt + LocalDateTime createdAt, + List comments ) { public static ArticleResponse from(Article article) { return new ArticleResponse( @@ -19,7 +21,10 @@ public static ArticleResponse from(Article article) { article.getTitle(), article.getContent(), article.getTag().name(), - article.getCreatedAt() + article.getCreatedAt(), + article.getComments().stream() + .map(CommentResponse::from) + .toList() ); } } diff --git a/src/main/java/org/sopt/dto/response/CommentResponse.java b/src/main/java/org/sopt/dto/response/CommentResponse.java new file mode 100644 index 0000000..617308a --- /dev/null +++ b/src/main/java/org/sopt/dto/response/CommentResponse.java @@ -0,0 +1,17 @@ +package org.sopt.dto.response; + +import org.sopt.domain.Comment; + +public record CommentResponse( + Long commentId, + String content, + String writerName +) { + public static CommentResponse from(Comment comment) { + return new CommentResponse( + comment.getId(), + comment.getContent(), + comment.getMember().getName() + ); + } +} diff --git a/src/main/java/org/sopt/exception/ErrorStatus.java b/src/main/java/org/sopt/exception/ErrorStatus.java new file mode 100644 index 0000000..5ffd89a --- /dev/null +++ b/src/main/java/org/sopt/exception/ErrorStatus.java @@ -0,0 +1,27 @@ +package org.sopt.exception; + +import lombok.AccessLevel; +import lombok.Getter; +import lombok.RequiredArgsConstructor; +import org.sopt.common.code.BaseCode; +import org.springframework.http.HttpStatus; + +@Getter +@RequiredArgsConstructor(access = AccessLevel.PRIVATE) +public enum ErrorStatus implements BaseCode { + // 400 BAD_REQUEST + VALIDATION_REQUEST_MISSING_EXCEPTION(HttpStatus.BAD_REQUEST, "f4001", "잘못된 요청입니다."), + VALIDATION_INVALID_ARTICLE_REQUEST_EXCEPTION(HttpStatus.BAD_REQUEST, "f4002", "잘못된 게시글 요청입니다."), + + // 404 NOT_FOUND + NOT_FOUND_MEMBER_EXCEPTION(HttpStatus.NOT_FOUND, "f4041", "존재하지 않는 회원입니다."), + NOT_FOUND_ARTICLE_EXCEPTION(HttpStatus.NOT_FOUND, "f4042", "존재하지 않는 게시글입니다."), + + // 500 INTERNAL_SERVER_ERROR + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "f5000", "서버 내부 오류입니다."), + ; + + private final HttpStatus httpStatus; + private final String code; + private final String message; +} diff --git a/src/main/java/org/sopt/repository/CommentRepository.java b/src/main/java/org/sopt/repository/CommentRepository.java new file mode 100644 index 0000000..6d6fb17 --- /dev/null +++ b/src/main/java/org/sopt/repository/CommentRepository.java @@ -0,0 +1,10 @@ +package org.sopt.repository; + +import org.sopt.domain.Comment; +import org.springframework.data.jpa.repository.JpaRepository; + +import java.util.List; + +public interface CommentRepository extends JpaRepository { + List findAllByArticleIdOrderByIdAsc(Long articleId); +} diff --git a/src/main/java/org/sopt/service/ArticleServiceImpl.java b/src/main/java/org/sopt/service/ArticleServiceImpl.java index 5f17151..4f172f1 100644 --- a/src/main/java/org/sopt/service/ArticleServiceImpl.java +++ b/src/main/java/org/sopt/service/ArticleServiceImpl.java @@ -1,6 +1,6 @@ package org.sopt.service; -import org.sopt.common.ArticleValidator; +import org.sopt.common.validator.ArticleValidator; import org.sopt.domain.Article; import org.sopt.domain.ArticleTag; import org.sopt.domain.Member; diff --git a/src/main/java/org/sopt/service/CommentService.java b/src/main/java/org/sopt/service/CommentService.java new file mode 100644 index 0000000..14256e6 --- /dev/null +++ b/src/main/java/org/sopt/service/CommentService.java @@ -0,0 +1,13 @@ +package org.sopt.service; + +import org.sopt.dto.request.CommentRequest; +import org.sopt.dto.response.CommentResponse; + +import java.util.List; + +public interface CommentService { + List getComments(Long articleId); + Long createComment(Long articleId, CommentRequest request); + void updateComment(Long commentId, CommentRequest request); + void deleteComment(Long commentId); +} diff --git a/src/main/java/org/sopt/service/CommentServiceImpl.java b/src/main/java/org/sopt/service/CommentServiceImpl.java new file mode 100644 index 0000000..7ae598d --- /dev/null +++ b/src/main/java/org/sopt/service/CommentServiceImpl.java @@ -0,0 +1,77 @@ +package org.sopt.service; + +import jakarta.persistence.EntityNotFoundException; +import lombok.RequiredArgsConstructor; +import org.sopt.common.validator.CommentValidator; +import org.sopt.domain.Article; +import org.sopt.domain.Comment; +import org.sopt.domain.Member; +import org.sopt.dto.request.CommentRequest; +import org.sopt.dto.response.CommentResponse; +import org.sopt.repository.ArticleRepository; +import org.sopt.repository.CommentRepository; +import org.sopt.repository.MemberRepository; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; + +@Service +@RequiredArgsConstructor +@Transactional(readOnly = true) +public class CommentServiceImpl implements CommentService { + + private final CommentRepository commentRepository; + private final MemberRepository memberRepository; + private final ArticleRepository articleRepository; + private final CommentValidator commentValidator; + + @Transactional + @Override + public Long createComment(Long articleId, CommentRequest request) { + Article article = articleRepository.findById(articleId) + .orElseThrow(() -> new EntityNotFoundException("해당 게시글이 없습니다.")); + + Member member = memberRepository.findById(request.memberId()) + .orElseThrow(() -> new EntityNotFoundException("해당 유저가 없습니다.")); + + commentValidator.validateLength(request.content()); + + Comment comment = Comment.builder() + .article(article) + .member(member) + .content(request.content()) + .build(); + + return commentRepository.save(comment).getId(); + } + + @Transactional + @Override + public void updateComment(Long commentId, CommentRequest request) { + Comment comment = commentRepository.findById(commentId) + .orElseThrow(() -> new EntityNotFoundException("해당 댓글이 없습니다.")); + + commentValidator.validateWriter(comment, request.memberId()); + commentValidator.validateLength(request.content()); + + comment.updateContent(request.content()); + } + + @Transactional + @Override + public void deleteComment(Long commentId) { + Comment comment = commentRepository.findById(commentId) + .orElseThrow(() -> new EntityNotFoundException("해당 댓글이 없습니다.")); + + commentRepository.delete(comment); + } + + @Override + public List getComments(Long articleId) { + return commentRepository.findAllByArticleIdOrderByIdAsc(articleId) + .stream() + .map(CommentResponse::from) + .toList(); + } +} diff --git a/src/main/java/org/sopt/service/MemberServiceImpl.java b/src/main/java/org/sopt/service/MemberServiceImpl.java index 1de8fee..89446cb 100644 --- a/src/main/java/org/sopt/service/MemberServiceImpl.java +++ b/src/main/java/org/sopt/service/MemberServiceImpl.java @@ -7,7 +7,7 @@ import org.sopt.dto.response.MemberListResponse; import org.sopt.dto.response.MemberResponse; import org.sopt.repository.MemberRepository; -import org.sopt.common.MemberValidator; +import org.sopt.common.validator.MemberValidator; import org.springframework.stereotype.Service; import java.util.List;