Skip to content

Commit

Permalink
Merge pull request #150 from sharemindteam/feature/145-counselorSide-…
Browse files Browse the repository at this point in the history
…post

feat: 일대다 상담 미답변 질문 조회 및 답변 기능 구현
  • Loading branch information
aeyongdodam authored Mar 23, 2024
2 parents c152334 + 5683f8b commit 98aa901
Show file tree
Hide file tree
Showing 17 changed files with 414 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package com.example.sharemind.comment.application;

import com.example.sharemind.comment.dto.request.CommentCreateRequest;
import com.example.sharemind.comment.dto.response.CommentGetResponse;

import java.util.List;

public interface CommentService {
List<CommentGetResponse> getCommentsByPost(Long postId, Long customerId);

void createComment(CommentCreateRequest commentCreateRequest, Long customerId);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
package com.example.sharemind.comment.application;

import com.example.sharemind.comment.domain.Comment;
import com.example.sharemind.comment.dto.request.CommentCreateRequest;
import com.example.sharemind.comment.dto.response.CommentGetResponse;
import com.example.sharemind.comment.exception.CommentErrorCode;
import com.example.sharemind.comment.exception.CommentException;
import com.example.sharemind.comment.repository.CommentRepository;
import com.example.sharemind.counselor.application.CounselorService;
import com.example.sharemind.counselor.domain.Counselor;
import com.example.sharemind.post.application.PostService;
import com.example.sharemind.post.content.PostStatus;
import com.example.sharemind.post.domain.Post;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;

@Service
@Transactional(readOnly = true)
@RequiredArgsConstructor
public class CommentServiceImpl implements CommentService {

private final PostService postService;
private final CounselorService counselorService;
private final CommentRepository commentRepository;

private static final Integer MAX_COMMENTS = 5;

@Override
public List<CommentGetResponse> getCommentsByPost(Long postId, Long customerId) {
Post post = postService.checkAndGetCounselorPost(postId, customerId);

List<Comment> comments = commentRepository.findByPostAndIsActivatedIsTrue(post);
return comments.stream()
.map(CommentGetResponse::of)
.toList();
}

@Transactional
@Override
public void createComment(CommentCreateRequest commentCreateRequest, Long customerId) {
Post post = postService.checkAndGetCounselorPost(commentCreateRequest.getPostId(), customerId);
Counselor counselor = counselorService.getCounselorByCustomerId(customerId);

if (commentRepository.findByPostAndCounselorAndIsActivatedIsTrue(post, counselor) != null)
throw new CommentException(CommentErrorCode.COMMENT_ALREADY_REGISTERED, counselor.getNickname());

commentRepository.save(commentCreateRequest.toEntity(post, counselor));

List<Comment> comments = commentRepository.findByPostAndIsActivatedIsTrue(post);
if (comments.size() == MAX_COMMENTS)
post.updatePostStatus(PostStatus.COMPLETED);
}
}
48 changes: 48 additions & 0 deletions src/main/java/com/example/sharemind/comment/domain/Comment.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package com.example.sharemind.comment.domain;

import com.example.sharemind.counselor.domain.Counselor;
import com.example.sharemind.global.common.BaseEntity;
import com.example.sharemind.post.domain.Post;
import jakarta.persistence.*;
import jakarta.validation.constraints.Size;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Getter
@Entity
public class Comment extends BaseEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "comment_id")
private Long commentId;

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "post_id")
private Post post;

@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "counselor_id")
private Counselor counselor;

@Size(max = 500, message = "상담 답변은 최대 500자입니다.")
private String content;

@Column(name = "is_chosen", nullable = false)
private Boolean isChosen;

@Column(name = "total_like", nullable = false)
private Long totalLike;

@Builder
public Comment(Post post, Counselor counselor, String content) {
this.post = post;
this.counselor = counselor;
this.content = content;
isChosen = false;
totalLike = 0L;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package com.example.sharemind.comment.dto.request;

import com.example.sharemind.comment.domain.Comment;
import com.example.sharemind.counselor.domain.Counselor;
import com.example.sharemind.post.domain.Post;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import lombok.Getter;

@Getter
public class CommentCreateRequest {

@Schema(description = "상담 id")
@NotNull
private Long postId;

@Schema(description = "content")
private String content;

public Comment toEntity(Post post, Counselor counselor) {
return Comment.builder()
.post(post)
.counselor(counselor)
.content(content)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
package com.example.sharemind.comment.dto.response;

import com.example.sharemind.comment.domain.Comment;
import com.example.sharemind.global.utils.TimeUtil;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Builder;
import lombok.Getter;

@Getter
public class CommentGetResponse {

@Schema(description = "상담사 닉네임")
private final String nickName;

@Schema(description = "답변 내용")
private final String content;

@Schema(description = "좋아요 수")
private final Long totalLike;

@Schema(description = "마지막 업데이트 일시", example = "오전 11:10")
private final String updatedAt;

@Schema(description = "채택 여부", example="true")
private final Boolean isChosen;

@Builder
public CommentGetResponse(String nickName, String content, Long totalLike, String updatedAt, Boolean isChosen) {
this.nickName = nickName;
this.content = content;
this.totalLike = totalLike;
this.updatedAt = updatedAt;
this.isChosen = isChosen;
}

public static CommentGetResponse of(Comment comment) {
return CommentGetResponse.builder()
.nickName(comment.getCounselor().getNickname())
.content(comment.getContent())
.totalLike(comment.getTotalLike())
.updatedAt(TimeUtil.getUpdatedAt(comment.getUpdatedAt()))
.isChosen(comment.getIsChosen())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package com.example.sharemind.comment.exception;

import lombok.Getter;
import org.springframework.http.HttpStatus;

@Getter
public enum CommentErrorCode {

COMMENT_ALREADY_REGISTERED(HttpStatus.BAD_REQUEST, "상담사 당 댓글은 한 번만 작성할 수 있습니다.");

private final HttpStatus httpStatus;
private final String message;

CommentErrorCode(HttpStatus httpStatus, String message) {
this.httpStatus = httpStatus;
this.message = message;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.example.sharemind.comment.exception;

import lombok.Getter;

@Getter
public class CommentException extends RuntimeException {

private final CommentErrorCode errorCode;

public CommentException(CommentErrorCode errorCode, String message) {
super(errorCode.getMessage() + " : " + message);
this.errorCode = errorCode;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
package com.example.sharemind.comment.presentation;

import com.example.sharemind.comment.application.CommentService;
import com.example.sharemind.comment.dto.request.CommentCreateRequest;
import com.example.sharemind.comment.dto.response.CommentGetResponse;
import com.example.sharemind.global.exception.CustomExceptionResponse;
import com.example.sharemind.global.jwt.CustomUserDetails;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.Parameters;
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 lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;

import java.util.List;

@Tag(name = "Comment Controller", description = "일대다 상담 답변 관리 컨트롤러")
@RestController
@RequestMapping("/api/v1/comments")
@RequiredArgsConstructor
public class CommentController {

private final CommentService commentService;

@Operation(summary = "상담사 사이드 일대다 상담 질문 단건의 댓글 조회", description = """
- 상담사가 일대다 상담 질문에 대답하기 위한 상담 질문 단건의 댓글 조회
- 주소 형식: /api/v1/comments/counselors/1""")
@ApiResponses({
@ApiResponse(responseCode = "200", description = "조회 성공"),
@ApiResponse(responseCode = "400", description = "1. 진행중이지 않은 상담 2. 마감된 상담 중 상담사 본인이 답변을 작성하지 않은 상담",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = CustomExceptionResponse.class))
)
})
@Parameters({
@Parameter(name = "postId", description = """
- 일대다 상담 ID""")
})
@GetMapping("/counselors/{postId}")
public ResponseEntity<List<CommentGetResponse>> getCounselorComments(@PathVariable Long postId,
@AuthenticationPrincipal CustomUserDetails customUserDetails) {
return ResponseEntity.ok(commentService.getCommentsByPost(postId, customUserDetails.getCustomer().getCustomerId()));
}

@Operation(summary = "상담사 사이드 일대다 상담 댓글 작성", description = """
- 상담사 사이드 일대다 상담 댓글 작성 API
- 주소 형식: /api/v1/comments/counselors""")
@ApiResponses({
@ApiResponse(responseCode = "201", description = "댓글 생성 성공"),
@ApiResponse(responseCode = "400", description = "1. 진행중이지 않은 상담 2. 마감된 상담 중 상담사 본인이 답변을 작성하지 않은 상담 3. 이미 답변을 작성한 상담",
content = @Content(mediaType = "application/json",
schema = @Schema(implementation = CustomExceptionResponse.class))
)
})
@PostMapping("/counselors")
public ResponseEntity<Void> createComments(@RequestBody CommentCreateRequest commentCreateRequest,
@AuthenticationPrincipal CustomUserDetails customUserDetails) {
commentService.createComment(commentCreateRequest, customUserDetails.getCustomer().getCustomerId());
return ResponseEntity.status(HttpStatus.CREATED).build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.example.sharemind.comment.repository;

import com.example.sharemind.comment.domain.Comment;
import com.example.sharemind.counselor.domain.Counselor;
import com.example.sharemind.post.domain.Post;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;

@Repository
public interface CommentRepository extends JpaRepository<Comment, Long> {
List<Comment> findByPostAndIsActivatedIsTrue(Post post);

Comment findByPostAndCounselorAndIsActivatedIsTrue(Post post, Counselor counselor);
}
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,8 @@ public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Excepti
.requestMatchers("/api/v1/chatMessages/counselors/**").hasRole(ROLE_COUNSELOR)
.requestMatchers("/api/v1/consults/counselors").hasRole(ROLE_COUNSELOR)
.requestMatchers("/api/v1/payments/counselors/**").hasRole(ROLE_COUNSELOR)
.requestMatchers("/api/v1/posts/counselors/**").hasRole(ROLE_COUNSELOR)
.requestMatchers("/api/v1/comments/counselors/**").hasRole(ROLE_COUNSELOR)
.requestMatchers(("/api/v1/counselors/account")).hasRole(ROLE_COUNSELOR)
.anyRequest().authenticated()
)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import com.example.sharemind.auth.exception.AuthException;
import com.example.sharemind.chat.exception.ChatException;
import com.example.sharemind.chatMessage.exception.ChatMessageException;
import com.example.sharemind.comment.exception.CommentException;
import com.example.sharemind.consult.exception.ConsultException;
import com.example.sharemind.counselor.exception.CounselorException;
import com.example.sharemind.customer.exception.CustomerException;
Expand Down Expand Up @@ -95,6 +96,13 @@ public ResponseEntity<CustomExceptionResponse> catchChatMessageException(ChatMes
.body(CustomExceptionResponse.of(e.getErrorCode().name(), e.getMessage()));
}

@ExceptionHandler(CommentException.class)
public ResponseEntity<CustomExceptionResponse> catchCommentException(CommentException e) {
log.error(e.getMessage(), e);
return ResponseEntity.status(e.getErrorCode().getHttpStatus())
.body(CustomExceptionResponse.of(e.getErrorCode().name(), e.getMessage()));
}

@ExceptionHandler(ReviewException.class)
public ResponseEntity<CustomExceptionResponse> catchReviewException(ReviewException e) {
log.error(e.getMessage(), e);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,4 +22,10 @@ public interface PostService {
PostGetResponse getPost(Long postId, Long customerId);

List<PostGetResponse> getPostsByCustomer(Boolean filter, Long postId, Long customerId);

List<Long> getRandomPosts();

PostGetResponse getCounselorPostContent(Long postId, Long customerId);

Post checkAndGetCounselorPost(Long postId, Long customerId);
}
Loading

0 comments on commit 98aa901

Please sign in to comment.