Skip to content

Commit

Permalink
Merge pull request #82 from DOEKYONG/dev-be/feature/#80
Browse files Browse the repository at this point in the history
[be] 이슈 작성 api 구현 ,통합테스트 , 레포지토리 테스트 완료
  • Loading branch information
DOEKYONG authored Aug 14, 2023
2 parents 8df6096 + 65ba0ef commit c51d2cb
Show file tree
Hide file tree
Showing 15 changed files with 426 additions and 36 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Original file line number Diff line number Diff line change
@@ -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<String> 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());
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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<Long> assignees; // 1,2
private List<Long> 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();
}

}
Original file line number Diff line number Diff line change
@@ -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();
}
}
Original file line number Diff line number Diff line change
@@ -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<Long> labels = request.getLabels();
List<Long> 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<Long> list) {
Set<Long> set = new HashSet<>();
for (Long temp : list) {
if (!set.add(temp)) {
throw new CustomException(ErrorCode.DUPLICATE_OBJECT_FOUND);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -80,6 +82,13 @@ public Optional<Integer> findMilestonesCount() {
return Optional.ofNullable(jdbcTemplate.queryForObject(sql, params, Integer.class));
}

public Optional<Label> findById(Long id) {
String sql = "SELECT id, name, description, background_color, text_color, is_deleted FROM labels WHERE id = :id";
return Optional.ofNullable(
DataAccessUtils.singleResult(
jdbcTemplate.query(sql, Map.of("id", id), labelRowMapper)));
}

private final RowMapper<Label> labelRowMapper = (rs, rowNum) -> Label.builder()
.id(rs.getLong("id"))
.name(rs.getString("name"))
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,13 @@ public LabelResponseDto findAll() {
.forEach(label ->
labels.add(LabelVo.from(label)));
return LabelResponseDto.of(labels,
labelRepository.findMilestonesCount().orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_MILESTONE)));
labelRepository.findMilestonesCount()
.orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_MILESTONE)));
}

public void validateLabelsId(List<Long> labels) {
for (Long label : labels) {
labelRepository.findById(label).orElseThrow(() -> new CustomException(ErrorCode.LABEL_FIND_FAILED));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -65,4 +65,10 @@ public MilestoneResponseDto findAll(MileStoneStatusDto request) {
return milestoneResponseDto;
}

public void isExistMilestone(Long id) {
if (!milestoneRepository.isExist(id)) {
throw new CustomException(ErrorCode.NOT_FOUND_MILESTONE);
}
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ public Long insert(User user) {
public Optional<User> findByEmail(String email) {
String sql = "SELECT id, email, password, profile_img, name, login_type FROM users WHERE email = :email";
return Optional.ofNullable(
DataAccessUtils.singleResult(jdbcTemplate.query(sql, Map.of("email", email), userRowMapper)));
DataAccessUtils.singleResult(jdbcTemplate.query(sql, Map.of("email", email), userRowMapper)));
}

public Long insertRefreshToken(Long userId, String refreshToken) {
Expand All @@ -60,7 +60,7 @@ public Long insertRefreshToken(Long userId, String refreshToken) {
public Optional<Token> findTokenByUserId(Long userId) {
String sql = "SELECT id, user_id, refresh_token FROM tokens WHERE user_id = :userId";
return Optional.ofNullable(
DataAccessUtils.singleResult(jdbcTemplate.query(sql, Map.of("userId", userId), tokenRowMapper)));
DataAccessUtils.singleResult(jdbcTemplate.query(sql, Map.of("userId", userId), tokenRowMapper)));
}

public int updateRefreshToken(Long userId, String refreshToken) {
Expand All @@ -74,8 +74,8 @@ public int updateRefreshToken(Long userId, String refreshToken) {
public Optional<Token> findTokenByUserToken(String refreshToken) {
String sql = "SELECT id, user_id, refresh_token FROM tokens WHERE refresh_token = :refreshToken";
return Optional.ofNullable(
DataAccessUtils.singleResult(
jdbcTemplate.query(sql, Map.of("refreshToken", refreshToken), tokenRowMapper)));
DataAccessUtils.singleResult(
jdbcTemplate.query(sql, Map.of("refreshToken", refreshToken), tokenRowMapper)));
}

public Optional<Integer> deleteTokenByUserId(Long userId) {
Expand All @@ -86,27 +86,34 @@ public Optional<Integer> deleteTokenByUserId(Long userId) {
}

private final RowMapper<User> userRowMapper = (((rs, rowNum) -> User.builder()
.id(rs.getLong("id"))
.email(rs.getString("email"))
.password(rs.getString("password"))
.profileImg(rs.getString("profile_img"))
.name(rs.getString("name"))
.loginType(LoginType.findByTypeString(rs.getString("login_type")))
.build()
.id(rs.getLong("id"))
.email(rs.getString("email"))
.password(rs.getString("password"))
.profileImg(rs.getString("profile_img"))
.name(rs.getString("name"))
.loginType(LoginType.findByTypeString(rs.getString("login_type")))
.build()
));

private final RowMapper<Token> tokenRowMapper = (((rs, rowNum) -> Token.builder()
.id(rs.getLong("id"))
.userId(rs.getLong("user_id"))
.refreshToken(rs.getString("refresh_token"))
.build()
.id(rs.getLong("id"))
.userId(rs.getLong("user_id"))
.refreshToken(rs.getString("refresh_token"))
.build()
));


public Long updateUserLoginType(User existUser, User user) {
String sql = "UPDATE users SET login_type = :loginType WHERE id = :userId";
jdbcTemplate.update(sql,Map.of("loginType",user.getLoginType().getType(),"userId",existUser.getId()));
jdbcTemplate.update(sql, Map.of("loginType", user.getLoginType().getType(), "userId", existUser.getId()));
return existUser.getId();
}

public Optional<User> findById(Long id) {
String sql = "SELECT id, email, password, profile_img, name, login_type FROM users WHERE id = :id";
return Optional.ofNullable(
DataAccessUtils.singleResult(
jdbcTemplate.query(sql, Map.of("id", id), userRowMapper)));
}

}

Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package codesquad.issueTracker.user.service;

import java.util.List;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;
Expand Down Expand Up @@ -102,4 +103,11 @@ public void logout(HttpServletRequest request) {
.orElseThrow(() -> new CustomException(ErrorCode.FAILED_LOGOUT_USER));
}

public void validateUserIds(List<Long> assignees) {
for (Long assignee : assignees) {
userRepository.findById(assignee).orElseThrow(() -> new CustomException(ErrorCode.NOT_FOUND_USER));
}

}

}
Loading

0 comments on commit c51d2cb

Please sign in to comment.