Skip to content

Commit

Permalink
#20 [feat] POST - 댓글 등록 API 구현
Browse files Browse the repository at this point in the history
#20 [feat] POST - 댓글 등록 API 구현
  • Loading branch information
sohyundoh authored Jan 7, 2024
2 parents 884a638 + c4786cb commit 47f1ae7
Show file tree
Hide file tree
Showing 21 changed files with 248 additions and 6 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.mile.controller.post;

import com.mile.dto.SuccessResponse;
import com.mile.exception.message.SuccessMessage;
import com.mile.post.service.PostService;
import com.mile.post.service.dto.CommentCreateRequest;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.security.Principal;

@RestController
@RequestMapping("/api/post")
@RequiredArgsConstructor
public class PostController implements PostControllerSwagger{

private final PostService postService;

@PostMapping("/{postId}/comment")
@Override
public SuccessResponse postComment(
@PathVariable final Long postId,
@Valid @RequestBody final CommentCreateRequest commentCreateRequest,
final Principal principal
) {
postService.createCommentOnPost(
postId,
Long.valueOf(principal.getName()),
commentCreateRequest
);
return SuccessResponse.of(SuccessMessage.COMMENT_CREATE_SUCCESS);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.mile.controller.post;

import com.mile.dto.ErrorResponse;
import com.mile.dto.SuccessResponse;
import com.mile.post.service.dto.CommentCreateRequest;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.media.Content;
import io.swagger.v3.oas.annotations.media.Schema;
import io.swagger.v3.oas.annotations.responses.ApiResponse;
import io.swagger.v3.oas.annotations.responses.ApiResponses;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestBody;

import java.security.Principal;

@Tag(name = "Post", description = "게시글 관련 API - 댓글 등록/ 조회 포함")
public interface PostControllerSwagger {

@Operation(summary = "댓글 작성")
@ApiResponses(
value = {
@ApiResponse(responseCode = "200", description = "댓글 등록이 완료되었습니다."),
@ApiResponse(responseCode = "400",
description = "1. 댓글 최대 입력 길이(500자)를 초과하였습니다.\n" +
"2.댓글에 내용이 없습니다.",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "403", description = "해당 사용자는 모임에 접근 권한이 없습니다.",
content = @Content(schema = @Schema(implementation = ErrorResponse.class))),
@ApiResponse(responseCode = "500", description = "서버 내부 오류입니다.",
content = @Content(schema = @Schema(implementation = ErrorResponse.class)))
}
)
SuccessResponse postComment(
@PathVariable final Long postId,
@Valid @RequestBody final CommentCreateRequest commentCreateRequest,
final Principal principal
);
}
4 changes: 4 additions & 0 deletions module-common/src/main/java/com/mile/dto/ErrorResponse.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,8 @@ public record ErrorResponse(
public static ErrorResponse of(final ErrorMessage errorMessage) {
return new ErrorResponse(errorMessage.getStatus(), errorMessage.getMessage());
}

public static ErrorResponse of(final int status, final String message) {
return new ErrorResponse(status, message);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,23 @@ public enum ErrorMessage {
*/
USER_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "해당 유저는 존재하지 않습니다."),
REFRESH_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "해당 유저의 리프레시 토큰이 존재하지 않습니다."),
POST_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "해당 글은 존재하지 않습니다."),

MOIM_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "해당 글모임이 존재하지 않습니다."),
CONTENT_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "해당 모임의 주제가 존재하지 않습니다."),
HANDLER_NOT_FOUND(HttpStatus.NOT_FOUND.value(), "요청하신 URL은 정보가 없습니다."),

/*
Bad Request
*/
ENUM_VALUE_BAD_REQUEST(HttpStatus.BAD_REQUEST.value(), "요청한 값이 유효하지 않습니다."),
AUTHENTICATION_CODE_EXPIRED(HttpStatus.BAD_REQUEST.value(), "인가 코드가 만료되었습니다."),
SOCIAL_TYPE_BAD_REQUEST(HttpStatus.BAD_REQUEST.value(), "로그인 요청이 유효하지 않습니다."),

VALIDATION_REQUEST_MISSING_EXCEPTION(HttpStatus.BAD_REQUEST.value(), "요청 값이 유효하지 않습니다."),

BEARER_LOST_ERROR(HttpStatus.BAD_REQUEST.value(), "토큰의 요청에 Bearer이 담겨 있지 않습니다."),

/*
Unauthorized
*/
Expand All @@ -33,6 +40,7 @@ public enum ErrorMessage {
Forbidden
*/
USER_AUTHENTICATE_ERROR(HttpStatus.FORBIDDEN.value(), "해당 사용자는 모임에 접근 권한이 없습니다."),

/*
Internal Server Error
*/
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,12 @@ public enum SuccessMessage {
USER_DELETE_SUCCESS(HttpStatus.OK.value(), "회원 삭제가 완료되었습니다."),
LOGOUT_SUCCESS(HttpStatus.OK.value(), "로그아웃이 완료되었습니다."),

/*
201 CREATED
*/
COMMENT_CREATE_SUCCESS(HttpStatus.CREATED.value(), "댓글 등록이 완료되었습니다."),


TOPIC_SEARCH_SUCCESS(HttpStatus.OK.value(), "주제 조회가 완료되었습니다."),
;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ public class ForbiddenException extends MileException {
public ForbiddenException(final ErrorMessage errorMessage) {
super(errorMessage);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.http.converter.HttpMessageNotReadableException;
import org.springframework.validation.FieldError;
import org.springframework.web.bind.MethodArgumentNotValidException;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestControllerAdvice;
import org.springframework.web.servlet.NoHandlerFoundException;
Expand Down Expand Up @@ -40,6 +42,13 @@ public ResponseEntity<ErrorResponse> handleJwtValidationException(final JwtValid
return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body(ErrorResponse.of(e.getErrorMessage()));
}

@ExceptionHandler(MethodArgumentNotValidException.class)
protected ResponseEntity<ErrorResponse> handleMethodArgumentNotValidException(final MethodArgumentNotValidException e) {
FieldError fieldError = e.getBindingResult().getFieldError();
if (fieldError == null) return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ErrorResponse.of(ErrorMessage.VALIDATION_REQUEST_MISSING_EXCEPTION));
else return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ErrorResponse.of(HttpStatus.BAD_REQUEST.value(), fieldError.getDefaultMessage()));
}

@ExceptionHandler(ForbiddenException.class)
public ResponseEntity<ErrorResponse> handleForbiddenException(final ForbiddenException e) {
return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ErrorResponse.of(e.getErrorMessage()));
Expand Down
3 changes: 3 additions & 0 deletions module-domain/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@ dependencies {
implementation project(':module-auth')
implementation project(':module-common')

//Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.1.0'

// QueryDSL Implementation
implementation 'com.querydsl:querydsl-jpa:5.0.0:jakarta'
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
Expand Down
25 changes: 24 additions & 1 deletion module-domain/src/main/java/com/mile/comment/domain/Comment.java
Original file line number Diff line number Diff line change
@@ -1,15 +1,23 @@
package com.mile.comment.domain;

import com.mile.config.BaseTimeEntity;
import com.mile.post.Post;
import com.mile.post.domain.Post;
import com.mile.post.service.dto.CommentCreateRequest;
import com.mile.user.domain.User;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

@Entity
@Builder
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@NoArgsConstructor
public class Comment extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand All @@ -23,4 +31,19 @@ public class Comment extends BaseTimeEntity {

@ManyToOne
private User user;

public static Comment create(
final Post post,
final User user,
final CommentCreateRequest createRequest,
final boolean anonymous
) {
return Comment
.builder()
.post(post)
.content(createRequest.content())
.user(user)
.anonymous(anonymous)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.mile.comment.repository;

import com.mile.comment.domain.Comment;
import org.springframework.data.jpa.repository.JpaRepository;

public interface CommentRepository extends JpaRepository<Comment, Long> {
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,24 @@
package com.mile.comment.service;

import com.mile.comment.domain.Comment;
import com.mile.comment.repository.CommentRepository;
import com.mile.post.domain.Post;
import com.mile.post.service.dto.CommentCreateRequest;
import com.mile.user.domain.User;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;

@Service
@RequiredArgsConstructor
public class CommentService {
private static boolean ANONYMOUS_TRUE = true;
private final CommentRepository commentRepository;

public void createComment(
final Post post,
final User user,
final CommentCreateRequest commentCreateRequest
) {
commentRepository.save(Comment.create(post, user, commentCreateRequest, ANONYMOUS_TRUE));
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
package com.mile.curious.domain;

import com.mile.post.Post;
import com.mile.post.domain.Post;
import com.mile.config.BaseTimeEntity;
import com.mile.user.domain.User;
import jakarta.persistence.Entity;
Expand Down
2 changes: 2 additions & 0 deletions module-domain/src/main/java/com/mile/moim/domain/Moim.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.Getter;

@Entity
@Getter
public class Moim {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public ContentListResponse getContentsFromMoim(
return ContentListResponse.of(topicService.getContentsFromMoim(moimId));
}

private void authenticateUserOfMoim(
public void authenticateUserOfMoim(
final Long moimId,
final Long userId
) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package com.mile.post;
package com.mile.post.domain;

import com.mile.config.BaseTimeEntity;
import com.mile.topic.domain.Topic;
Expand All @@ -7,8 +7,10 @@
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
import lombok.Getter;

@Entity
@Getter
public class Post extends BaseTimeEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.mile.post.repository;

import com.mile.post.domain.Post;
import org.springframework.data.jpa.repository.JpaRepository;

public interface PostRepository extends JpaRepository<Post, Long> {
}
52 changes: 52 additions & 0 deletions module-domain/src/main/java/com/mile/post/service/PostService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package com.mile.post.service;

import com.mile.comment.service.CommentService;
import com.mile.exception.message.ErrorMessage;
import com.mile.exception.model.NotFoundException;
import com.mile.moim.serivce.MoimService;
import com.mile.post.domain.Post;
import com.mile.post.repository.PostRepository;
import com.mile.post.service.dto.CommentCreateRequest;
import com.mile.user.serivce.UserService;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
public class PostService {

private final PostRepository postRepository;
private final MoimService moimService;
private final CommentService commentService;
private final UserService userService;

@Transactional
public void createCommentOnPost(
final Long postId,
final Long userId,
final CommentCreateRequest commentCreateRequest

) {
Post post = findById(postId);
authenticateUserWithPost(post, userId);
commentService.createComment(post, userService.findById(userId), commentCreateRequest);
}


private void authenticateUserWithPost(
final Post post,
final Long userId
) {
moimService.authenticateUserOfMoim(post.getTopic().getMoim().getId(), userId);
}

public Post findById(
final Long postId
) {
return postRepository.findById(postId)
.orElseThrow(
() -> new NotFoundException(ErrorMessage.POST_NOT_FOUND)
);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.mile.post.service.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Size;

public record CommentCreateRequest(

@Schema(description = "댓글 내용", example = "댓글 내용을 입력해주세요")
@NotBlank(message = "댓글에 내용이 없습니다.")
@Size(max = 500, message = "댓글 최대 입력 길이(500자)를 초과하였습니다.")
String content
) {
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ public class TopicService {

private final TopicRepository topicRepository;


public List<ContentResponse> getContentsFromMoim(
final Long moimId
) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,4 +118,13 @@ private LoginSuccessResponse getTokenDto(
return getTokenByUserId(id);
}
}

public User findById(
Long userId
) {
return userRepository.findById(userId)
.orElseThrow(
() -> new NotFoundException(ErrorMessage.USER_NOT_FOUND)
);
}
}
Loading

0 comments on commit 47f1ae7

Please sign in to comment.