diff --git a/be/issue/src/main/java/codesquad/issueTracker/global/exception/ErrorCode.java b/be/issue/src/main/java/codesquad/issueTracker/global/exception/ErrorCode.java index 2a516eb34..ad90994f4 100644 --- a/be/issue/src/main/java/codesquad/issueTracker/global/exception/ErrorCode.java +++ b/be/issue/src/main/java/codesquad/issueTracker/global/exception/ErrorCode.java @@ -37,7 +37,7 @@ public enum ErrorCode implements StatusCode { ALREADY_DELETED_COMMENT(HttpStatus.BAD_REQUEST, "이미 삭제된 댓글입니다."), FAILED_CREATE_COMMENT(HttpStatus.SERVICE_UNAVAILABLE, "DB 서버 오류로 인해 댓글 생성에 실패했습니다."), FAILED_UPDATE_COMMENT(HttpStatus.SERVICE_UNAVAILABLE, "DB 서버 오류로 인해 댓글 수정에 실패했습니다."), - FAILED_DELETE_COMMENT(HttpStatus.SERVICE_UNAVAILABLE,"DB 서버 오류로 인해 댓글 삭제에 실패했습니다."), + FAILED_DELETE_COMMENT(HttpStatus.SERVICE_UNAVAILABLE, "DB 서버 오류로 인해 댓글 삭제에 실패했습니다."), // -- [Milestone] -- // INVALIDATE_DATE(HttpStatus.BAD_REQUEST, "현재 날짜보다 이전 날짜 입니다."), @@ -51,10 +51,9 @@ public enum ErrorCode implements StatusCode { LABEL_DELETE_FAILED(HttpStatus.BAD_REQUEST, "DB에서 라벨 삭제에 실패했습니다"), LABEL_FIND_FAILED(HttpStatus.BAD_REQUEST, "서버 오류로 라벨을 조회할 수 없습니다"), - // -- [Issue] -- // - DUPLICATE_OBJECT_FOUND(HttpStatus.BAD_REQUEST, "중복된 항목 선택입니다."); - + DUPLICATE_OBJECT_FOUND(HttpStatus.BAD_REQUEST, "중복된 항목 선택입니다."), + NOT_FOUND_ISSUES(HttpStatus.BAD_REQUEST, "이슈를 찾을 수 없습니다."); private HttpStatus status; private String message; diff --git a/be/issue/src/main/java/codesquad/issueTracker/issue/controller/IssueController.java b/be/issue/src/main/java/codesquad/issueTracker/issue/controller/IssueController.java index 4cf9203c8..8d5868201 100644 --- a/be/issue/src/main/java/codesquad/issueTracker/issue/controller/IssueController.java +++ b/be/issue/src/main/java/codesquad/issueTracker/issue/controller/IssueController.java @@ -5,6 +5,9 @@ import javax.servlet.http.HttpServletRequest; import javax.validation.Valid; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PatchMapping; +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; @@ -12,6 +15,14 @@ import codesquad.issueTracker.global.common.ApiResponse; import codesquad.issueTracker.issue.dto.IssueWriteRequestDto; +import codesquad.issueTracker.issue.dto.ModifyAssigneeRequestDto; +import codesquad.issueTracker.issue.dto.ModifyIssueContentRequestDto; +import codesquad.issueTracker.issue.dto.ModifyIssueContentResponseDto; +import codesquad.issueTracker.issue.dto.ModifyIssueMilestoneDto; +import codesquad.issueTracker.issue.dto.ModifyIssueStatusRequestDto; +import codesquad.issueTracker.issue.dto.ModifyIssueTitleRequest; +import codesquad.issueTracker.issue.dto.ModifyIssueTitleResponse; +import codesquad.issueTracker.issue.dto.ModifyLabelRequestDto; import codesquad.issueTracker.issue.service.IssueService; import lombok.RequiredArgsConstructor; @@ -29,4 +40,55 @@ public ApiResponse postIssues(@Valid @RequestBody IssueWriteRequestDto r issueService.save(request, id); return ApiResponse.success(SUCCESS.getStatus(), SUCCESS.getMessage()); } + + @PatchMapping("/issues/status") + public ApiResponse patchStatus(@RequestBody ModifyIssueStatusRequestDto request) { + issueService.modifyIssueStatus(request); + return ApiResponse.success(SUCCESS.getStatus(), SUCCESS.getMessage()); + } + + @PatchMapping("/issues/{id}/status") + public ApiResponse patchInDetailStatus(@PathVariable Long id, + @RequestBody ModifyIssueStatusRequestDto request) { + issueService.modifyIssueStatusInDetail(id, request); + return ApiResponse.success(SUCCESS.getStatus(), SUCCESS.getMessage()); + } + + @PatchMapping("/issues/{id}/content") + public ApiResponse patchContent(@PathVariable Long id, + @RequestBody ModifyIssueContentRequestDto request) { + ModifyIssueContentResponseDto response = issueService.modifyIssueContent(id, request); + return ApiResponse.success(SUCCESS.getStatus(), response); + } + + @PatchMapping("/issues/{id}/title") + public ApiResponse patchTitle(@PathVariable Long id, + @Valid @RequestBody ModifyIssueTitleRequest request) { + ModifyIssueTitleResponse response = issueService.modifyIssueTitle(id, request); + return ApiResponse.success(SUCCESS.getStatus(), response); + } + + @DeleteMapping("/issues/{id}") + public ApiResponse deleteIssues(@PathVariable Long id) { + issueService.delete(id); + return ApiResponse.success(SUCCESS.getStatus(), SUCCESS.getMessage()); + } + + @PatchMapping("/issues/{id}/assignees") + public ApiResponse patchAssignees(@PathVariable Long id, @RequestBody ModifyAssigneeRequestDto request) { + issueService.modifyAssignees(id, request); + return ApiResponse.success(SUCCESS.getStatus(), SUCCESS.getMessage()); + } + + @PatchMapping("/issues/{id}/labels") + public ApiResponse patchLabels(@PathVariable Long id, @RequestBody ModifyLabelRequestDto request) { + issueService.modifyLabels(id, request); + return ApiResponse.success(SUCCESS.getStatus(), SUCCESS.getMessage()); + } + + @PatchMapping("/issues/{id}/milestones") + public ApiResponse patchMilestone(@PathVariable Long id, @RequestBody ModifyIssueMilestoneDto request) { + issueService.modifyMilestone(id, request); + return ApiResponse.success(SUCCESS.getStatus(), SUCCESS.getMessage()); + } } diff --git a/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyAssigneeRequestDto.java b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyAssigneeRequestDto.java new file mode 100644 index 000000000..31769493e --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyAssigneeRequestDto.java @@ -0,0 +1,10 @@ +package codesquad.issueTracker.issue.dto; + +import java.util.List; + +import lombok.Getter; + +@Getter +public class ModifyAssigneeRequestDto { + private List assignees; +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueContentRequestDto.java b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueContentRequestDto.java new file mode 100644 index 000000000..13a4c0d1f --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueContentRequestDto.java @@ -0,0 +1,12 @@ +package codesquad.issueTracker.issue.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class ModifyIssueContentRequestDto { + private String content; +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueContentResponseDto.java b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueContentResponseDto.java new file mode 100644 index 000000000..66cf5b013 --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueContentResponseDto.java @@ -0,0 +1,10 @@ +package codesquad.issueTracker.issue.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ModifyIssueContentResponseDto { + private String modifiedContent; +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueMilestoneDto.java b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueMilestoneDto.java new file mode 100644 index 000000000..f08986eb2 --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueMilestoneDto.java @@ -0,0 +1,8 @@ +package codesquad.issueTracker.issue.dto; + +import lombok.Getter; + +@Getter +public class ModifyIssueMilestoneDto { + private Long milestone; +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueStatusRequestDto.java b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueStatusRequestDto.java new file mode 100644 index 000000000..944bfd7e0 --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueStatusRequestDto.java @@ -0,0 +1,14 @@ +package codesquad.issueTracker.issue.dto; + +import java.util.List; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ModifyIssueStatusRequestDto { + private String status; + private List issueIds; + +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueTitleRequest.java b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueTitleRequest.java new file mode 100644 index 000000000..b5087285f --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueTitleRequest.java @@ -0,0 +1,17 @@ +package codesquad.issueTracker.issue.dto; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@AllArgsConstructor +@NoArgsConstructor +public class ModifyIssueTitleRequest { + @NotNull(message = "제목을 입력해주세요.") + @NotBlank(message = "공백은 입력할 수 없습니다.") + private String title; +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueTitleResponse.java b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueTitleResponse.java new file mode 100644 index 000000000..90f93ee5c --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyIssueTitleResponse.java @@ -0,0 +1,10 @@ +package codesquad.issueTracker.issue.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ModifyIssueTitleResponse { + private String modifiedTitle; +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyLabelRequestDto.java b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyLabelRequestDto.java new file mode 100644 index 000000000..a57f4559b --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/ModifyLabelRequestDto.java @@ -0,0 +1,11 @@ +package codesquad.issueTracker.issue.dto; + +import java.util.List; + +import lombok.Getter; + +@Getter +public class ModifyLabelRequestDto { + private List labels; + +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/issue/repository/IssueRepository.java b/be/issue/src/main/java/codesquad/issueTracker/issue/repository/IssueRepository.java index 13a9c25fd..f54861999 100644 --- a/be/issue/src/main/java/codesquad/issueTracker/issue/repository/IssueRepository.java +++ b/be/issue/src/main/java/codesquad/issueTracker/issue/repository/IssueRepository.java @@ -1,6 +1,12 @@ package codesquad.issueTracker.issue.repository; +import java.time.LocalDateTime; +import java.util.Map; +import java.util.Optional; + +import org.springframework.dao.support.DataAccessUtils; import org.springframework.jdbc.core.JdbcTemplate; +import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; import org.springframework.jdbc.core.namedparam.SqlParameterSource; @@ -49,4 +55,74 @@ public Long insertAssignees(Long issueId, Long userId) { jdbcTemplate.update(sql, parameters, keyHolder); return keyHolder.getKey().longValue(); } + + public Optional findById(Long id) { + String sql = "SELECT id, milestone_id, user_id, title, content, created_at, is_closed FROM issues WHERE id = :id AND is_deleted = 0"; + return Optional.ofNullable( + DataAccessUtils.singleResult( + jdbcTemplate.query(sql, Map.of("id", id), issueRowMapper))); + } + + public Long modifyStatus(Long issueId, Boolean status) { + String sql = "UPDATE issues SET is_closed = :status where id = :id AND is_deleted = 0"; + SqlParameterSource parameterSource = new MapSqlParameterSource() + .addValue("id", issueId) + .addValue("status", status); + jdbcTemplate.update(sql, parameterSource); + return issueId; + } + + private final RowMapper issueRowMapper = (rs, rowNum) -> Issue.builder() + .id(rs.getLong("id")) + .milestoneId(rs.getLong("milestone_id")) + .userId(rs.getLong("user_id")) + .title(rs.getString("title")) + .content(rs.getString("content")) + .createdAt(rs.getObject("created_at", LocalDateTime.class)) + .isClosed(rs.getBoolean("is_closed")) + .build(); + + public Long updateContent(Long id, String modifiedContent) { + String sql = "UPDATE issues SET content = :modifiedContent WHERE Id = :id"; + SqlParameterSource parameterSource = new MapSqlParameterSource() + .addValue("id", id) + .addValue("modifiedContent", modifiedContent); + jdbcTemplate.update(sql, parameterSource); + return id; + + } + + public Long updateTitle(Long id, String modifiedTitle) { + String sql = "UPDATE issues SET title = :modifiedTitle WHERE Id = :id"; + SqlParameterSource parameterSource = new MapSqlParameterSource() + .addValue("id", id) + .addValue("modifiedTitle", modifiedTitle); + jdbcTemplate.update(sql, parameterSource); + return id; + } + + public Long delete(Long id) { + String sql = "UPDATE issues SET is_deleted = 1 where id = :id"; + SqlParameterSource parameters = new MapSqlParameterSource() + .addValue("id", id); + jdbcTemplate.update(sql, parameters); + return id; + } + + public Long resetAssignees(Long issueId) { + String sql = "DELETE FROM assignees WHERE issue_id = :issueId"; + SqlParameterSource parameterSource = new MapSqlParameterSource() + .addValue("issueId", issueId); + jdbcTemplate.update(sql, parameterSource); + return issueId; + } + + public Long updateMilestone(Long issueId, Long milestoneId) { + String sql = "UPDATE issues SET milestone_id = :milestoneId WHERE id = :issueId "; + SqlParameterSource parameterSource = new MapSqlParameterSource() + .addValue("issueId", issueId) + .addValue("milestoneId", milestoneId); + jdbcTemplate.update(sql, parameterSource); + return issueId; + } } diff --git a/be/issue/src/main/java/codesquad/issueTracker/issue/service/IssueService.java b/be/issue/src/main/java/codesquad/issueTracker/issue/service/IssueService.java index 8a6dd0bfb..7b1f7a06a 100644 --- a/be/issue/src/main/java/codesquad/issueTracker/issue/service/IssueService.java +++ b/be/issue/src/main/java/codesquad/issueTracker/issue/service/IssueService.java @@ -9,10 +9,19 @@ import org.springframework.transaction.annotation.Transactional; import codesquad.issueTracker.comment.service.CommentService; +import codesquad.issueTracker.global.common.Status; import codesquad.issueTracker.global.exception.CustomException; import codesquad.issueTracker.global.exception.ErrorCode; import codesquad.issueTracker.issue.domain.Issue; import codesquad.issueTracker.issue.dto.IssueWriteRequestDto; +import codesquad.issueTracker.issue.dto.ModifyAssigneeRequestDto; +import codesquad.issueTracker.issue.dto.ModifyIssueContentRequestDto; +import codesquad.issueTracker.issue.dto.ModifyIssueContentResponseDto; +import codesquad.issueTracker.issue.dto.ModifyIssueMilestoneDto; +import codesquad.issueTracker.issue.dto.ModifyIssueStatusRequestDto; +import codesquad.issueTracker.issue.dto.ModifyIssueTitleRequest; +import codesquad.issueTracker.issue.dto.ModifyIssueTitleResponse; +import codesquad.issueTracker.issue.dto.ModifyLabelRequestDto; import codesquad.issueTracker.issue.repository.IssueRepository; import codesquad.issueTracker.label.service.LabelService; import codesquad.issueTracker.milestone.service.MilestoneService; @@ -41,18 +50,18 @@ public Long save(IssueWriteRequestDto request, Long id) { // 라벨 리스트가 null 이 아니면 해당 라벨이 존재하는지 검증 후 라벨과 이슈 연결 테이블에 insert if (labels != null) { duplicatedId(labels); - labelService.validateLabelsId(labels); labels.stream() - .map(findLabel -> issueRepository.insertLabels(savedIssueId, findLabel)) + .map(labelId -> labelService.validateLabelsId(labelId)) + .map(existLabel -> issueRepository.insertLabels(savedIssueId, existLabel.getId())) .collect(Collectors.toList()); } // assignee 리스트가 null 이 아니면 assignees( 유저 id )가 존재하는지 검증 후 assignees 테이블에 insert if (assignees != null) { duplicatedId(assignees); - userService.validateUserIds(assignees); assignees.stream() - .map(findUser -> issueRepository.insertAssignees(savedIssueId, findUser)) + .map(assigneesId -> userService.validateUserId(assigneesId)) + .map(existUser -> issueRepository.insertAssignees(savedIssueId, existUser.getId())) .collect(Collectors.toList()); } return savedIssueId; @@ -66,4 +75,106 @@ private void duplicatedId(List list) { } } } + + @Transactional + public List modifyIssueStatus(ModifyIssueStatusRequestDto request) { + List issueIds = request.getIssueIds(); + Boolean status = Status.from(request.getStatus()).getStatus(); + if (issueIds != null) { + duplicatedId(issueIds); + issueIds.stream() + .map(issueId -> validateExistIssue(issueId)) + .map(existIssue -> issueRepository.modifyStatus(existIssue.getId(), status)) + .collect(Collectors.toList()); + ; + } + return issueIds; + + } + + @Transactional + public Long modifyIssueStatusInDetail(Long id, ModifyIssueStatusRequestDto request) { + Boolean status = Status.from(request.getStatus()).getStatus(); + validateExistIssue(id); + return issueRepository.modifyStatus(id, status); + + } + + private Issue validateExistIssue(Long issuesIds) { + return issueRepository.findById(issuesIds).orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_ISSUES)); + } + + @Transactional + public ModifyIssueContentResponseDto modifyIssueContent(Long id, ModifyIssueContentRequestDto request) { + validateExistIssue(id); + String modifiedContent = request.getContent(); + issueRepository.updateContent(id, modifiedContent); + return new ModifyIssueContentResponseDto(modifiedContent); + } + + @Transactional + public ModifyIssueTitleResponse modifyIssueTitle(Long id, ModifyIssueTitleRequest request) { + validateExistIssue(id); + String modifiedTitle = request.getTitle(); + issueRepository.updateTitle(id, modifiedTitle); + return new ModifyIssueTitleResponse(modifiedTitle); + } + + @Transactional + public Long delete(Long id) { + validateExistIssue(id); + Long deletedId = issueRepository.delete(id); + return deletedId; + + } + + @Transactional + public Long modifyAssignees(Long id, ModifyAssigneeRequestDto request) { + validateExistIssue(id); + List assignees = request.getAssignees(); + + if (assignees != null) { + duplicatedId(assignees); + for (Long assigneeId : assignees) { + userService.validateUserId(assigneeId); + } + issueRepository.resetAssignees(id); + for (Long assigneeId : assignees) { + issueRepository.insertAssignees(id, assigneeId); + } + return id; + } + issueRepository.resetAssignees(id); + return id; + } + + @Transactional + public Long modifyLabels(Long id, ModifyLabelRequestDto request) { + validateExistIssue(id); + List labels = request.getLabels(); + + if (labels != null) { + duplicatedId(labels); + for (Long labelId : labels) { + labelService.validateLabelsId(labelId); + } + labelService.resetLabels(id); + for (Long labelId : labels) { + labelService.insertIssuesLabels(id, labelId); + } + return id; + } + labelService.resetLabels(id); + return id; + } + + @Transactional + public Long modifyMilestone(Long id, ModifyIssueMilestoneDto request) { + validateExistIssue(id); + Long milestoneId = request.getMilestone(); + if (milestoneId != null) { + milestoneService.isExistMilestone(milestoneId); + } + return issueRepository.updateMilestone(id, milestoneId); + } } diff --git a/be/issue/src/main/java/codesquad/issueTracker/label/repository/LabelRepository.java b/be/issue/src/main/java/codesquad/issueTracker/label/repository/LabelRepository.java index 4fe621101..d13e74f01 100644 --- a/be/issue/src/main/java/codesquad/issueTracker/label/repository/LabelRepository.java +++ b/be/issue/src/main/java/codesquad/issueTracker/label/repository/LabelRepository.java @@ -9,6 +9,7 @@ import org.springframework.jdbc.core.RowMapper; import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.core.namedparam.SqlParameterSource; import org.springframework.jdbc.support.GeneratedKeyHolder; import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; @@ -96,4 +97,23 @@ public Optional