diff --git a/src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java b/src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java index be475dd8..6df58ca9 100644 --- a/src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java +++ b/src/main/java/clap/server/adapter/inbound/web/task/ManagementTaskController.java @@ -4,10 +4,13 @@ import clap.server.adapter.inbound.web.dto.task.request.CreateTaskRequest; import clap.server.adapter.inbound.web.dto.task.request.UpdateTaskRequest; import clap.server.adapter.inbound.web.dto.task.response.CreateTaskResponse; -import clap.server.adapter.inbound.web.dto.task.response.UpdateTaskResponse; -import clap.server.application.port.inbound.task.*; +import clap.server.application.port.inbound.task.CreateTaskUsecase; +import clap.server.application.port.inbound.task.UpdateTaskUsecase; import clap.server.common.annotation.architecture.WebAdapter; +import clap.server.exception.AdapterException; +import clap.server.exception.code.TaskErrorCode; import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.media.Schema; import io.swagger.v3.oas.annotations.tags.Tag; import jakarta.validation.Valid; import jakarta.validation.constraints.NotNull; @@ -21,6 +24,8 @@ import java.util.List; +import static clap.server.domain.policy.task.TaskPolicyConstants.TASK_MAX_FILE_COUNT; + @Tag(name = "02. Task [생성/수정]", description = "작업 생성/수정 API") @WebAdapter @@ -37,10 +42,14 @@ public class ManagementTaskController { @Secured({"ROLE_MANAGER", "ROLE_USER"}) public ResponseEntity createTask( @RequestPart(name = "taskInfo") @Valid CreateTaskRequest createTaskRequest, + @Schema(description = "파일은 5개 이하만 업로드 가능합니다.") @RequestPart(name = "attachment", required = false) List attachments, @AuthenticationPrincipal SecurityUserDetails userInfo - ){ - return ResponseEntity.ok(createTaskUsecase.createTask(userInfo.getUserId(), createTaskRequest, attachments)); + ) { + if (attachments != null && attachments.size() > TASK_MAX_FILE_COUNT) { + throw new AdapterException(TaskErrorCode.FILE_COUNT_EXCEEDED); + } + return ResponseEntity.ok(createTaskUsecase.createTask(userInfo.getUserId(), createTaskRequest, attachments)); } @Operation(summary = "작업 수정") @@ -49,8 +58,12 @@ public ResponseEntity createTask( public void updateTask( @PathVariable @NotNull Long taskId, @RequestPart(name = "taskInfo") @Valid UpdateTaskRequest updateTaskRequest, + @Schema(description = "하나의 작업에는 총 5개 이하만 업로드 가능합니다.") @RequestPart(name = "attachment", required = false) List attachments, - @AuthenticationPrincipal SecurityUserDetails userInfo){ + @AuthenticationPrincipal SecurityUserDetails userInfo) { + if (attachments != null && attachments.size() > 5) { + throw new AdapterException(TaskErrorCode.FILE_COUNT_EXCEEDED); + } updateTaskUsecase.updateTask(userInfo.getUserId(), taskId, updateTaskRequest, attachments); } } diff --git a/src/main/java/clap/server/adapter/outbound/infrastructure/s3/FileIDGenerator.java b/src/main/java/clap/server/adapter/outbound/infrastructure/s3/FileIDGenerator.java new file mode 100644 index 00000000..c990d3b9 --- /dev/null +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/s3/FileIDGenerator.java @@ -0,0 +1,17 @@ +package clap.server.adapter.outbound.infrastructure.s3; + +import java.time.LocalDateTime; +import java.time.format.DateTimeFormatter; +import java.util.concurrent.atomic.AtomicInteger; + +public class FileIDGenerator { + private static final DateTimeFormatter formatter = DateTimeFormatter.ofPattern("yyyyMMddHHmmss"); + private static final AtomicInteger sequence = new AtomicInteger(0); + + public static String createFileId() { + LocalDateTime now = LocalDateTime.now(); + String date = now.format(formatter); + int seq = sequence.getAndIncrement() % 1000; // 0부터 999까지 순환 + return String.format("%s-%03d", date, seq); + } +} diff --git a/src/main/java/clap/server/adapter/outbound/infrastructure/s3/S3UploadAdapter.java b/src/main/java/clap/server/adapter/outbound/infrastructure/s3/S3UploadAdapter.java index cb033305..485cc6c5 100644 --- a/src/main/java/clap/server/adapter/outbound/infrastructure/s3/S3UploadAdapter.java +++ b/src/main/java/clap/server/adapter/outbound/infrastructure/s3/S3UploadAdapter.java @@ -3,7 +3,7 @@ import clap.server.application.port.outbound.s3.S3UploadPort; import clap.server.common.annotation.architecture.InfrastructureAdapter; import clap.server.config.s3.KakaoS3Config; -import clap.server.domain.policy.attachment.FilePathPolicy; +import clap.server.domain.policy.attachment.FilePathPolicyConstants; import clap.server.exception.S3Exception; import clap.server.exception.code.FileErrorcode; import lombok.RequiredArgsConstructor; @@ -17,20 +17,20 @@ import java.nio.file.Path; import java.nio.file.StandardCopyOption; import java.util.List; -import java.util.UUID; @Slf4j @InfrastructureAdapter @RequiredArgsConstructor public class S3UploadAdapter implements S3UploadPort { + private final KakaoS3Config kakaoS3Config; private final S3Client s3Client; - public List uploadFiles(FilePathPolicy filePrefix, List multipartFiles) { + public List uploadFiles(FilePathPolicyConstants filePrefix, List multipartFiles) { return multipartFiles.stream().map((file) -> uploadSingleFile(filePrefix, file)).toList(); } - public String uploadSingleFile(FilePathPolicy filePrefix, MultipartFile file) { + public String uploadSingleFile(FilePathPolicyConstants filePrefix, MultipartFile file) { try { Path filePath = getFilePath(file); String objectKey = createObjectKey(filePrefix.getPath(), file.getOriginalFilename()); @@ -62,13 +62,9 @@ private void uploadToS3(String filePath, Path path) { s3Client.putObject(putObjectRequest, path); } - private String createFileId() { - return UUID.randomUUID().toString(); - } - private String createObjectKey(String filepath, String fileName) { - String fileId = createFileId(); - return String.format("%s/%s-%s", filepath, fileId , fileName); + String fileId = FileIDGenerator.createFileId(); + return String.format("%s/%s-%s", filepath, fileId, fileName); } } diff --git a/src/main/java/clap/server/adapter/outbound/persistense/entity/task/TaskEntity.java b/src/main/java/clap/server/adapter/outbound/persistense/entity/task/TaskEntity.java index 53d264d7..d5f465c0 100644 --- a/src/main/java/clap/server/adapter/outbound/persistense/entity/task/TaskEntity.java +++ b/src/main/java/clap/server/adapter/outbound/persistense/entity/task/TaskEntity.java @@ -65,4 +65,7 @@ public class TaskEntity extends BaseTimeEntity { @Column private LocalDateTime finishedAt; + + @Column(nullable = false) + private int attachmentCount; } diff --git a/src/main/java/clap/server/application/port/outbound/s3/S3UploadPort.java b/src/main/java/clap/server/application/port/outbound/s3/S3UploadPort.java index 34fcaa02..ed9d1af4 100644 --- a/src/main/java/clap/server/application/port/outbound/s3/S3UploadPort.java +++ b/src/main/java/clap/server/application/port/outbound/s3/S3UploadPort.java @@ -1,12 +1,12 @@ package clap.server.application.port.outbound.s3; -import clap.server.domain.policy.attachment.FilePathPolicy; +import clap.server.domain.policy.attachment.FilePathPolicyConstants; import org.springframework.web.multipart.MultipartFile; import java.util.List; public interface S3UploadPort { - List uploadFiles(FilePathPolicy filePrefix, List multipartFiles); + List uploadFiles(FilePathPolicyConstants filePrefix, List multipartFiles); - String uploadSingleFile(FilePathPolicy filePrefix, MultipartFile file); + String uploadSingleFile(FilePathPolicyConstants filePrefix, MultipartFile file); } diff --git a/src/main/java/clap/server/application/service/history/PostCommentService.java b/src/main/java/clap/server/application/service/history/PostCommentService.java index f7b138d1..e8897906 100644 --- a/src/main/java/clap/server/application/service/history/PostCommentService.java +++ b/src/main/java/clap/server/application/service/history/PostCommentService.java @@ -19,7 +19,7 @@ import clap.server.domain.model.task.Comment; import clap.server.domain.model.task.Task; import clap.server.domain.model.task.TaskHistory; -import clap.server.domain.policy.attachment.FilePathPolicy; +import clap.server.domain.policy.attachment.FilePathPolicyConstants; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -85,7 +85,7 @@ public void saveCommentAttachment(Long userId, Long taskId, MultipartFile file) } private String saveAttachment(MultipartFile file, Task task, Comment comment) { - String fileUrl = s3UploadPort.uploadSingleFile(FilePathPolicy.TASK_COMMENT, file); + String fileUrl = s3UploadPort.uploadSingleFile(FilePathPolicyConstants.TASK_COMMENT, file); Attachment attachment = Attachment.createCommentAttachment(task, comment, file.getOriginalFilename(), fileUrl, file.getSize()); commandAttachmentPort.save(attachment); return file.getOriginalFilename(); diff --git a/src/main/java/clap/server/application/service/member/UpdateMemberInfoService.java b/src/main/java/clap/server/application/service/member/UpdateMemberInfoService.java index 50446cdf..a864fb52 100644 --- a/src/main/java/clap/server/application/service/member/UpdateMemberInfoService.java +++ b/src/main/java/clap/server/application/service/member/UpdateMemberInfoService.java @@ -7,7 +7,7 @@ import clap.server.application.port.outbound.s3.S3UploadPort; import clap.server.common.annotation.architecture.ApplicationService; import clap.server.domain.model.member.Member; -import clap.server.domain.policy.attachment.FilePathPolicy; +import clap.server.domain.policy.attachment.FilePathPolicyConstants; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; import org.springframework.web.multipart.MultipartFile; @@ -25,7 +25,7 @@ class UpdateMemberInfoService implements UpdateMemberInfoUsecase { @Override public void updateMemberInfo(Long memberId, UpdateMemberInfoRequest request, MultipartFile profileImage) throws IOException { Member member = memberService.findActiveMember(memberId); - String profileImageUrl = profileImage != null ? s3UploadPort.uploadSingleFile(FilePathPolicy.MEMBER_IMAGE, profileImage) : null; + String profileImageUrl = profileImage != null ? s3UploadPort.uploadSingleFile(FilePathPolicyConstants.MEMBER_IMAGE, profileImage) : null; member.updateMemberInfo(request.name(), request.agitNotification(), request.emailNotification(), request.kakaoWorkNotification(), profileImageUrl); commandMemberPort.save(member); diff --git a/src/main/java/clap/server/application/service/task/CreateTaskService.java b/src/main/java/clap/server/application/service/task/CreateTaskService.java index 3601aed0..8ec330e8 100644 --- a/src/main/java/clap/server/application/service/task/CreateTaskService.java +++ b/src/main/java/clap/server/application/service/task/CreateTaskService.java @@ -15,7 +15,7 @@ import clap.server.application.port.outbound.task.CommandTaskPort; import clap.server.application.service.webhook.SendNotificationService; import clap.server.common.annotation.architecture.ApplicationService; -import clap.server.domain.policy.attachment.FilePathPolicy; +import clap.server.domain.policy.attachment.FilePathPolicyConstants; import clap.server.domain.model.member.Member; import clap.server.domain.model.task.Attachment; import clap.server.domain.model.task.Category; @@ -43,21 +43,26 @@ public class CreateTaskService implements CreateTaskUsecase { public CreateTaskResponse createTask(Long requesterId, CreateTaskRequest createTaskRequest, List files) { Member member = memberService.findActiveMember(requesterId); Category category = categoryService.findById(createTaskRequest.categoryId()); - Task task = Task.createTask(member, category, createTaskRequest.title(), createTaskRequest.description()); + + int fileSize = files == null ? 0 : files.size(); + Task task = Task.createTask(member, category, createTaskRequest.title(), createTaskRequest.description(), fileSize); Task savedTask = commandTaskPort.save(task); - savedTask.setInitialProcessorOrder(); - commandTaskPort.save(savedTask); if (files != null) { - saveAttachments(files, savedTask);} + fileSize = saveAttachments(files, savedTask); + } + savedTask.finalSave(fileSize); + commandTaskPort.save(savedTask); + publishNotification(savedTask); return TaskResponseMapper.toCreateTaskResponse(savedTask); } - private void saveAttachments(List files, Task task) { - List fileUrls = s3UploadPort.uploadFiles(FilePathPolicy.TASK_IMAGE, files); + private int saveAttachments(List files, Task task) { + List fileUrls = s3UploadPort.uploadFiles(FilePathPolicyConstants.TASK_FILE, files); List attachments = AttachmentMapper.toTaskAttachments(task, files, fileUrls); commandAttachmentPort.saveAll(attachments); + return fileUrls.size(); } private void publishNotification(Task task) { @@ -65,7 +70,8 @@ private void publishNotification(Task task) { reviewers.forEach(reviewer -> { boolean isManager = reviewer.getMemberInfo().getRole() == MemberRole.ROLE_MANAGER; sendNotificationService.sendPushNotification(reviewer, NotificationType.TASK_REQUESTED, - task, null, null, isManager);}); + task, null, null, isManager); + }); sendNotificationService.sendAgitNotification(NotificationType.TASK_REQUESTED, task, null, null); diff --git a/src/main/java/clap/server/application/service/task/UpdateTaskService.java b/src/main/java/clap/server/application/service/task/UpdateTaskService.java index 358ed8cf..688f67b3 100644 --- a/src/main/java/clap/server/application/service/task/UpdateTaskService.java +++ b/src/main/java/clap/server/application/service/task/UpdateTaskService.java @@ -24,7 +24,7 @@ import clap.server.common.annotation.architecture.ApplicationService; import clap.server.domain.model.member.Member; import clap.server.domain.model.task.*; -import clap.server.domain.policy.attachment.FilePathPolicy; +import clap.server.domain.policy.attachment.FilePathPolicyConstants; import clap.server.exception.ApplicationException; import clap.server.exception.code.TaskErrorCode; import lombok.RequiredArgsConstructor; @@ -34,6 +34,7 @@ import java.util.List; +import static clap.server.domain.policy.task.TaskPolicyConstants.TASK_MAX_FILE_COUNT; import static clap.server.domain.policy.task.TaskPolicyConstants.TASK_UPDATABLE_STATUS; @@ -55,17 +56,21 @@ public class UpdateTaskService implements UpdateTaskUsecase, UpdateTaskStatusUse @Override @Transactional - public void updateTask(Long requesterId, Long taskId, UpdateTaskRequest updateTaskRequest, List files) { + public void updateTask(Long requesterId, Long taskId, UpdateTaskRequest request, List files) { memberService.findActiveMember(requesterId); - Category category = categoryService.findById(updateTaskRequest.categoryId()); + Category category = categoryService.findById(request.categoryId()); Task task = taskService.findById(taskId); - task.updateTask(requesterId, category, updateTaskRequest.title(), updateTaskRequest.description()); - taskService.upsert(task); - - if (!updateTaskRequest.attachmentsToDelete().isEmpty()) { - updateAttachments(updateTaskRequest.attachmentsToDelete(), files, task); + int attachmentToAdd = files==null? 0 : files.size(); + int attachmentCount = task.getAttachmentCount() - request.attachmentsToDelete().size() + attachmentToAdd; + if (attachmentCount > TASK_MAX_FILE_COUNT) { + throw new ApplicationException(TaskErrorCode.FILE_COUNT_EXCEEDED); + } + if (!request.attachmentsToDelete().isEmpty()) { + updateAttachments(request.attachmentsToDelete(), files, task); } + task.updateTask(requesterId, category, request.title(), request.description(), attachmentCount); + taskService.upsert(task); } @Override @@ -75,14 +80,14 @@ public void updateTaskStatus(Long memberId, Long taskId, TaskStatus taskStatus) memberService.findReviewer(memberId); Task task = taskService.findById(taskId); - if(!TASK_UPDATABLE_STATUS.contains(taskStatus)){ + if (!TASK_UPDATABLE_STATUS.contains(taskStatus)) { throw new ApplicationException(TaskErrorCode.TASK_STATUS_NOT_ALLOWED); } - if(!task.getTaskStatus().equals(taskStatus)){ + if (!task.getTaskStatus().equals(taskStatus)) { task.updateTaskStatus(taskStatus); Task updateTask = taskService.upsert(task); - TaskHistory taskHistory = TaskHistory.createTaskHistory(TaskHistoryType.STATUS_SWITCHED, task, taskStatus.getDescription(), null,null); + TaskHistory taskHistory = TaskHistory.createTaskHistory(TaskHistoryType.STATUS_SWITCHED, task, taskStatus.getDescription(), null, null); commandTaskHistoryPort.save(taskHistory); List receivers = List.of(task.getRequester(), task.getProcessor()); @@ -101,7 +106,7 @@ public void updateTaskProcessor(Long taskId, Long userId, UpdateTaskProcessorReq task.updateProcessor(processor); Task updateTask = taskService.upsert(task); - TaskHistory taskHistory = TaskHistory.createTaskHistory(TaskHistoryType.PROCESSOR_CHANGED, task, null, processor,null); + TaskHistory taskHistory = TaskHistory.createTaskHistory(TaskHistoryType.PROCESSOR_CHANGED, task, null, processor, null); commandTaskHistoryPort.save(taskHistory); List receivers = List.of(updateTask.getRequester(), updateTask.getProcessor()); @@ -122,13 +127,16 @@ public void updateTaskLabel(Long taskId, Long userId, UpdateTaskLabelRequest req private void updateAttachments(List attachmentIdsToDelete, List files, Task task) { List attachmentsToDelete = validateAndGetAttachments(attachmentIdsToDelete, task); - attachmentsToDelete.forEach(Attachment::softDelete); + attachmentsToDelete.stream() + .peek(Attachment::softDelete) + .forEach(commandAttachmentPort::save); if (files != null) { - List fileUrls = s3UploadPort.uploadFiles(FilePathPolicy.TASK_IMAGE, files); + List fileUrls = s3UploadPort.uploadFiles(FilePathPolicyConstants.TASK_FILE, files); List attachments = AttachmentMapper.toTaskAttachments(task, files, fileUrls); commandAttachmentPort.saveAll(attachments); } + } private List validateAndGetAttachments(List attachmentIdsToDelete, Task task) { diff --git a/src/main/java/clap/server/application/service/webhook/SendNotificationService.java b/src/main/java/clap/server/application/service/webhook/SendNotificationService.java index 473a37b5..ef0c842f 100644 --- a/src/main/java/clap/server/application/service/webhook/SendNotificationService.java +++ b/src/main/java/clap/server/application/service/webhook/SendNotificationService.java @@ -9,6 +9,7 @@ import clap.server.domain.model.task.Task; import lombok.RequiredArgsConstructor; import org.springframework.scheduling.annotation.Async; +import org.springframework.transaction.annotation.Transactional; import java.util.concurrent.CompletableFuture; @@ -75,6 +76,7 @@ public void sendPushNotification(Member receiver, NotificationType notificationT } @Async("notificationExecutor") + @Transactional public void sendAgitNotification(NotificationType notificationType, Task task, String message, String commenterName) { PushNotificationTemplate pushNotificationTemplate = new PushNotificationTemplate( diff --git a/src/main/java/clap/server/domain/model/task/Task.java b/src/main/java/clap/server/domain/model/task/Task.java index bab637dc..ac6f6a47 100644 --- a/src/main/java/clap/server/domain/model/task/Task.java +++ b/src/main/java/clap/server/domain/model/task/Task.java @@ -5,7 +5,6 @@ import clap.server.domain.model.member.Member; import clap.server.exception.ApplicationException; import clap.server.exception.DomainException; -import clap.server.exception.code.TaskErrorCode; import lombok.AccessLevel; import lombok.Getter; import lombok.NoArgsConstructor; @@ -36,8 +35,9 @@ public class Task extends BaseTime { private Member reviewer; private LocalDateTime dueDate; private LocalDateTime finishedAt; + private int attachmentCount; - public static Task createTask(Member member, Category category, String title, String description) { + public static Task createTask(Member member, Category category, String title, String description, int attachmentCount) { return Task.builder() .requester(member) .category(category) @@ -45,10 +45,11 @@ public static Task createTask(Member member, Category category, String title, St .description(description) .taskStatus(TaskStatus.REQUESTED) .taskCode(toTaskCode(category)) + .attachmentCount(attachmentCount) .build(); } - public void updateTask(Long requesterId, Category category, String title, String description) { + public void updateTask(Long requesterId, Category category, String title, String description, int attachmentCount) { if (!Objects.equals(requesterId, this.requester.getMemberId())) { throw new ApplicationException(NOT_A_REQUESTER); } @@ -56,12 +57,14 @@ public void updateTask(Long requesterId, Category category, String title, String this.title = title; this.description = description; this.taskCode = toTaskCode(category); + this.attachmentCount = attachmentCount; } - public void setInitialProcessorOrder() { + public void finalSave(int attachmentCount) { if (this.processor == null) { this.processorOrder = this.taskId * DEFAULT_PROCESSOR_ORDER_GAP; } + this.attachmentCount = attachmentCount; } public void updateTaskStatus(TaskStatus status) { diff --git a/src/main/java/clap/server/domain/policy/attachment/FilePathPolicy.java b/src/main/java/clap/server/domain/policy/attachment/FilePathPolicyConstants.java similarity index 72% rename from src/main/java/clap/server/domain/policy/attachment/FilePathPolicy.java rename to src/main/java/clap/server/domain/policy/attachment/FilePathPolicyConstants.java index 8d15a9b9..ef2e9a55 100644 --- a/src/main/java/clap/server/domain/policy/attachment/FilePathPolicy.java +++ b/src/main/java/clap/server/domain/policy/attachment/FilePathPolicyConstants.java @@ -5,9 +5,8 @@ @Getter @RequiredArgsConstructor -public enum FilePathPolicy { - TASK_IMAGE("task/image"), - TASK_DOCUMENT("task/docs"), +public enum FilePathPolicyConstants { + TASK_FILE("task"), TASK_COMMENT("task/comments"), MEMBER_IMAGE("member"), ; diff --git a/src/main/java/clap/server/domain/policy/task/TaskPolicyConstants.java b/src/main/java/clap/server/domain/policy/task/TaskPolicyConstants.java index 0efbdd4f..e6e38d85 100644 --- a/src/main/java/clap/server/domain/policy/task/TaskPolicyConstants.java +++ b/src/main/java/clap/server/domain/policy/task/TaskPolicyConstants.java @@ -20,4 +20,6 @@ public class TaskPolicyConstants { TaskStatus.IN_REVIEWING, TaskStatus.COMPLETED ); + + public static final int TASK_MAX_FILE_COUNT = 5; } \ No newline at end of file diff --git a/src/main/java/clap/server/exception/code/AuthErrorCode.java b/src/main/java/clap/server/exception/code/AuthErrorCode.java index 9ca50b1f..be69cff0 100644 --- a/src/main/java/clap/server/exception/code/AuthErrorCode.java +++ b/src/main/java/clap/server/exception/code/AuthErrorCode.java @@ -9,7 +9,7 @@ public enum AuthErrorCode implements BaseErrorCode { UNAUTHORIZED(HttpStatus.UNAUTHORIZED, "AUTH_001", "인증 과정에서 오류가 발생하였습니다."), FORBIDDEN(HttpStatus.FORBIDDEN, "AUTH_002", "접근이 거부되었습니다"), - EMPTY_ACCESS_KEY(HttpStatus.FORBIDDEN, "AUTH_003", "AccessToken 이 비어있습니다."), + EMPTY_ACCESS_KEY(HttpStatus.UNAUTHORIZED, "AUTH_003", "AccessToken 이 비어있습니다."), // LOGOUT_ERROR(HttpStatus.FORBIDDEN, "AUTH_004", "로그 아웃된 사용자입니다."), EXPIRED_TOKEN(HttpStatus.FORBIDDEN, "AUTH_005", "사용기간이 만료된 토큰입니다."), TAKEN_AWAY_TOKEN(HttpStatus.FORBIDDEN, "AUTH_006", "탈취당한 토큰입니다. 다시 로그인 해주세요."), diff --git a/src/main/java/clap/server/exception/code/TaskErrorCode.java b/src/main/java/clap/server/exception/code/TaskErrorCode.java index 2b34f604..04b87837 100644 --- a/src/main/java/clap/server/exception/code/TaskErrorCode.java +++ b/src/main/java/clap/server/exception/code/TaskErrorCode.java @@ -17,7 +17,8 @@ public enum TaskErrorCode implements BaseErrorCode { INVALID_TASK_STATUS_TRANSITION(HttpStatus.BAD_REQUEST, "TASK_008", "유효하지 않은 작업 상태 전환입니다."), NOT_A_REVIEWER(HttpStatus.FORBIDDEN, "TASK_009", "작업 승인 및 수정 권한이 없습니다."), NOT_A_REQUESTER(HttpStatus.FORBIDDEN, "TASK_010", "작업 수정 및 취소 권한이 없습니다."), - TASK_STATUS_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "TASK_011", "변경할 수 없는 작업 상태입니다. 다른 API를 사용해주세요") + TASK_STATUS_NOT_ALLOWED(HttpStatus.BAD_REQUEST, "TASK_011", "변경할 수 없는 작업 상태입니다. 다른 API를 사용해주세요"), + FILE_COUNT_EXCEEDED(HttpStatus.BAD_REQUEST, "TASK_012", "파일은 5개 이상 첨부 가능합니다.") ; private final HttpStatus httpStatus; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index d8331c82..69d999b6 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -13,6 +13,10 @@ spring: application: name: taskflow web.resources.add-mappings: false + servlet: + multipart: + max-file-size: 5MB + server: @@ -46,7 +50,7 @@ spring.config.activate.on-profile: local logging: level: root: INFO - taskflow.clap.server: ERROR + taskflow.clap.server: DEBUG # org: # springframework: DEBUG diff --git a/src/main/resources/db/migration/dev/V20250207317__Add_Attachment_Count_Column_From_Task.sql b/src/main/resources/db/migration/dev/V20250207317__Add_Attachment_Count_Column_From_Task.sql new file mode 100644 index 00000000..a6af88de --- /dev/null +++ b/src/main/resources/db/migration/dev/V20250207317__Add_Attachment_Count_Column_From_Task.sql @@ -0,0 +1,11 @@ +ALTER TABLE task ADD COLUMN attachment_count INT NOT NULL DEFAULT 0; + +UPDATE task t +SET attachment_count = ( + SELECT COUNT(*) + FROM attachment a + WHERE a.task_id = t.task_id + AND a.is_deleted = false +); + +ALTER TABLE task MODIFY COLUMN attachment_count INT NOT NULL; \ No newline at end of file