diff --git a/.github/workflows/actions.yml b/.github/workflows/actions.yml new file mode 100644 index 0000000000..3d981d6ccf --- /dev/null +++ b/.github/workflows/actions.yml @@ -0,0 +1,71 @@ +name: CI + +on: + push: + branches: [ "deploy" ] + pull_request: + branches: [ "develop-BE" ] + + workflow_dispatch: + +jobs: + build: + runs-on: ubuntu-latest + defaults: + run: + working-directory: "./be" + + steps: + - uses: actions/checkout@v3 + + - name: Set application.yml for deploy + working-directory: ./be/src/main/resources + run: | + cat /dev/null > application.yml + echo "${{ secrets.APPLICATION_DEVELOP }}" >> ./application.yml + echo "${{ secrets.APPLICATION }}" >> ./application-prod.yml + + - name: Setup Java JDK + uses: actions/setup-java@v3.4.0 + with: + distribution: 'adopt-hotspot' + java-version: '11' + + - name: Grant execute permission for gradlew + run: chmod +x gradlew + + - name: Setup MySQL + uses: mirromutth/mysql-action@v1.1 + with: + mysql database: issuetracker + mysql user: root + mysql root password: ${{ secrets.MYSQL_ROOT_PASSWORD }} + + - name: Build with Gradle + run: ./gradlew build + + - name: Docker Login + uses: docker/login-action@v2.0.0 + with: + username: ${{ secrets.DOCKERHUB_USERNAME }} + password: ${{ secrets.DOCKERHUB_SECRET }} + + - name: Docker build + run: | + docker build -t issue-tracker . + docker tag issue-tracker ${{ secrets.DOCKERHUB_USERNAME }}/issue-tracker:latest + docker push ${{ secrets.DOCKERHUB_USERNAME }}/issue-tracker:latest + + - name: deploy! + uses: appleboy/ssh-action@v0.1.4 + with: + host: ${{ secrets.SSH_HOST }} + username: ubuntu + key: ${{ secrets.PRIVATE_KEY }} + envs: GITHUB_SHA + script: | + sudo docker ps -a -q -f "name=issue-tracker" | grep -q . && sudo docker stop issue-tracker && sudo docker rm issue-tracker | true + sudo docker rmi ${{ secrets.DOCKERHUB_USERNAME }}/issue-tracker:latest + sudo docker pull ${{ secrets.DOCKERHUB_USERNAME }}/issue-tracker:latest + sudo docker tag ${{ secrets.DOCKERHUB_USERNAME }}/issue-tracker:latest issue-tracker + sudo docker run -d --name issue-tracker -p 80:8080 issue-tracker diff --git a/be/Dockerfile b/be/Dockerfile new file mode 100644 index 0000000000..88da91a8c1 --- /dev/null +++ b/be/Dockerfile @@ -0,0 +1,5 @@ +FROM adoptopenjdk/openjdk11 +ARG JAR_FILE=build/libs/*.jar +COPY ${JAR_FILE} app.jar +ENTRYPOINT ["java", "-Dspring.profiles.active=prod", "-jar", "/app.jar"] + diff --git a/be/build.gradle b/be/build.gradle index 0f9b70e605..34de9b0a21 100644 --- a/be/build.gradle +++ b/be/build.gradle @@ -4,6 +4,10 @@ plugins { id 'java' } +jar { + enabled = false +} + group = 'com.codesquad' version = '0.0.1-SNAPSHOT' sourceCompatibility = '11' diff --git a/be/src/main/java/com/codesquad/issuetracker/IssuetrackerApplication.java b/be/src/main/java/com/codesquad/issuetracker/IssuetrackerApplication.java index 7efcaa3640..82eb763a0c 100644 --- a/be/src/main/java/com/codesquad/issuetracker/IssuetrackerApplication.java +++ b/be/src/main/java/com/codesquad/issuetracker/IssuetrackerApplication.java @@ -2,7 +2,11 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.boot.context.properties.ConfigurationPropertiesScan; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +@ConfigurationPropertiesScan +@EnableJpaAuditing @SpringBootApplication public class IssuetrackerApplication { diff --git a/be/src/main/java/com/codesquad/issuetracker/auth/application/AuthService.java b/be/src/main/java/com/codesquad/issuetracker/auth/application/AuthService.java index 48b1ea1f26..ae808200f6 100644 --- a/be/src/main/java/com/codesquad/issuetracker/auth/application/AuthService.java +++ b/be/src/main/java/com/codesquad/issuetracker/auth/application/AuthService.java @@ -16,9 +16,7 @@ public class AuthService { private final JwtProvider jwtProvider; private final UserService userService; - public AccessTokenDto refreshAccessToken(String authorization) { - String refreshToken = TokenParser.parseToken(authorization); - + public AccessTokenDto refreshAccessToken(String refreshToken) { jwtProvider.validateJwtToken(refreshToken); Long userId = jwtProvider.getClaimFromToken(refreshToken, JwtProvider.USER_ID_CLAIM_KEY); diff --git a/be/src/main/java/com/codesquad/issuetracker/auth/presentation/controller/AuthController.java b/be/src/main/java/com/codesquad/issuetracker/auth/presentation/controller/AuthController.java index 7fb5b5acc5..b86dd6984c 100644 --- a/be/src/main/java/com/codesquad/issuetracker/auth/presentation/controller/AuthController.java +++ b/be/src/main/java/com/codesquad/issuetracker/auth/presentation/controller/AuthController.java @@ -4,13 +4,17 @@ import com.codesquad.issuetracker.auth.application.AuthService; import com.codesquad.issuetracker.auth.presentation.dto.AccessTokenDto; import com.codesquad.issuetracker.exception.domain.type.AuthExceptionType; +import com.codesquad.issuetracker.exception.dto.ExceptionResponseDto; +import io.swagger.v3.oas.annotations.Operation; import lombok.RequiredArgsConstructor; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.MissingRequestCookieException; +import org.springframework.web.bind.annotation.CookieValue; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.RestController; -import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.Cookie; @RequiredArgsConstructor @RestController @@ -18,16 +22,28 @@ public class AuthController { private final AuthService authService; + @Operation(summary = "Access 토큰 갱신하기", description = "Refresh token을 통해 Access 토큰을 재발급합니다.") @GetMapping("/auth/refresh") - public AccessTokenDto refresh(HttpServletRequest request) { - String refreshToken = request.getHeader("Authorization"); - return authService.refreshAccessToken(refreshToken); + public AccessTokenDto refresh(@CookieValue(value = "refreshToken") Cookie refreshToken) { + return authService.refreshAccessToken(refreshToken.getValue()); } @ExceptionHandler(JWTVerificationException.class) - public ResponseEntity handleJwtVerificationException(JWTVerificationException ex) { + public ResponseEntity handleJwtVerificationException(JWTVerificationException ex) { + ex.printStackTrace(); AuthExceptionType type = AuthExceptionType.INVALID_REFRESH_TOKEN; - return ResponseEntity.status(type.getStatusCode()).body(type.getMessage()); + ExceptionResponseDto exceptionResponseDto = + new ExceptionResponseDto(type.getErrorCode(), type.getMessage()); + return ResponseEntity.status(type.getStatusCode()).body(exceptionResponseDto); + } + + @ExceptionHandler(MissingRequestCookieException.class) + public ResponseEntity handleMissingRequestCookieException(MissingRequestCookieException ex) { + ex.printStackTrace(); + AuthExceptionType type = AuthExceptionType.REFRESH_TOKEN_NOT_FOUND; + ExceptionResponseDto exceptionResponseDto = + new ExceptionResponseDto(type.getErrorCode(), type.getMessage()); + return ResponseEntity.status(type.getStatusCode()).body(exceptionResponseDto); } } diff --git a/be/src/main/java/com/codesquad/issuetracker/comment/domain/Comment.java b/be/src/main/java/com/codesquad/issuetracker/comment/domain/Comment.java new file mode 100644 index 0000000000..65196a29ed --- /dev/null +++ b/be/src/main/java/com/codesquad/issuetracker/comment/domain/Comment.java @@ -0,0 +1,22 @@ +package com.codesquad.issuetracker.comment.domain; + +import com.codesquad.issuetracker.common.domain.BaseEntity; +import com.codesquad.issuetracker.issue.domain.Issue; +import com.codesquad.issuetracker.user.domain.User; + +import javax.persistence.*; + +@Entity +public class Comment extends BaseEntity { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + private String content; + + @ManyToOne(fetch = FetchType.LAZY) + private User user; + + @ManyToOne(fetch = FetchType.LAZY) + private Issue issue; +} diff --git a/be/src/main/java/com/codesquad/issuetracker/common/config/AuthConfig.java b/be/src/main/java/com/codesquad/issuetracker/common/config/AuthConfig.java index 6085d3e44c..9d622dcbd7 100644 --- a/be/src/main/java/com/codesquad/issuetracker/common/config/AuthConfig.java +++ b/be/src/main/java/com/codesquad/issuetracker/common/config/AuthConfig.java @@ -2,14 +2,20 @@ import com.codesquad.issuetracker.auth.presentation.argumentresolver.AuthArgumentResolver; import com.codesquad.issuetracker.auth.presentation.interceptor.AuthInterceptor; +import com.codesquad.issuetracker.user.application.oauth.OAuthProvider; +import com.codesquad.issuetracker.user.domain.LoginType; import lombok.RequiredArgsConstructor; +import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.web.method.support.HandlerMethodArgumentResolver; import org.springframework.web.servlet.config.annotation.CorsRegistry; import org.springframework.web.servlet.config.annotation.InterceptorRegistry; import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; +import java.util.EnumMap; import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; @RequiredArgsConstructor @Configuration @@ -44,4 +50,10 @@ public void addCorsMappings(CorsRegistry registry) { .allowedOrigins("*") .allowedMethods("*"); } + + @Bean + public EnumMap oAuthProviderEnumMap(List oAuthProviders) { + return new EnumMap<>(oAuthProviders.stream() + .collect(Collectors.toMap(OAuthProvider::getOAuthType, u -> u))); + } } diff --git a/be/src/main/java/com/codesquad/issuetracker/common/domain/BaseEntity.java b/be/src/main/java/com/codesquad/issuetracker/common/domain/BaseEntity.java new file mode 100644 index 0000000000..e7ea99c541 --- /dev/null +++ b/be/src/main/java/com/codesquad/issuetracker/common/domain/BaseEntity.java @@ -0,0 +1,25 @@ +package com.codesquad.issuetracker.common.domain; + +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import javax.persistence.Column; +import javax.persistence.EntityListeners; +import javax.persistence.MappedSuperclass; +import java.time.LocalDateTime; + +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +public class BaseEntity { + + @CreatedDate + @Column(columnDefinition = "TIMESTAMP", nullable = false) + private LocalDateTime createdAt; + + @Column(columnDefinition = "BOOLEAN", nullable = false) + private boolean isDeleted; + + protected void changeDeleted(boolean isDeleted) { + this.isDeleted = isDeleted; + } +} diff --git a/be/src/main/java/com/codesquad/issuetracker/common/properties/GithubProperty.java b/be/src/main/java/com/codesquad/issuetracker/common/properties/GithubProperty.java new file mode 100644 index 0000000000..c1b6c225fd --- /dev/null +++ b/be/src/main/java/com/codesquad/issuetracker/common/properties/GithubProperty.java @@ -0,0 +1,25 @@ +package com.codesquad.issuetracker.common.properties; + +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConstructorBinding; + +@Getter +@ConstructorBinding +@ConfigurationProperties(prefix = "oauth.github") +public class GithubProperty { + + private final String clientId; + private final String clientSecret; + private final String redirectUrl; + private final String accessTokenUrl; + private final String resourceUrl; + + public GithubProperty(String clientId, String clientSecret, String redirectUrl, String accessTokenUrl, String resourceUrl) { + this.clientId = clientId; + this.clientSecret = clientSecret; + this.redirectUrl = redirectUrl; + this.accessTokenUrl = accessTokenUrl; + this.resourceUrl = resourceUrl; + } +} diff --git a/be/src/main/java/com/codesquad/issuetracker/common/properties/GoogleProperty.java b/be/src/main/java/com/codesquad/issuetracker/common/properties/GoogleProperty.java new file mode 100644 index 0000000000..4c1e15a496 --- /dev/null +++ b/be/src/main/java/com/codesquad/issuetracker/common/properties/GoogleProperty.java @@ -0,0 +1,26 @@ +package com.codesquad.issuetracker.common.properties; + +import lombok.Getter; +import org.springframework.boot.context.properties.ConfigurationProperties; +import org.springframework.boot.context.properties.ConstructorBinding; + +@Getter +@ConstructorBinding +@ConfigurationProperties(prefix = "oauth.google") +public class GoogleProperty { + + private final String clientId; + private final String clientSecret; + private final String redirectUrl; + private final String accessTokenUrl; + private final String resourceUrl; + + + public GoogleProperty(String clientId, String clientSecret, String redirectUrl, String accessTokenUrl, String resourceUrl) { + this.clientId = clientId; + this.clientSecret = clientSecret; + this.redirectUrl = redirectUrl; + this.accessTokenUrl = accessTokenUrl; + this.resourceUrl = resourceUrl; + } +} diff --git a/be/src/main/java/com/codesquad/issuetracker/common/util/TokenParser.java b/be/src/main/java/com/codesquad/issuetracker/common/util/TokenParser.java index e87caab16f..b491660bbb 100644 --- a/be/src/main/java/com/codesquad/issuetracker/common/util/TokenParser.java +++ b/be/src/main/java/com/codesquad/issuetracker/common/util/TokenParser.java @@ -17,7 +17,7 @@ public static String parseToken(String authorization) { token = authorization.split(" ")[1].trim(); } catch (ArrayIndexOutOfBoundsException | IllegalArgumentException e) { e.printStackTrace(); - throw new BusinessException(AuthExceptionType.TOKEN_NOT_FOUND); + throw new BusinessException(AuthExceptionType.ACCESS_TOKEN_NOT_FOUND); } return token; } diff --git a/be/src/main/java/com/codesquad/issuetracker/exception/domain/type/AuthExceptionType.java b/be/src/main/java/com/codesquad/issuetracker/exception/domain/type/AuthExceptionType.java index 39fbf056f0..f9e3f19fbf 100644 --- a/be/src/main/java/com/codesquad/issuetracker/exception/domain/type/AuthExceptionType.java +++ b/be/src/main/java/com/codesquad/issuetracker/exception/domain/type/AuthExceptionType.java @@ -1,13 +1,16 @@ package com.codesquad.issuetracker.exception.domain.type; +import lombok.Getter; import org.springframework.http.HttpStatus; +@Getter public enum AuthExceptionType implements ExceptionType { - TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "AUTH001", "이 api에 접근하기 위해서는 Access 토큰이 필요합니다."), + ACCESS_TOKEN_NOT_FOUND(HttpStatus.UNAUTHORIZED, "AUTH001", "이 api에 접근하기 위해서는 Access 토큰이 필요합니다."), TOKEN_EXPIRED(HttpStatus.UNAUTHORIZED, "AUTH002", "액세스 토큰이 만료되었습니다."), INVALID_ACCESS_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH003", "유효하지 않은 Access 토큰입니다."), - INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH004", "유효하지 않은 Refresh 토큰입니다."); + INVALID_REFRESH_TOKEN(HttpStatus.UNAUTHORIZED, "AUTH004", "유효하지 않은 Refresh 토큰입니다."), + REFRESH_TOKEN_NOT_FOUND(HttpStatus.NOT_FOUND, "AUTH005", "refresh 토큰이 존재하지 않습니다."); private final HttpStatus statusCode; private final String errorCode; @@ -19,15 +22,4 @@ public enum AuthExceptionType implements ExceptionType { this.message = message; } - public HttpStatus getStatusCode() { - return statusCode; - } - - public String getErrorCode() { - return errorCode; - } - - public String getMessage() { - return message; - } } diff --git a/be/src/main/java/com/codesquad/issuetracker/exception/domain/type/IssueExceptionType.java b/be/src/main/java/com/codesquad/issuetracker/exception/domain/type/IssueExceptionType.java new file mode 100644 index 0000000000..91eb3eb8ca --- /dev/null +++ b/be/src/main/java/com/codesquad/issuetracker/exception/domain/type/IssueExceptionType.java @@ -0,0 +1,20 @@ +package com.codesquad.issuetracker.exception.domain.type; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public enum IssueExceptionType implements ExceptionType { + + NOT_FOUND(HttpStatus.NOT_FOUND, "ISSUE001", "이슈를 찾을 수 없습니다"); + + private final HttpStatus statusCode; + private final String errorCode; + private final String message; + + IssueExceptionType(HttpStatus statusCode, String errorCode, String message) { + this.statusCode = statusCode; + this.errorCode = errorCode; + this.message = message; + } +} diff --git a/be/src/main/java/com/codesquad/issuetracker/exception/domain/type/MilestoneExceptionType.java b/be/src/main/java/com/codesquad/issuetracker/exception/domain/type/MilestoneExceptionType.java new file mode 100644 index 0000000000..8ec208c0a6 --- /dev/null +++ b/be/src/main/java/com/codesquad/issuetracker/exception/domain/type/MilestoneExceptionType.java @@ -0,0 +1,20 @@ +package com.codesquad.issuetracker.exception.domain.type; + +import lombok.Getter; +import org.springframework.http.HttpStatus; + +@Getter +public enum MilestoneExceptionType implements ExceptionType{ + + NOT_FOUND(HttpStatus.NOT_FOUND, "MILE001", "마일스톤이 존재하지 않습니다."); + + private final HttpStatus statusCode; + private final String errorCode; + private final String message; + + MilestoneExceptionType(HttpStatus statusCode, String errorCode, String message) { + this.statusCode = statusCode; + this.errorCode = errorCode; + this.message = message; + } +} diff --git a/be/src/main/java/com/codesquad/issuetracker/exception/domain/type/UserExceptionType.java b/be/src/main/java/com/codesquad/issuetracker/exception/domain/type/UserExceptionType.java index 00dc45b502..94964c0fd4 100644 --- a/be/src/main/java/com/codesquad/issuetracker/exception/domain/type/UserExceptionType.java +++ b/be/src/main/java/com/codesquad/issuetracker/exception/domain/type/UserExceptionType.java @@ -1,13 +1,15 @@ package com.codesquad.issuetracker.exception.domain.type; +import lombok.Getter; import org.springframework.http.HttpStatus; +@Getter public enum UserExceptionType implements ExceptionType { - NOT_FOUND(HttpStatus.NOT_FOUND, "USER001","유저를 찾을 수 없습니다."), - INVALID_PASSWORD(HttpStatus.BAD_REQUEST, "USER002","비밀번호가 틀렸습니다."), + NOT_FOUND(HttpStatus.NOT_FOUND, "USER001", "유저를 찾을 수 없습니다."), + INVALID_PASSWORD(HttpStatus.BAD_REQUEST, "USER002", "비밀번호가 틀렸습니다."), INVALID_FORMAT(HttpStatus.BAD_REQUEST, "USER003", "아이디나 비밀번호 형식이 잘못 되었습니다."), - DUPLICATED_USERNAME(HttpStatus.BAD_REQUEST, "USER004","이미 가입된 아이디입니다."); + DUPLICATED_USERNAME(HttpStatus.BAD_REQUEST, "USER004", "이미 가입된 아이디입니다."); private final HttpStatus statusCode; private final String errorCode; @@ -18,16 +20,4 @@ public enum UserExceptionType implements ExceptionType { this.errorCode = errorCode; this.message = message; } - - public HttpStatus getStatusCode() { - return statusCode; - } - - public String getErrorCode() { - return errorCode; - } - - public String getMessage() { - return message; - } } diff --git a/be/src/main/java/com/codesquad/issuetracker/issue/application/IssueService.java b/be/src/main/java/com/codesquad/issuetracker/issue/application/IssueService.java new file mode 100644 index 0000000000..e3ffc171bf --- /dev/null +++ b/be/src/main/java/com/codesquad/issuetracker/issue/application/IssueService.java @@ -0,0 +1,150 @@ +package com.codesquad.issuetracker.issue.application; + +import com.codesquad.issuetracker.exception.domain.BusinessException; +import com.codesquad.issuetracker.exception.domain.type.IssueExceptionType; +import com.codesquad.issuetracker.exception.domain.type.MilestoneExceptionType; +import com.codesquad.issuetracker.exception.domain.type.UserExceptionType; +import com.codesquad.issuetracker.issue.domain.*; +import com.codesquad.issuetracker.issue.presentation.dto.*; +import com.codesquad.issuetracker.label.domain.Label; +import com.codesquad.issuetracker.label.domain.LabelRepository; +import com.codesquad.issuetracker.milestone.domain.Milestone; +import com.codesquad.issuetracker.milestone.domain.MilestoneRepository; +import com.codesquad.issuetracker.user.domain.User; +import com.codesquad.issuetracker.user.domain.UserRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Transactional(readOnly = true) +@Service +public class IssueService { + + private final UserRepository userRepository; + private final IssueRepository issueRepository; + private final LabelRepository labelRepository; + private final MilestoneRepository milestoneRepository; + + @Transactional + public long save(long userId, IssueSaveRequestDto issueSaveRequestDto) { + User user = findUser(userId); + Issue issue = new Issue(issueSaveRequestDto.getTitle(), issueSaveRequestDto.getContent(), user); + + if (issueSaveRequestDto.getMilestoneName() != null) { + issue.updateMilestone(findMilestone(issueSaveRequestDto.getMilestoneName())); + } + + issueRepository.save(issue); + + if (issueSaveRequestDto.getAssignees() != null) { + List issueAssignees = createIssueAssignees(issueSaveRequestDto.getAssignees(), issue); + issue.updateAssignees(issueAssignees); + } + + if (issueSaveRequestDto.getLabelNames() != null) { + List labels = createIssueLabels(issueSaveRequestDto.getLabelNames(), issue); + issue.updateLabels(labels); + } + + return issue.getId(); + } + + @Transactional + public void editAssignee(long issueId, IssueAssigneeEditRequestDto issueAssigneeEditRequestDto) { + Issue issue = findIssue(issueId); + + List assignees = createIssueAssignees(issueAssigneeEditRequestDto.getAssignees(), issue); + + issue.updateAssignees(assignees); + } + + @Transactional + public void editLabels(long issueId, IssueLabelEditRequestDto issueLabelEditRequestDto) { + Issue issue = findIssue(issueId); + + List labels = createIssueLabels(issueLabelEditRequestDto.getLabelNames(), issue); + + issue.updateLabels(labels); + } + + @Transactional + public void editMilestone(long issueId, IssueMilestoneEditRequestDto issueMilestoneEditRequestDto) { + Issue issue = findIssue(issueId); + + Milestone milestone = findMilestone(issueMilestoneEditRequestDto.getMilestoneName()); + + issue.updateMilestone(milestone); + } + + @Transactional + public void editContent(long issueId, IssueContentEditRequestDto issueContentEditRequestDto) { + Issue issue = findIssue(issueId); + + issue.updateContent(issueContentEditRequestDto.getContent()); + } + + @Transactional + public void editTitle(long issueId, IssueTitleEditRequestDto issueTitleEditRequestDto) { + Issue issue = findIssue(issueId); + + issue.updateTitle(issueTitleEditRequestDto.getTitle()); + } + + @Transactional + public long softDelete(long issueId) { + Issue issue = findIssue(issueId); + + issue.delete(); + return issueId; + } + + @Transactional + public void changeStatus(IssueStatusDto issueStatusDto) { + IssueStatus changedStatus = issueStatusDto.getStatus(); + + issueStatusDto.getIssues() + .forEach(id -> findIssue(id).changeStatus(changedStatus)); + + } + + private List createIssueAssignees(List assigneeIds, Issue issue) { + List assignees = userRepository.findAllById(assigneeIds); + validateInputMatchWithResult(assigneeIds.size(), assignees.size()); + return assignees.stream() + .map(user -> new IssueAssignee(issue, user)) + .collect(Collectors.toList()); + } + + private List createIssueLabels(List labelNames, Issue issue) { + List