-
Notifications
You must be signed in to change notification settings - Fork 4
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
댓글 작성 기능 구현 #151
댓글 작성 기능 구현 #151
Changes from all commits
5d2755c
ab3cfb1
4b390da
17fcafb
c8f173c
eb7817b
84a9e31
9c7ba51
d14f23a
d7bf9de
c8c4a51
ebc28b9
0e74bdb
db959ef
e2e044e
32239f2
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,7 +1,12 @@ | ||
package com.votogether.config; | ||
|
||
import io.swagger.v3.oas.models.Components; | ||
import io.swagger.v3.oas.models.OpenAPI; | ||
import io.swagger.v3.oas.models.info.Info; | ||
import io.swagger.v3.oas.models.security.SecurityRequirement; | ||
import io.swagger.v3.oas.models.security.SecurityScheme; | ||
import io.swagger.v3.oas.models.security.SecurityScheme.In; | ||
import io.swagger.v3.oas.models.security.SecurityScheme.Type; | ||
import io.swagger.v3.oas.models.servers.Server; | ||
import java.util.List; | ||
import org.springframework.beans.factory.annotation.Value; | ||
|
@@ -37,9 +42,22 @@ public OpenAPI openAPI() { | |
.version("v1.0.0") | ||
.description("보투게더 API"); | ||
|
||
final SecurityScheme securityScheme = new SecurityScheme() | ||
.type(Type.HTTP) | ||
.in(In.HEADER) | ||
.name("Authorization") | ||
.scheme("bearer") | ||
.bearerFormat("JWT") | ||
.description("Bearer JWT"); | ||
|
||
final SecurityRequirement securityRequirement = new SecurityRequirement() | ||
.addList("bearerAuth"); | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 오호 직접 실험까지! 감사합니다 |
||
return new OpenAPI() | ||
.info(info) | ||
.servers(List.of(devServer, prodServer)); | ||
.servers(List.of(devServer, prodServer)) | ||
.components(new Components().addSecuritySchemes("bearerAuth", securityScheme)) | ||
.security(List.of(securityRequirement)); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,52 @@ | ||
package com.votogether.domain.post.controller; | ||
|
||
import com.votogether.domain.member.entity.Member; | ||
import com.votogether.domain.post.dto.request.CommentRegisterRequest; | ||
import com.votogether.domain.post.service.PostCommentService; | ||
import com.votogether.exception.ExceptionResponse; | ||
import com.votogether.global.jwt.Auth; | ||
import io.swagger.v3.oas.annotations.Operation; | ||
import io.swagger.v3.oas.annotations.Parameter; | ||
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 lombok.RequiredArgsConstructor; | ||
import org.springframework.http.HttpStatus; | ||
import org.springframework.http.ResponseEntity; | ||
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; | ||
|
||
@Tag(name = "게시글 댓글", description = "게시글 댓글 API") | ||
@RequiredArgsConstructor | ||
@RequestMapping("/posts") | ||
@RestController | ||
public class PostCommentController { | ||
|
||
private final PostCommentService postCommentService; | ||
|
||
@Operation(summary = "게시글 댓글 작성", description = "게시글 댓글을 작성한다.") | ||
@ApiResponses({ | ||
@ApiResponse(responseCode = "200", description = "게시글 댓글 작성 성공"), | ||
@ApiResponse( | ||
responseCode = "404", | ||
description = "존재하지 않는 게시글에 대한 댓글 작성", | ||
content = @Content(schema = @Schema(implementation = ExceptionResponse.class)) | ||
) | ||
}) | ||
@PostMapping("/{postId}/comments") | ||
public ResponseEntity<Void> createComment( | ||
@Auth final Member member, | ||
@PathVariable @Parameter(description = "댓글 작성 게시글 ID") final Long postId, | ||
@Valid @RequestBody CommentRegisterRequest commentRegisterRequest | ||
) { | ||
postCommentService.createComment(member, postId, commentRegisterRequest); | ||
return ResponseEntity.status(HttpStatus.CREATED).build(); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,12 @@ | ||
package com.votogether.domain.post.dto.request; | ||
|
||
import io.swagger.v3.oas.annotations.media.Schema; | ||
import jakarta.validation.constraints.NotBlank; | ||
|
||
@Schema(description = "게시글 댓글 작성 요청") | ||
public record CommentRegisterRequest( | ||
@Schema(description = "댓글 내용", example = "hello") | ||
@NotBlank(message = "댓글 내용은 존재해야 합니다.") | ||
String content | ||
) { | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,9 +3,11 @@ | |
import com.votogether.domain.category.entity.Category; | ||
import com.votogether.domain.common.BaseEntity; | ||
import com.votogether.domain.member.entity.Member; | ||
import com.votogether.domain.post.entity.comment.Comment; | ||
import com.votogether.domain.post.exception.PostExceptionType; | ||
import com.votogether.domain.vote.entity.Vote; | ||
import com.votogether.exception.BadRequestException; | ||
import jakarta.persistence.CascadeType; | ||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Embedded; | ||
import jakarta.persistence.Entity; | ||
|
@@ -15,7 +17,9 @@ | |
import jakarta.persistence.Id; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.ManyToOne; | ||
import jakarta.persistence.OneToMany; | ||
import java.time.LocalDateTime; | ||
import java.util.ArrayList; | ||
import java.util.List; | ||
import java.util.Objects; | ||
import java.util.stream.IntStream; | ||
|
@@ -50,6 +54,9 @@ public class Post extends BaseEntity { | |
@Column(columnDefinition = "datetime(2)", nullable = false) | ||
private LocalDateTime deadline; | ||
|
||
@OneToMany(mappedBy = "post", cascade = CascadeType.PERSIST) | ||
private List<Comment> comments = new ArrayList<>(); | ||
Comment on lines
+57
to
+58
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 좋은 의견 감사합니다 👍 해당 속성은 댓글의
|
||
|
||
@Builder | ||
private Post( | ||
final Member member, | ||
|
@@ -129,4 +136,9 @@ private void validatePostOption(PostOption postOption) { | |
} | ||
} | ||
|
||
public void addComment(final Comment comment) { | ||
comments.add(comment); | ||
comment.setPost(this); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
package com.votogether.domain.post.entity.comment; | ||
|
||
import com.votogether.domain.common.BaseEntity; | ||
import com.votogether.domain.member.entity.Member; | ||
import com.votogether.domain.post.entity.Post; | ||
import jakarta.persistence.Embedded; | ||
import jakarta.persistence.Entity; | ||
import jakarta.persistence.FetchType; | ||
import jakarta.persistence.GeneratedValue; | ||
import jakarta.persistence.GenerationType; | ||
import jakarta.persistence.Id; | ||
import jakarta.persistence.JoinColumn; | ||
import jakarta.persistence.ManyToOne; | ||
import lombok.AccessLevel; | ||
import lombok.Builder; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
import lombok.Setter; | ||
|
||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
@Getter | ||
@Entity | ||
public class Comment extends BaseEntity { | ||
|
||
@Id | ||
@GeneratedValue(strategy = GenerationType.IDENTITY) | ||
private Long id; | ||
|
||
@Setter | ||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "post_id", nullable = false) | ||
private Post post; | ||
|
||
@ManyToOne(fetch = FetchType.LAZY) | ||
@JoinColumn(name = "member_id", nullable = false) | ||
private Member member; | ||
|
||
@Embedded | ||
private Content content; | ||
|
||
@Builder | ||
private Comment(final Post post, final Member member, final String content) { | ||
this.post = post; | ||
this.member = member; | ||
this.content = new Content(content); | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,32 @@ | ||
package com.votogether.domain.post.entity.comment; | ||
|
||
import com.votogether.domain.post.exception.CommentExceptionType; | ||
import com.votogether.exception.BadRequestException; | ||
import jakarta.persistence.Column; | ||
import jakarta.persistence.Embeddable; | ||
import lombok.AccessLevel; | ||
import lombok.Getter; | ||
import lombok.NoArgsConstructor; | ||
|
||
@NoArgsConstructor(access = AccessLevel.PROTECTED) | ||
@Getter | ||
@Embeddable | ||
class Content { | ||
|
||
private static final int MAXIMUM_LENGTH = 500; | ||
|
||
@Column(name = "content", nullable = false, length = MAXIMUM_LENGTH) | ||
private String value; | ||
|
||
public Content(final String value) { | ||
validate(value); | ||
this.value = value; | ||
} | ||
|
||
private void validate(final String content) { | ||
if (content.length() > MAXIMUM_LENGTH) { | ||
throw new BadRequestException(CommentExceptionType.INVALID_CONTENT_LENGTH); | ||
} | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
package com.votogether.domain.post.exception; | ||
|
||
import com.votogether.exception.ExceptionType; | ||
import lombok.Getter; | ||
|
||
@Getter | ||
public enum CommentExceptionType implements ExceptionType { | ||
|
||
INVALID_CONTENT_LENGTH(2000, "유효하지 않은 댓글 길이입니다."); | ||
|
||
private final int code; | ||
private final String message; | ||
|
||
CommentExceptionType(final int code, final String message) { | ||
this.code = code; | ||
this.message = message; | ||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
package com.votogether.domain.post.repository; | ||
|
||
import com.votogether.domain.post.entity.comment.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 |
---|---|---|
@@ -0,0 +1,37 @@ | ||
package com.votogether.domain.post.service; | ||
|
||
import com.votogether.domain.member.entity.Member; | ||
import com.votogether.domain.post.dto.request.CommentRegisterRequest; | ||
import com.votogether.domain.post.entity.Post; | ||
import com.votogether.domain.post.entity.comment.Comment; | ||
import com.votogether.domain.post.exception.PostExceptionType; | ||
import com.votogether.domain.post.repository.PostRepository; | ||
import com.votogether.exception.NotFoundException; | ||
import lombok.RequiredArgsConstructor; | ||
import org.springframework.stereotype.Service; | ||
import org.springframework.transaction.annotation.Transactional; | ||
|
||
@RequiredArgsConstructor | ||
@Service | ||
public class PostCommentService { | ||
|
||
private final PostRepository postRepository; | ||
|
||
@Transactional | ||
public void createComment( | ||
final Member member, | ||
final Long postId, | ||
final CommentRegisterRequest commentRegisterRequest | ||
) { | ||
final Post post = postRepository.findById(postId) | ||
.orElseThrow(() -> new NotFoundException(PostExceptionType.POST_NOT_FOUND)); | ||
|
||
final Comment comment = Comment.builder() | ||
.member(member) | ||
.content(commentRegisterRequest.content()) | ||
.build(); | ||
|
||
post.addComment(comment); | ||
Comment on lines
+29
to
+34
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 저는 이전 방식보다 이렇게 작성하는 것이 더 좋아보인다고 생각했는데, 변경해주셨군요👍🏻 |
||
} | ||
|
||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,10 +1,12 @@ | ||
package com.votogether.global.jwt; | ||
|
||
import io.swagger.v3.oas.annotations.Hidden; | ||
import java.lang.annotation.ElementType; | ||
import java.lang.annotation.Retention; | ||
import java.lang.annotation.RetentionPolicy; | ||
import java.lang.annotation.Target; | ||
|
||
@Hidden | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more.
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. |
||
@Target(ElementType.PARAMETER) | ||
@Retention(RetentionPolicy.RUNTIME) | ||
public @interface Auth { | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Q
신기한 기능이네요!
swagger에 토큰을 적용해볼 수 있는 코드라고 생각하면 될까요?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
맞습니다 :) 해당 기능을 사용하면 Swagger 전역적으로 토큰을 적용할 수 있습니다. 현재는
Bearer
토큰을 설정해두었는데, 설정을 수정하면Basic
토큰도 사용이 가능합니다!