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 43dbc2d3c..2a516eb34 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 @@ -49,7 +49,11 @@ public enum ErrorCode implements StatusCode { LABEL_INSERT_FAILED(HttpStatus.BAD_REQUEST, "DB에서 라벨 생성에 실패했습니다."), LABEL_UPDATE_FAILED(HttpStatus.BAD_REQUEST, "DB에서 라벨 수정에 실패했습니다."), LABEL_DELETE_FAILED(HttpStatus.BAD_REQUEST, "DB에서 라벨 삭제에 실패했습니다"), - LABEL_FIND_FAILED(HttpStatus.BAD_REQUEST, "서버 오류로 라벨을 조회할 수 없습니다"); + LABEL_FIND_FAILED(HttpStatus.BAD_REQUEST, "서버 오류로 라벨을 조회할 수 없습니다"), + + + // -- [Issue] -- // + DUPLICATE_OBJECT_FOUND(HttpStatus.BAD_REQUEST, "중복된 항목 선택입니다."); private HttpStatus status; 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 new file mode 100644 index 000000000..4cf9203c8 --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/issue/controller/IssueController.java @@ -0,0 +1,32 @@ +package codesquad.issueTracker.issue.controller; + +import static codesquad.issueTracker.global.exception.SuccessCode.*; + +import javax.servlet.http.HttpServletRequest; +import javax.validation.Valid; + +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import codesquad.issueTracker.global.common.ApiResponse; +import codesquad.issueTracker.issue.dto.IssueWriteRequestDto; +import codesquad.issueTracker.issue.service.IssueService; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@RestController +@RequestMapping("/api") +public class IssueController { + + private final IssueService issueService; + + @PostMapping("/issues") + public ApiResponse postIssues(@Valid @RequestBody IssueWriteRequestDto request, + HttpServletRequest httpServletRequest) { + Long id = Long.parseLong(String.valueOf(httpServletRequest.getAttribute("userId"))); + issueService.save(request, id); + return ApiResponse.success(SUCCESS.getStatus(), SUCCESS.getMessage()); + } +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/issue/domain/Issue.java b/be/issue/src/main/java/codesquad/issueTracker/issue/domain/Issue.java new file mode 100644 index 000000000..d0723ba79 --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/issue/domain/Issue.java @@ -0,0 +1,29 @@ +package codesquad.issueTracker.issue.domain; + +import java.time.LocalDateTime; + +import lombok.Builder; +import lombok.Getter; + +@Getter +public class Issue { + private Long id; + private Long milestoneId; + private Long userId; + private String title; + private String content; + private LocalDateTime createdAt; + private Boolean isClosed; + + @Builder + public Issue(Long id, Long milestoneId, Long userId, String title, String content, LocalDateTime createdAt, + Boolean isClosed) { + this.id = id; + this.milestoneId = milestoneId; + this.userId = userId; + this.title = title; + this.content = content; + this.createdAt = createdAt; + this.isClosed = isClosed; + } +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/issue/dto/IssueWriteRequestDto.java b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/IssueWriteRequestDto.java new file mode 100644 index 000000000..37cf45a4d --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/issue/dto/IssueWriteRequestDto.java @@ -0,0 +1,30 @@ +package codesquad.issueTracker.issue.dto; + +import java.util.List; + +import javax.validation.constraints.NotNull; + +import codesquad.issueTracker.issue.domain.Issue; +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class IssueWriteRequestDto { + @NotNull(message = "제목을 입력해주세요") + private String title; + private String content; + private List assignees; // 1,2 + private List labels; // 1,2 + private Long milestoneId; + + public static Issue toEntity(IssueWriteRequestDto request, Long userId) { + return Issue.builder() + .title(request.title) + .content(request.getContent()) + .milestoneId(request.getMilestoneId()) + .userId(userId) + .build(); + } + +} 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 new file mode 100644 index 000000000..13a9c25fd --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/issue/repository/IssueRepository.java @@ -0,0 +1,52 @@ +package codesquad.issueTracker.issue.repository; + +import org.springframework.jdbc.core.JdbcTemplate; +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; + +import codesquad.issueTracker.issue.domain.Issue; + +@Repository +public class IssueRepository { + private final NamedParameterJdbcTemplate jdbcTemplate; + + public IssueRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); + } + + public Long insert(Issue issue) { + String sql = "INSERT INTO issues(title, content, milestone_id, user_id) VALUES (:title,:content,:milestoneId, :userId)"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + SqlParameterSource parameters = new MapSqlParameterSource() + .addValue("title", issue.getTitle()) + .addValue("content", issue.getContent()) + .addValue("milestoneId", issue.getMilestoneId()) + .addValue("userId", issue.getUserId()); + jdbcTemplate.update(sql, parameters, keyHolder); + return keyHolder.getKey().longValue(); + } + + public Long insertLabels(Long issueId, Long labelId) { + String sql = "INSERT INTO issues_labels(issue_id, label_id) VALUES(:issueId, :labelId)"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + SqlParameterSource parameters = new MapSqlParameterSource() + .addValue("issueId", issueId) + .addValue("labelId", labelId); + jdbcTemplate.update(sql, parameters, keyHolder); + return keyHolder.getKey().longValue(); + } + + public Long insertAssignees(Long issueId, Long userId) { + String sql = "INSERT INTO assignees(issue_id, user_id) VALUES(:issueId, :userId)"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + SqlParameterSource parameters = new MapSqlParameterSource() + .addValue("issueId", issueId) + .addValue("userId", userId); + jdbcTemplate.update(sql, parameters, keyHolder); + return keyHolder.getKey().longValue(); + } +} 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 new file mode 100644 index 000000000..8a6dd0bfb --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/issue/service/IssueService.java @@ -0,0 +1,69 @@ +package codesquad.issueTracker.issue.service; + +import java.util.HashSet; +import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import codesquad.issueTracker.comment.service.CommentService; +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.repository.IssueRepository; +import codesquad.issueTracker.label.service.LabelService; +import codesquad.issueTracker.milestone.service.MilestoneService; +import codesquad.issueTracker.user.service.UserService; +import lombok.RequiredArgsConstructor; + +@RequiredArgsConstructor +@Service +@Transactional(readOnly = true) +public class IssueService { + + private final IssueRepository issueRepository; + private final LabelService labelService; + private final UserService userService; + private final MilestoneService milestoneService; + private final CommentService commentService; + + @Transactional + public Long save(IssueWriteRequestDto request, Long id) { + milestoneService.isExistMilestone(request.getMilestoneId()); + List labels = request.getLabels(); + List assignees = request.getAssignees(); + Issue issue = IssueWriteRequestDto.toEntity(request, id); + Long savedIssueId = issueRepository.insert(issue); + + // 라벨 리스트가 null 이 아니면 해당 라벨이 존재하는지 검증 후 라벨과 이슈 연결 테이블에 insert + if (labels != null) { + duplicatedId(labels); + labelService.validateLabelsId(labels); + labels.stream() + .map(findLabel -> issueRepository.insertLabels(savedIssueId, findLabel)) + .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)) + .collect(Collectors.toList()); + } + return savedIssueId; + } + + private void duplicatedId(List list) { + Set set = new HashSet<>(); + for (Long temp : list) { + if (!set.add(temp)) { + throw new CustomException(ErrorCode.DUPLICATE_OBJECT_FOUND); + } + } + } +} 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 18202cfaf..4fe621101 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 @@ -1,8 +1,10 @@ package codesquad.issueTracker.label.repository; import java.util.List; +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; @@ -80,6 +82,13 @@ public Optional findMilestonesCount() { return Optional.ofNullable(jdbcTemplate.queryForObject(sql, params, Integer.class)); } + public Optional