Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package clap.server.adapter.inbound.web.comment;

import clap.server.adapter.inbound.security.SecurityUserDetails;
import clap.server.adapter.inbound.web.dto.task.PostAndEditCommentRequest;
import clap.server.application.port.inbound.comment.PostCommentUsecase;
import clap.server.common.annotation.architecture.WebAdapter;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.enums.ParameterIn;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.constraints.NotNull;
import lombok.RequiredArgsConstructor;
import org.springframework.security.access.annotation.Secured;
import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

@Tag(name = "02. Task", description = "작업 생성/수정 API")
@WebAdapter
@RestController
@RequiredArgsConstructor
@RequestMapping("/api/comment")
public class PostCommentController {

private final PostCommentUsecase postCommentUsecase;

@Operation(summary = "댓글 작성")
@Parameter(name = "labelId", description = "댓글 작성할 작업 고유 ID", required = true, in = ParameterIn.PATH)
@PostMapping("/{taskId}")
@Secured({"ROLE_MANAGER", "ROLE_USER"})
public void createTask(
@AuthenticationPrincipal SecurityUserDetails userInfo,
@PathVariable Long taskId,
@RequestBody(required = true) PostAndEditCommentRequest request){
postCommentUsecase.save(userInfo.getUserId(), taskId, request);
}

@Operation(summary = "댓글 작성(첨부 파일)")
@Parameter(name = "labelId", description = "댓글 작성할 작업 고유 ID", required = true, in = ParameterIn.PATH)
@PostMapping("/attachment/{taskId}")
@Secured({"ROLE_MANAGER", "ROLE_USER"})
public void createAttachmentTask(
@AuthenticationPrincipal SecurityUserDetails userInfo,
@PathVariable Long taskId,
@RequestPart(name = "attachment") @NotNull List<MultipartFile> attachments) {
postCommentUsecase.saveCommentAttachment(userInfo.getUserId(), taskId, attachments);
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package clap.server.adapter.inbound.web.dto.task;


import io.swagger.v3.oas.annotations.media.Schema;

public record PostAndEditCommentRequest(
@Schema(description = "댓글 내용")
String content
) {
}
Original file line number Diff line number Diff line change
@@ -1,13 +1,31 @@
package clap.server.adapter.outbound.persistense;

import clap.server.application.port.outbound.taskhistory.LoadCommentPort;
import clap.server.adapter.outbound.persistense.entity.task.CommentEntity;
import clap.server.adapter.outbound.persistense.mapper.CommentPersistenceMapper;
import clap.server.adapter.outbound.persistense.repository.task.CommentRepository;
import clap.server.application.port.outbound.task.CommandCommentPort;
import clap.server.application.port.outbound.task.LoadCommentPort;
import clap.server.common.annotation.architecture.PersistenceAdapter;
import clap.server.domain.model.task.Comment;
import lombok.RequiredArgsConstructor;

import java.util.Optional;

public class CommentPersistenceAdapter implements LoadCommentPort {
@PersistenceAdapter
@RequiredArgsConstructor
public class CommentPersistenceAdapter implements LoadCommentPort, CommandCommentPort {

private final CommentRepository commentRepository;
private final CommentPersistenceMapper commentPersistenceMapper;

@Override
public Optional<Comment> findById(Long id) {
return Optional.empty();
}

@Override
public Comment saveComment(Comment comment) {
CommentEntity commentEntity = commentRepository.save(commentPersistenceMapper.toEntity(comment));
return commentPersistenceMapper.toDomain(commentEntity);
}
}
15 changes: 15 additions & 0 deletions src/main/java/clap/server/application/mapper/AttachmentMapper.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@

import clap.server.adapter.inbound.web.dto.task.AttachmentResponse;
import clap.server.domain.model.task.Attachment;
import clap.server.domain.model.task.Comment;
import clap.server.domain.model.task.Task;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;
import java.util.stream.IntStream;

import static clap.server.domain.model.task.Attachment.createAttachment;
import static clap.server.domain.model.task.Attachment.createCommentAttachment;

public class AttachmentMapper {
private AttachmentMapper() {
Expand All @@ -26,6 +28,19 @@ public static List<Attachment> toTaskAttachments(Task task, List<MultipartFile>
.toList();
}

public static List<Attachment> toCommentAttachments(Task task, Comment comment, List<MultipartFile> files, List<String> fileUrls) {
return IntStream.range(0, files.size())
.mapToObj(i -> createCommentAttachment(
task,
comment,
files.get(i).getOriginalFilename(),
fileUrls.get(i),
files.get(i).getSize()
))
.toList();
}


public static List<AttachmentResponse> toAttachmentResponseList(List<Attachment> attachments) {
return attachments.stream()
.map(attachment -> new AttachmentResponse(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package clap.server.application.port.inbound.comment;

import clap.server.adapter.inbound.web.dto.task.PostAndEditCommentRequest;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

public interface PostCommentUsecase {

void save(Long userId, Long taskId, PostAndEditCommentRequest request);

void saveCommentAttachment(Long userId, Long taskId, List<MultipartFile> files);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package clap.server.application.port.outbound.task;

import clap.server.domain.model.task.Comment;

public interface CommandCommentPort {

Comment saveComment(Comment comment);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package clap.server.application.port.outbound.task;

import clap.server.domain.model.task.Comment;

import java.util.Optional;

public interface LoadCommentPort {
Optional<Comment> findById(Long commentId);
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,5 +5,5 @@
import java.util.Optional;

public interface CommandCommentPort {
Optional<Comment> save(Comment comment);
void save(Comment comment);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package clap.server.application.service.comment;

import clap.server.adapter.inbound.web.dto.task.PostAndEditCommentRequest;
import clap.server.adapter.outbound.infrastructure.s3.S3UploadAdapter;
import clap.server.adapter.outbound.persistense.entity.member.constant.MemberRole;
import clap.server.adapter.outbound.persistense.entity.task.constant.TaskHistoryType;
import clap.server.application.mapper.AttachmentMapper;
import clap.server.application.port.inbound.comment.PostCommentUsecase;
import clap.server.application.port.inbound.domain.MemberService;
import clap.server.application.port.inbound.domain.TaskService;
import clap.server.application.port.outbound.task.CommandAttachmentPort;
import clap.server.application.port.outbound.task.CommandCommentPort;
import clap.server.application.port.outbound.taskhistory.CommandTaskHistoryPort;
import clap.server.common.annotation.architecture.ApplicationService;
import clap.server.common.constants.FilePathConstants;
import clap.server.domain.model.member.Member;
import clap.server.domain.model.task.Attachment;
import clap.server.domain.model.task.Comment;
import clap.server.domain.model.task.Task;
import clap.server.domain.model.task.TaskHistory;
import clap.server.exception.ApplicationException;
import clap.server.exception.code.MemberErrorCode;
import lombok.RequiredArgsConstructor;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;

import java.util.List;

@ApplicationService
@RequiredArgsConstructor
public class PostCommentService implements PostCommentUsecase {

private final MemberService memberService;
private final TaskService taskService;
private final CommandCommentPort commandCommentPort;
private final S3UploadAdapter s3UploadAdapter;
private final CommandAttachmentPort commandAttachmentPort;
private final CommandTaskHistoryPort commandTaskHistoryPort;

@Transactional
@Override
public void save(Long userId, Long taskId, PostAndEditCommentRequest request) {
Task task = taskService.findById(taskId);
Member member = memberService.findActiveMember(userId);

// 일반 회원일 경우 => 요청자인지 확인
// 담당자일 경우 => 처리자인지 확인
if (checkCommenter(task, member)) {
Comment comment = Comment.createComment(member, task, request.content());
Comment savedComment = commandCommentPort.saveComment(comment);

TaskHistory taskHistory = TaskHistory.createTaskHistory(TaskHistoryType.COMMENT, task, null, member,savedComment);
commandTaskHistoryPort.save(taskHistory);
}
}

@Transactional
@Override
public void saveCommentAttachment(Long userId, Long taskId, List<MultipartFile> files) {
Task task = taskService.findById(taskId);
Member member = memberService.findActiveMember(userId);

if (checkCommenter(task, member)) {
Comment comment = Comment.createComment(member, task, "Attachment");
Comment savedComment = commandCommentPort.saveComment(comment);
saveAttachment(files, task, savedComment);

TaskHistory taskHistory = TaskHistory.createTaskHistory(TaskHistoryType.COMMENT_FILE, task, null, member, savedComment);
commandTaskHistoryPort.save(taskHistory);
}
}

private void saveAttachment(List<MultipartFile> files, Task task, Comment comment) {
List<String> fileUrls = s3UploadAdapter.uploadFiles(FilePathConstants.TASK_IMAGE, files);
List<Attachment> attachments = AttachmentMapper.toCommentAttachments(task, comment, files, fileUrls);
commandAttachmentPort.saveAll(attachments);
}

public Boolean checkCommenter(Task task, Member member) {
// 일반 회원일 경우 => 요청자인지 확인
// 담당자일 경우 => 처리자인지 확인
if ((member.getMemberInfo().getRole() == MemberRole.ROLE_MANAGER)
&& !(member.getMemberId() == task.getProcessor().getMemberId())) {
throw new ApplicationException(MemberErrorCode.NOT_A_COMMENTER);
}

else if ((member.getMemberInfo().getRole() == MemberRole.ROLE_USER)
&& !(member.getMemberId() == task.getRequester().getMemberId())) {
throw new ApplicationException(MemberErrorCode.NOT_A_COMMENTER);
}
else {
return true;
}

}
}
11 changes: 11 additions & 0 deletions src/main/java/clap/server/domain/model/task/Attachment.java
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,17 @@ public static Attachment createAttachment(Task task, String originalName, String
.build();
}

public static Attachment createCommentAttachment(Task task, Comment comment, String originalName, String fileUrl, long fileSize) {
return Attachment.builder()
.task(task)
.comment(comment)
.originalName(originalName)
.fileUrl(fileUrl)
.fileSize(formatFileSize(fileSize))
.isDeleted(false)
.build();
}

public void softDelete() {
this.isDeleted = true;
}
Expand Down
9 changes: 9 additions & 0 deletions src/main/java/clap/server/domain/model/task/Comment.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package clap.server.domain.model.task;

import clap.server.adapter.inbound.web.dto.task.PostAndEditCommentRequest;
import clap.server.domain.model.common.BaseTime;
import clap.server.domain.model.member.Member;
import lombok.AccessLevel;
Expand All @@ -17,4 +18,12 @@ public class Comment extends BaseTime {
private String content;
private boolean isModified;

public static Comment createComment(Member member, Task task, String content) {
return Comment.builder()
.member(member)
.task(task)
.content(content)
.isModified(false)
.build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ public enum MemberErrorCode implements BaseErrorCode {
MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBER_001", "회원을 찾을 수 없습니다."),
ACTIVE_MEMBER_NOT_FOUND(HttpStatus.NOT_FOUND, "MEMBER_002", "활성화 회원을 찾을 수 없습니다."),
NOT_A_REVIEWER(HttpStatus.FORBIDDEN, "MEMBER_003", "리뷰어 권한이 없습니다."),
MEMBER_REGISTER_FAILED(HttpStatus.BAD_REQUEST, "MEMBER_004", "회원 등록에 실패하였습니다");
MEMBER_REGISTER_FAILED(HttpStatus.BAD_REQUEST, "MEMBER_004", "회원 등록에 실패하였습니다"),
NOT_A_COMMENTER(HttpStatus.FORBIDDEN, "MEMBER_005", "댓글 작성 권한이 없습니다.")
;

private final HttpStatus httpStatus;
Expand Down