Merge branch 'be/feat/156-comment' into 'be/develop'
Feat: Comment 기능 구현 - S11P21S002-156 See merge request s11-s-project/S11P21S002!55
This commit is contained in:
commit
d55d39eba3
@ -0,0 +1,89 @@
|
|||||||
|
package com.worlabel.domain.comment.contoller;
|
||||||
|
|
||||||
|
import com.worlabel.domain.comment.entity.dto.CommentRequest;
|
||||||
|
import com.worlabel.domain.comment.entity.dto.CommentResponse;
|
||||||
|
import com.worlabel.domain.comment.entity.dto.CommentResponses;
|
||||||
|
import com.worlabel.domain.comment.service.CommentService;
|
||||||
|
import com.worlabel.global.annotation.CurrentUser;
|
||||||
|
import com.worlabel.global.config.swagger.SwaggerApiError;
|
||||||
|
import com.worlabel.global.config.swagger.SwaggerApiSuccess;
|
||||||
|
import com.worlabel.global.exception.ErrorCode;
|
||||||
|
import com.worlabel.global.response.BaseResponse;
|
||||||
|
import com.worlabel.global.response.SuccessResponse;
|
||||||
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/api/projects/{project_id}/comments")
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
@Tag(name = "댓글 관련 API")
|
||||||
|
public class CommentController {
|
||||||
|
|
||||||
|
private final CommentService commentService;
|
||||||
|
|
||||||
|
@GetMapping("/images/{image_id}")
|
||||||
|
@SwaggerApiSuccess(description = "댓글 목록을 성공적으로 조회합니다.")
|
||||||
|
@Operation(summary = "댓글 목록 조회", description = "댓글 목록을 조회합니다.")
|
||||||
|
@SwaggerApiError({ErrorCode.BAD_REQUEST, ErrorCode.NOT_AUTHOR, ErrorCode.SERVER_ERROR})
|
||||||
|
public BaseResponse<CommentResponses> getAllComments(
|
||||||
|
@CurrentUser final Integer memberId,
|
||||||
|
@PathVariable("project_id") final Integer projectId,
|
||||||
|
@PathVariable("image_id") final Long imageId) {
|
||||||
|
List<CommentResponse> comments = commentService.getAllComments(memberId, projectId, imageId);
|
||||||
|
return new SuccessResponse<>(CommentResponses.from(comments));
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping("/{comment_id}")
|
||||||
|
@SwaggerApiSuccess(description = "댓글을 성공적으로 조회합니다.")
|
||||||
|
@Operation(summary = "댓글 조회", description = "댓글을 조회합니다.")
|
||||||
|
@SwaggerApiError({ErrorCode.COMMENT_NOT_FOUND, ErrorCode.NOT_AUTHOR, ErrorCode.SERVER_ERROR})
|
||||||
|
public BaseResponse<CommentResponse> getCommentById(
|
||||||
|
@CurrentUser final Integer memberId,
|
||||||
|
@PathVariable("project_id") final Integer projectId,
|
||||||
|
@PathVariable("comment_id") final Integer commentId) {
|
||||||
|
CommentResponse comment = commentService.getCommentById(memberId, projectId, commentId);
|
||||||
|
return new SuccessResponse<>(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PostMapping("/images/{image_id}")
|
||||||
|
@SwaggerApiSuccess(description = "댓글을 성공적으로 생성합니다.")
|
||||||
|
@Operation(summary = "댓글 생성", description = "댓글을 생성합니다.")
|
||||||
|
@SwaggerApiError({ErrorCode.NOT_AUTHOR, ErrorCode.SERVER_ERROR})
|
||||||
|
public BaseResponse<CommentResponse> createComment(
|
||||||
|
@RequestBody final CommentRequest commentRequest,
|
||||||
|
@CurrentUser final Integer memberId,
|
||||||
|
@PathVariable("project_id") final Integer projectId,
|
||||||
|
@PathVariable("image_id") final Long imageId) {
|
||||||
|
CommentResponse comment = commentService.createComment(commentRequest, memberId, projectId, imageId);
|
||||||
|
return new SuccessResponse<>(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@PutMapping("/{comment_id}")
|
||||||
|
@SwaggerApiSuccess(description = "댓글을 성공적으로 수정합니다.")
|
||||||
|
@Operation(summary = "댓글 수정", description = "댓글을 수정합니다.")
|
||||||
|
@SwaggerApiError({ErrorCode.NOT_AUTHOR, ErrorCode.SERVER_ERROR})
|
||||||
|
public BaseResponse<CommentResponse> updateComment(
|
||||||
|
@RequestBody final CommentRequest commentRequest,
|
||||||
|
@CurrentUser final Integer memberId,
|
||||||
|
@PathVariable("project_id") final Integer projectId,
|
||||||
|
@PathVariable("comment_id") final Integer commentId) {
|
||||||
|
CommentResponse comment = commentService.updateComment(commentRequest, memberId, projectId, commentId);
|
||||||
|
return new SuccessResponse<>(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
@DeleteMapping("/{comment_id}")
|
||||||
|
@SwaggerApiSuccess(description = "댓글을 성공적으로 생성합니다.")
|
||||||
|
@Operation(summary = "댓글 생성", description = "댓글을 생성합니다.")
|
||||||
|
@SwaggerApiError({ErrorCode.NOT_AUTHOR, ErrorCode.SERVER_ERROR})
|
||||||
|
public BaseResponse<Void> deleteComment(
|
||||||
|
@CurrentUser final Integer memberId,
|
||||||
|
@PathVariable("project_id") final Integer projectId,
|
||||||
|
@PathVariable("comment_id") final Integer commentId) {
|
||||||
|
commentService.deleteComment(memberId, projectId, commentId);
|
||||||
|
return new SuccessResponse<>();
|
||||||
|
}
|
||||||
|
}
|
@ -1,6 +1,7 @@
|
|||||||
package com.worlabel.domain.comment.entity;
|
package com.worlabel.domain.comment.entity;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
import com.worlabel.domain.image.entity.Image;
|
||||||
import com.worlabel.domain.member.entity.Member;
|
import com.worlabel.domain.member.entity.Member;
|
||||||
import com.worlabel.global.common.BaseEntity;
|
import com.worlabel.global.common.BaseEntity;
|
||||||
import jakarta.persistence.*;
|
import jakarta.persistence.*;
|
||||||
@ -47,4 +48,29 @@ public class Comment extends BaseEntity {
|
|||||||
@JoinColumn(name = "member_id", nullable = false)
|
@JoinColumn(name = "member_id", nullable = false)
|
||||||
@JsonIgnore
|
@JsonIgnore
|
||||||
private Member member;
|
private Member member;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 속한 이미지
|
||||||
|
*/
|
||||||
|
@ManyToOne(fetch = FetchType.LAZY)
|
||||||
|
@JoinColumn(name = "image_id", nullable = false)
|
||||||
|
private Image image;
|
||||||
|
|
||||||
|
public Comment(final String content, final double positionX, final double positionY, final Member member, final Image image) {
|
||||||
|
this.content = content;
|
||||||
|
this.positionX = positionX;
|
||||||
|
this.positionY = positionY;
|
||||||
|
this.member = member;
|
||||||
|
this.image = image;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Comment of(final String content, final double positionX, final double positionY, final Member member, final Image image) {
|
||||||
|
return new Comment(content, positionX, positionY, member, image);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void update(final String content, final double positionX, final double positionY) {
|
||||||
|
this.content = content;
|
||||||
|
this.positionX = positionX;
|
||||||
|
this.positionY = positionY;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,28 @@
|
|||||||
|
package com.worlabel.domain.comment.entity.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import jakarta.validation.constraints.NotEmpty;
|
||||||
|
import jakarta.validation.constraints.NotNull;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
|
@Schema(name = "댓글 요청 dto", description = "댓글 요청 DTO")
|
||||||
|
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
@AllArgsConstructor
|
||||||
|
@Getter
|
||||||
|
public class CommentRequest {
|
||||||
|
|
||||||
|
@Schema(description = "댓글 내용", example = "여기 부분 더 상세하게 나타내야해요")
|
||||||
|
@NotEmpty(message = "내용을 입력하세요.")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "X 좌표", example = "3.1462")
|
||||||
|
@NotNull(message = "x좌표를 입력해주세요.")
|
||||||
|
private double positionX;
|
||||||
|
|
||||||
|
@Schema(description = "Y 좌표", example = "7.1462")
|
||||||
|
@NotNull(message = "y좌표를 입력해주세요.")
|
||||||
|
private double positionY;
|
||||||
|
}
|
@ -0,0 +1,49 @@
|
|||||||
|
package com.worlabel.domain.comment.entity.dto;
|
||||||
|
|
||||||
|
import com.worlabel.domain.comment.entity.Comment;
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
|
||||||
|
@Schema(name = "댓글 응답 DTO", description = "댓글 조회 응답 DTO")
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class CommentResponse {
|
||||||
|
|
||||||
|
@Schema(description = "댓글 ID", example = "1")
|
||||||
|
private Integer id;
|
||||||
|
|
||||||
|
@Schema(description = "작성자 ID", example = "1")
|
||||||
|
private Integer memberId;
|
||||||
|
|
||||||
|
@Schema(description = "작성자 닉네임", example = "javajoha")
|
||||||
|
private String memberNickname;
|
||||||
|
|
||||||
|
@Schema(description = "작성자 프로필", example = "profile.jpg")
|
||||||
|
private String memberProfileImage;
|
||||||
|
|
||||||
|
@Schema(description = "y좌표", example = "3.16324")
|
||||||
|
private double positionY;
|
||||||
|
|
||||||
|
@Schema(description = "x좌표", example = "7.16324")
|
||||||
|
private double positionX;
|
||||||
|
|
||||||
|
@Schema(description = "댓글 내용", example = "이 부분 더 자세하게 표현해주세요")
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
@Schema(description = "작성 일자", example = "2024-09-09T14:47:45")
|
||||||
|
private LocalDateTime createTime;
|
||||||
|
|
||||||
|
public static CommentResponse from(final Comment comment) {
|
||||||
|
return new CommentResponse(comment.getId(),
|
||||||
|
comment.getMember().getId(),
|
||||||
|
comment.getMember().getNickname(),
|
||||||
|
comment.getMember().getProfileImage(),
|
||||||
|
comment.getPositionY(),
|
||||||
|
comment.getPositionX(),
|
||||||
|
comment.getContent(),
|
||||||
|
comment.getCreatedAt());
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,21 @@
|
|||||||
|
package com.worlabel.domain.comment.entity.dto;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
import lombok.AccessLevel;
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Getter;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Schema(name = "댓글 목록 응답 dto", description = "댓글 목록 응답 DTO")
|
||||||
|
@Getter
|
||||||
|
@AllArgsConstructor(access = AccessLevel.PRIVATE)
|
||||||
|
public class CommentResponses {
|
||||||
|
|
||||||
|
@Schema(description = "댓글 목록", example = "")
|
||||||
|
private List<CommentResponse> commentResponses;
|
||||||
|
|
||||||
|
public static CommentResponses from(final List<CommentResponse> commentResponses) {
|
||||||
|
return new CommentResponses(commentResponses);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,16 @@
|
|||||||
|
package com.worlabel.domain.comment.repository;
|
||||||
|
|
||||||
|
import com.worlabel.domain.comment.entity.Comment;
|
||||||
|
import org.springframework.data.jpa.repository.JpaRepository;
|
||||||
|
import org.springframework.stereotype.Repository;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface CommentRepository extends JpaRepository<Comment, Integer> {
|
||||||
|
|
||||||
|
List<Comment> findByImageId(Long image_id);
|
||||||
|
|
||||||
|
Optional<Comment> findByIdAndMemberId(Integer commentId, Integer memberId);
|
||||||
|
}
|
@ -0,0 +1,95 @@
|
|||||||
|
package com.worlabel.domain.comment.service;
|
||||||
|
|
||||||
|
import com.worlabel.domain.comment.entity.Comment;
|
||||||
|
import com.worlabel.domain.comment.entity.dto.CommentRequest;
|
||||||
|
import com.worlabel.domain.comment.entity.dto.CommentResponse;
|
||||||
|
import com.worlabel.domain.comment.repository.CommentRepository;
|
||||||
|
import com.worlabel.domain.image.entity.Image;
|
||||||
|
import com.worlabel.domain.image.repository.ImageRepository;
|
||||||
|
import com.worlabel.domain.member.entity.Member;
|
||||||
|
import com.worlabel.domain.member.repository.MemberRepository;
|
||||||
|
import com.worlabel.domain.participant.repository.ParticipantRepository;
|
||||||
|
import com.worlabel.global.exception.CustomException;
|
||||||
|
import com.worlabel.global.exception.ErrorCode;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@Transactional
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class CommentService {
|
||||||
|
|
||||||
|
private final CommentRepository commentRepository;
|
||||||
|
private final ParticipantRepository participantRepository;
|
||||||
|
private final MemberRepository memberRepository;
|
||||||
|
private final ImageRepository imageRepository;
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public List<CommentResponse> getAllComments(final Integer memberId, final Integer projectId, final Long imageId) {
|
||||||
|
checkAuthorized(memberId, projectId);
|
||||||
|
|
||||||
|
return commentRepository.findByImageId(imageId).stream()
|
||||||
|
.map(CommentResponse::from)
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional(readOnly = true)
|
||||||
|
public CommentResponse getCommentById(final Integer memberId, final Integer projectId, final Integer commentId) {
|
||||||
|
checkAuthorized(memberId, projectId);
|
||||||
|
Comment comment = getComment(commentId);
|
||||||
|
|
||||||
|
return CommentResponse.from(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommentResponse createComment(final CommentRequest commentRequest, Integer memberId, final Integer projectId, final Long imageId) {
|
||||||
|
checkAuthorized(memberId, projectId);
|
||||||
|
Member member = getMember(memberId);
|
||||||
|
Image image = getImage(imageId);
|
||||||
|
|
||||||
|
Comment comment = Comment.of(commentRequest.getContent(), commentRequest.getPositionX(), commentRequest.getPositionY(), member, image);
|
||||||
|
comment = commentRepository.save(comment);
|
||||||
|
|
||||||
|
return CommentResponse.from(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public CommentResponse updateComment(final CommentRequest commentRequest, final Integer memberId, final Integer projectId, final Integer commentId) {
|
||||||
|
Comment comment = getCommentWithMemberId(commentId, memberId);
|
||||||
|
comment.update(commentRequest.getContent(), commentRequest.getPositionX(), commentRequest.getPositionY());
|
||||||
|
|
||||||
|
return CommentResponse.from(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void deleteComment(final Integer memberId, final Integer projectId, final Integer commentId) {
|
||||||
|
Comment comment = getCommentWithMemberId(commentId, memberId);
|
||||||
|
commentRepository.delete(comment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void checkAuthorized(final Integer memberId, final Integer projectId) {
|
||||||
|
if (!participantRepository.existsByMemberIdAndProjectId(memberId, projectId)) {
|
||||||
|
throw new CustomException(ErrorCode.UNAUTHORIZED);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Comment getComment(final Integer commentId) {
|
||||||
|
return commentRepository.findById(commentId)
|
||||||
|
.orElseThrow(() -> new CustomException(ErrorCode.COMMENT_NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Comment getCommentWithMemberId(final Integer commentId, final Integer memberId) {
|
||||||
|
return commentRepository.findByIdAndMemberId(commentId, memberId)
|
||||||
|
.orElseThrow(() -> new CustomException(ErrorCode.COMMENT_NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Member getMember(final Integer memberId) {
|
||||||
|
return memberRepository.findById(memberId)
|
||||||
|
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
|
||||||
|
}
|
||||||
|
|
||||||
|
private Image getImage(final Long imageId) {
|
||||||
|
return imageRepository.findById(imageId)
|
||||||
|
.orElseThrow(() -> new CustomException(ErrorCode.IMAGE_NOT_FOUND));
|
||||||
|
}
|
||||||
|
}
|
@ -1,13 +1,8 @@
|
|||||||
package com.worlabel.domain.image.controller;
|
package com.worlabel.domain.image.controller;
|
||||||
|
|
||||||
import com.worlabel.domain.folder.entity.dto.FolderRequest;
|
|
||||||
import com.worlabel.domain.folder.entity.dto.FolderResponse;
|
|
||||||
import com.worlabel.domain.folder.service.FolderService;
|
|
||||||
import com.worlabel.domain.image.entity.dto.ImageMoveRequest;
|
import com.worlabel.domain.image.entity.dto.ImageMoveRequest;
|
||||||
import com.worlabel.domain.image.entity.dto.ImageResponse;
|
import com.worlabel.domain.image.entity.dto.ImageResponse;
|
||||||
import com.worlabel.domain.image.service.ImageService;
|
import com.worlabel.domain.image.service.ImageService;
|
||||||
import com.worlabel.domain.project.service.ProjectService;
|
|
||||||
import com.worlabel.domain.workspace.entity.dto.WorkspaceResponse;
|
|
||||||
import com.worlabel.global.annotation.CurrentUser;
|
import com.worlabel.global.annotation.CurrentUser;
|
||||||
import com.worlabel.global.config.swagger.SwaggerApiError;
|
import com.worlabel.global.config.swagger.SwaggerApiError;
|
||||||
import com.worlabel.global.config.swagger.SwaggerApiSuccess;
|
import com.worlabel.global.config.swagger.SwaggerApiSuccess;
|
||||||
@ -24,7 +19,6 @@ import org.springframework.web.multipart.MultipartFile;
|
|||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@RestController
|
@RestController
|
||||||
@RequiredArgsConstructor
|
@RequiredArgsConstructor
|
||||||
@ -95,5 +89,3 @@ public class ImageController {
|
|||||||
return SuccessResponse.empty();
|
return SuccessResponse.empty();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
@ -51,6 +51,9 @@ public enum ErrorCode {
|
|||||||
// AI - 8000
|
// AI - 8000
|
||||||
AI_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 8000, "AI 서버 오류 입니다."),
|
AI_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, 8000, "AI 서버 오류 입니다."),
|
||||||
|
|
||||||
|
// Comment - 9000
|
||||||
|
COMMENT_NOT_FOUND(HttpStatus.NOT_FOUND, 9000, "해당 댓글을 찾을 수 없습니다."),
|
||||||
|
|
||||||
;
|
;
|
||||||
private final HttpStatus status;
|
private final HttpStatus status;
|
||||||
private final int code;
|
private final int code;
|
||||||
|
Loading…
Reference in New Issue
Block a user