diff --git a/.github/workflows/deploy.yml b/.github/workflows/deploy.yml new file mode 100644 index 000000000..cb6e41692 --- /dev/null +++ b/.github/workflows/deploy.yml @@ -0,0 +1,111 @@ +name: Java CI with Gradle + +on: + push: + branches: release + +env: + APPLICATION_YML_FILE_PATH: ./src/main/resources/application.yml + AWS_REGION: ap-northeast-2 + S3_BUCKET_NAME: issue-tracker-s3-bucket + CODE_DEPLOY_APPLICATION_NAME: issue-tracker-codedeploy-app + CODE_DEPLOY_DEPLOYMENT_GROUP_NAME: issuetracker-codedeploy-deployment-group + +permissions: + contents: read + +jobs: + app-build-and-deploy: + name: BE Deploy + runs-on: ubuntu-latest + environment: production + defaults: + run: + shell: bash + working-directory: ./be/issue + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + + - name: Output application information + run: echo '${{ secrets.APPLICATION }}' > '${{ env.APPLICATION_YML_FILE_PATH }}' + + - name: Build with Gradle + run: | + chmod +x gradlew + ./gradlew clean build -x test + + - name: Make zip file + run: zip -r ./$GITHUB_SHA.zip . + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{secrets.AWS_ACCESS_KEY_ID}} + aws-secret-access-key: ${{secrets.AWS_SECRET_ACCESS_KEY}} + aws-region: ${{env.AWS_REGION}} + + - name: Upload to S3 + run: aws s3 cp + --region '${{ env.AWS_REGION }}' ./$GITHUB_SHA.zip + s3://$S3_BUCKET_NAME/Build/$GITHUB_SHA.zip + + - name: Code Deploy + run: aws deploy create-deployment + --application-name $CODE_DEPLOY_APPLICATION_NAME + --deployment-config-name CodeDeployDefault.AllAtOnce + --deployment-group-name $CODE_DEPLOY_DEPLOYMENT_GROUP_NAME + --s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=Build/$GITHUB_SHA.zip + + web-build-and-deploy: + name: FE Deploy + needs: app-build-and-deploy + runs-on: ubuntu-latest + environment: production + defaults: + run: + shell: bash + working-directory: ./frontend + + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Set up Node.js + uses: actions/setup-node@v2 + with: + node-version: '18' + + - name: Build with npm + run: | + npm install + npm run build + + - name: Make zip file + run: zip -r ./react-build.zip . + + - name: Configure AWS credentials + uses: aws-actions/configure-aws-credentials@v2 + with: + aws-access-key-id: ${{secrets.AWS_ACCESS_KEY_ID}} + aws-secret-access-key: ${{secrets.AWS_SECRET_ACCESS_KEY}} + aws-region: ${{env.AWS_REGION}} + + - name: Upload to S3 + run: aws s3 cp + --region ${{ env.AWS_REGION }} ./react-build.zip + s3://$S3_BUCKET_NAME/Build/react-build.zip + + - name: Code Deploy + run: aws deploy create-deployment + --application-name $CODE_DEPLOY_APPLICATION_NAME + --deployment-config-name CodeDeployDefault.AllAtOnce + --deployment-group-name $CODE_DEPLOY_DEPLOYMENT_GROUP_NAME + --s3-location bucket=$S3_BUCKET_NAME,bundleType=zip,key=Build/react-build.zip diff --git a/.gitignore b/.gitignore index 06b59439a..d3d78dc05 100644 --- a/.gitignore +++ b/.gitignore @@ -141,4 +141,4 @@ dist /.idea/compiler.xml /.idea/codeStyles/codeStyleConfig.xml /.idea/checkstyle-idea.xml -*.yml \ No newline at end of file +/.idea/ diff --git a/README.md b/README.md index 092fa8da9..0eae1025d 100644 --- a/README.md +++ b/README.md @@ -52,3 +52,7 @@ ### BE + +## ERD +![image](https://github.com/codesquad-issue-team-05/issue-tracker-max/assets/103398897/0ffd58d6-e2b2-4786-a916-927623f670f8) + diff --git a/be/issue/.gitignore b/be/issue/.gitignore index c2065bc26..c489d2899 100644 --- a/be/issue/.gitignore +++ b/be/issue/.gitignore @@ -35,3 +35,5 @@ out/ ### VS Code ### .vscode/ + + diff --git a/be/issue/appspec.yml b/be/issue/appspec.yml new file mode 100644 index 000000000..9e24388bf --- /dev/null +++ b/be/issue/appspec.yml @@ -0,0 +1,19 @@ +version: 0.0 +os: linux + +files: + - source: / + destination: /home/ubuntu/app + overwrite: yes + +permissions: + - object: / + pattern: "**" + owner: ubuntu + group: ubuntu + +hooks: + AfterInstall: + - location: deploy-app.sh + timeout: 60 + runas: ubuntu diff --git a/be/issue/build.gradle b/be/issue/build.gradle index e5b28f2d5..0c9bdcb6f 100644 --- a/be/issue/build.gradle +++ b/be/issue/build.gradle @@ -23,7 +23,6 @@ repositories { dependencies { implementation 'org.springframework.boot:spring-boot-starter-data-jdbc' -// implementation 'org.springframework.boot:spring-boot-starter-oauth2-client' implementation 'org.springframework.boot:spring-boot-starter-web' implementation 'org.springframework.boot:spring-boot-starter-validation' compileOnly 'org.projectlombok:lombok' @@ -39,7 +38,6 @@ dependencies { annotationProcessor 'org.springframework.boot:spring-boot-configuration-processor' // 패스워드 암호화 implementation group: 'org.mindrot', name: 'jbcrypt', version: '0.4' - implementation 'org.springframework.boot:spring-boot-starter-webflux' } diff --git a/be/issue/deploy-app.sh b/be/issue/deploy-app.sh new file mode 100644 index 000000000..0d14bcc7f --- /dev/null +++ b/be/issue/deploy-app.sh @@ -0,0 +1,12 @@ +ISSUE_ID=$(jps | grep be | awk '{ print $1 }') + +if [ -z $ISSUE_ID ]; then + echo "동작중인 서버가 없습니다." +else + echo "$ISSUE_ID 프로세스를 삭제합니다." + kill -9 $ISSUE_ID +fi + +echo "서버 시작" +nohup java -jar ~/app/build/libs/issue-0.0.1-SNAPSHOT.jar >/home/ubuntu/app/log.txt 2>&1 & +echo "배포 성공" diff --git a/be/issue/src/main/java/codesquad/issueTracker/comment/controller/CommentController.java b/be/issue/src/main/java/codesquad/issueTracker/comment/controller/CommentController.java index b15037e9c..af38e5d66 100644 --- a/be/issue/src/main/java/codesquad/issueTracker/comment/controller/CommentController.java +++ b/be/issue/src/main/java/codesquad/issueTracker/comment/controller/CommentController.java @@ -1,12 +1,59 @@ package codesquad.issueTracker.comment.controller; +import static codesquad.issueTracker.global.exception.SuccessCode.*; + +import java.util.List; + +import javax.servlet.http.HttpServletRequest; + +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +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; import org.springframework.web.bind.annotation.RestController; +import codesquad.issueTracker.comment.dto.CommentRequestDto; +import codesquad.issueTracker.comment.dto.CommentResponseDto; import codesquad.issueTracker.comment.service.CommentService; +import codesquad.issueTracker.global.common.ApiResponse; import lombok.RequiredArgsConstructor; -@RequiredArgsConstructor @RestController +@RequiredArgsConstructor +@RequestMapping("/api") public class CommentController { + private final CommentService commentService; + + @GetMapping("/issues/{issueId}/comments") + public ApiResponse> getComments(@PathVariable Long issueId) { + List commentResponseDtos = commentService.getComments(issueId); + + return ApiResponse.success(SUCCESS.getStatus(), commentResponseDtos); + } + + @PostMapping("/issues/{issueId}/comments") + public ApiResponse save(@PathVariable Long issueId, + @RequestBody CommentRequestDto commentRequestDto, + HttpServletRequest request) { + commentService.save(request, issueId, commentRequestDto); + + return ApiResponse.success(SUCCESS.getStatus(), SUCCESS.getMessage()); + } + + @PatchMapping("/issues/comments/{commentId}") + public ApiResponse modify(@PathVariable Long commentId, + @RequestBody CommentRequestDto commentRequestDto) { + commentService.modify(commentId, commentRequestDto); + return ApiResponse.success(SUCCESS.getStatus(), SUCCESS.getMessage()); + } + + @DeleteMapping("issues/comments/{commentId}") + public ApiResponse delete(@PathVariable Long commentId) { + commentService.delete(commentId); + return ApiResponse.success(SUCCESS.getStatus(), SUCCESS.getMessage()); + } } diff --git a/be/issue/src/main/java/codesquad/issueTracker/comment/domain/Comment.java b/be/issue/src/main/java/codesquad/issueTracker/comment/domain/Comment.java index 3d0abcf28..729709de9 100644 --- a/be/issue/src/main/java/codesquad/issueTracker/comment/domain/Comment.java +++ b/be/issue/src/main/java/codesquad/issueTracker/comment/domain/Comment.java @@ -1,26 +1,23 @@ package codesquad.issueTracker.comment.domain; import java.time.LocalDateTime; - import lombok.Builder; import lombok.Getter; @Getter public class Comment { + private Long id; + private Long userId; + private Long issueId; + private String content; + private LocalDateTime createdAt; - private Long id; - private Long userId; - private Long issueId; - private String content; - private LocalDateTime createdAt; - - @Builder - - public Comment(Long id, Long userId, Long issueId, String content, LocalDateTime createdAt) { - this.id = id; - this.userId = userId; - this.issueId = issueId; - this.content = content; - this.createdAt = createdAt; - } + @Builder + public Comment(Long id, Long userId, Long issueId, String content, LocalDateTime createdAt) { + this.id = id; + this.userId = userId; + this.issueId = issueId; + this.content = content; + this.createdAt = createdAt; + } } diff --git a/be/issue/src/main/java/codesquad/issueTracker/comment/dto/CommentRequestDto.java b/be/issue/src/main/java/codesquad/issueTracker/comment/dto/CommentRequestDto.java new file mode 100644 index 000000000..3f2e07b19 --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/comment/dto/CommentRequestDto.java @@ -0,0 +1,14 @@ +package codesquad.issueTracker.comment.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@NoArgsConstructor +@AllArgsConstructor +public class CommentRequestDto { + + private String content; + +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/comment/dto/CommentResponseDto.java b/be/issue/src/main/java/codesquad/issueTracker/comment/dto/CommentResponseDto.java new file mode 100644 index 000000000..e4ed8e023 --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/comment/dto/CommentResponseDto.java @@ -0,0 +1,24 @@ +package codesquad.issueTracker.comment.dto; + +import java.time.LocalDateTime; + +import codesquad.issueTracker.comment.vo.CommentUserVo; +import lombok.Builder; +import lombok.Getter; + +@Getter +public class CommentResponseDto { + + private Long id; + private LocalDateTime createdAt; + private String content; + private CommentUserVo writer; + + @Builder + public CommentResponseDto(Long id, LocalDateTime createdAt, String content, CommentUserVo writer) { + this.id = id; + this.createdAt = createdAt; + this.content = content; + this.writer = writer; + } +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/comment/repository/CommentRepository.java b/be/issue/src/main/java/codesquad/issueTracker/comment/repository/CommentRepository.java index 6f9356a2d..07cedf273 100644 --- a/be/issue/src/main/java/codesquad/issueTracker/comment/repository/CommentRepository.java +++ b/be/issue/src/main/java/codesquad/issueTracker/comment/repository/CommentRepository.java @@ -1,13 +1,113 @@ package codesquad.issueTracker.comment.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; import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; +import org.springframework.jdbc.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; +import codesquad.issueTracker.comment.domain.Comment; +import codesquad.issueTracker.comment.dto.CommentRequestDto; +import codesquad.issueTracker.comment.dto.CommentResponseDto; +import codesquad.issueTracker.comment.vo.CommentUserVo; + @Repository public class CommentRepository { + private final NamedParameterJdbcTemplate jdbcTemplate; - public CommentRepository(NamedParameterJdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; + public CommentRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); + } + + public List findByIssueId(Long issueId) { + String sql = "SELECT c.*, u.name, u.profile_img " + + "FROM comments c " + + "JOIN users u ON c.user_id = u.id " + + "WHERE c.issue_id = :issueId AND c.is_deleted = false"; + return jdbcTemplate.query(sql, Map.of("issueId", issueId), commentResponseDtoRowMapper); + } + + private final RowMapper commentResponseDtoRowMapper = ((rs, rowNum) -> { + CommentUserVo writer = CommentUserVo.builder() + .name(rs.getString("name")) + .profileImg(rs.getString("profile_img")) + .build(); + + return CommentResponseDto.builder() + .id(rs.getLong("id")) + .writer(writer) + .content(rs.getString("content")) + .createdAt(rs.getTimestamp("created_At").toLocalDateTime()) + .build(); + }); + + public Optional create(Long userId, Long issueId, CommentRequestDto commentRequestDto) { + String sql = "INSERT INTO comments (user_id, issue_id, content) VALUES (:userId, :issueId, :content)"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("userId", userId); + params.addValue("issueId", issueId); + params.addValue("content", commentRequestDto.getContent()); + + int updatedRow = jdbcTemplate.update(sql, params, keyHolder); + + if (updatedRow > 0) { + return Optional.ofNullable(keyHolder.getKey().longValue()); + } + return Optional.empty(); } + + public Optional update(Long commentId, CommentRequestDto commentRequestDto) { + String sql = "UPDATE comments SET content = :content WHERE id = :commentId"; + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("content", commentRequestDto.getContent()); + params.addValue("commentId", commentId); + + int updatedRow = jdbcTemplate.update(sql, params); + + if (updatedRow > 0) { + return Optional.ofNullable(commentId); + } + return Optional.empty(); + } + + public Optional deleteById(Long commentId) { + String sql = "UPDATE comments SET is_deleted = true WHERE id = :commentId"; + int updatedRow = jdbcTemplate.update(sql, Map.of("commentId", commentId)); + + if (updatedRow > 0) { + return Optional.ofNullable(commentId); + } + return Optional.empty(); + } + + public Optional findById(Long commentId) { + String sql = "SELECT * FROM comments WHERE id = :commentId"; + return Optional.ofNullable( + DataAccessUtils.singleResult( + jdbcTemplate.query(sql, Map.of("commentId", commentId), commentRowMapper))); + } + + public Optional findExistCommentById(Long commentId) { + String sql = "SELECT * FROM comments WHERE id = :commentId AND is_deleted = false"; + return Optional.ofNullable( + DataAccessUtils.singleResult( + jdbcTemplate.query(sql, Map.of("commentId", commentId), commentRowMapper))); + } + + private final RowMapper commentRowMapper = ((rs, rowNum) -> Comment.builder() + .id(rs.getLong("id")) + .userId(rs.getLong("user_id")) + .issueId(rs.getLong("issue_id")) + .content(rs.getString("content")) + .createdAt(rs.getTimestamp("created_At").toLocalDateTime()) + .build()); } diff --git a/be/issue/src/main/java/codesquad/issueTracker/comment/service/CommentService.java b/be/issue/src/main/java/codesquad/issueTracker/comment/service/CommentService.java index ac38d13ba..b666b7ca9 100644 --- a/be/issue/src/main/java/codesquad/issueTracker/comment/service/CommentService.java +++ b/be/issue/src/main/java/codesquad/issueTracker/comment/service/CommentService.java @@ -1,12 +1,57 @@ package codesquad.issueTracker.comment.service; -import org.springframework.stereotype.Service; - +import codesquad.issueTracker.comment.domain.Comment; +import codesquad.issueTracker.comment.dto.CommentRequestDto; +import codesquad.issueTracker.comment.dto.CommentResponseDto; import codesquad.issueTracker.comment.repository.CommentRepository; +import codesquad.issueTracker.global.exception.CustomException; +import codesquad.issueTracker.global.exception.ErrorCode; +import java.util.List; +import javax.servlet.http.HttpServletRequest; import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; -@RequiredArgsConstructor @Service +@Transactional(readOnly = true) +@RequiredArgsConstructor public class CommentService { - private final CommentRepository commentRepository; + private final CommentRepository commentRepository; + + public List getComments(Long issueId) { + return commentRepository.findByIssueId(issueId); + } + + @Transactional + public Long save(HttpServletRequest request, Long issueId, CommentRequestDto commentRequestDto) { + Long userId = Long.parseLong(String.valueOf(request.getAttribute("userId"))); + return commentRepository.create(userId, issueId, commentRequestDto) + .orElseThrow(() -> new CustomException(ErrorCode.DB_EXCEPTION)); + } + + @Transactional + public Long modify(Long commentId, CommentRequestDto commentRequestDto) { + validateExistComment(commentId); + validateCommentStatus(commentId); + return commentRepository.update(commentId, commentRequestDto) + .orElseThrow(() -> new CustomException(ErrorCode.DB_EXCEPTION)); + } + + @Transactional + public Long delete(Long commentId) { + validateExistComment(commentId); + validateCommentStatus(commentId); + return commentRepository.deleteById(commentId) + .orElseThrow(() -> new CustomException(ErrorCode.DB_EXCEPTION)); + } + + private Comment validateExistComment(Long commentId) { + return commentRepository.findById(commentId) + .orElseThrow(() -> new CustomException(ErrorCode.NOT_EXIST_COMMENT)); + } + + private Comment validateCommentStatus(Long commentId) { + return commentRepository.findExistCommentById(commentId) + .orElseThrow(() -> new CustomException(ErrorCode.ALREADY_DELETED_COMMENT)); + } } diff --git a/be/issue/src/main/java/codesquad/issueTracker/comment/vo/CommentUserVo.java b/be/issue/src/main/java/codesquad/issueTracker/comment/vo/CommentUserVo.java new file mode 100644 index 000000000..735d91609 --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/comment/vo/CommentUserVo.java @@ -0,0 +1,16 @@ +package codesquad.issueTracker.comment.vo; + +import lombok.Builder; +import lombok.Getter; + +@Getter +public class CommentUserVo { + private String name; + private String profileImg; + + @Builder + public CommentUserVo(String name, String profileImg) { + this.name = name; + this.profileImg = profileImg; + } +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/global/ApiResponse.java b/be/issue/src/main/java/codesquad/issueTracker/global/ApiResponse.java deleted file mode 100644 index 900655bf6..000000000 --- a/be/issue/src/main/java/codesquad/issueTracker/global/ApiResponse.java +++ /dev/null @@ -1,21 +0,0 @@ -package codesquad.issueTracker.global; - -import lombok.Getter; -import lombok.RequiredArgsConstructor; -import org.springframework.http.HttpStatus; - -@Getter -@RequiredArgsConstructor -public class ApiResponse { - - private final HttpStatus status; - private final T message; - - public static ApiResponse success(HttpStatus status, T message) { - return new ApiResponse<>(status, message); - } - - public static ApiResponse fail(HttpStatus status, T message) { - return new ApiResponse<>(status, message); - } -} diff --git a/be/issue/src/main/java/codesquad/issueTracker/global/common/ApiResponse.java b/be/issue/src/main/java/codesquad/issueTracker/global/common/ApiResponse.java new file mode 100644 index 000000000..06ba97a3f --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/global/common/ApiResponse.java @@ -0,0 +1,22 @@ +package codesquad.issueTracker.global.common; + +import org.springframework.http.HttpStatus; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class ApiResponse { + + private final HttpStatus status; + private final T message; + + public static ApiResponse success(HttpStatus status, T message) { + return new ApiResponse<>(status, message); + } + + public static ApiResponse fail(HttpStatus status, T message) { + return new ApiResponse<>(status, message); + } +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/global/common/Status.java b/be/issue/src/main/java/codesquad/issueTracker/global/common/Status.java new file mode 100644 index 000000000..2a0c8a69f --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/global/common/Status.java @@ -0,0 +1,31 @@ +package codesquad.issueTracker.global.common; + +import codesquad.issueTracker.global.exception.CustomException; +import codesquad.issueTracker.global.exception.ErrorCode; + +public enum Status { + OPEN("open", false), + CLOSED("closed", true); + + private final String type; + private final boolean value; + + Status(String type, boolean value) { + this.type = type; + this.value = value; + } + + public boolean getStatus() { + return value; + } + + public static Status from(String type) { + for (Status status : Status.values()) { + if (status.type.equalsIgnoreCase(type)) { + return status; + } + } + throw new CustomException(ErrorCode.ILLEGAL_STATUS_MILESTONE); + } + +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/global/config/CorsConfig.java b/be/issue/src/main/java/codesquad/issueTracker/global/config/CorsConfig.java new file mode 100644 index 000000000..5033d270e --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/global/config/CorsConfig.java @@ -0,0 +1,17 @@ +package codesquad.issueTracker.global.config; + +import org.springframework.context.annotation.Configuration; +import org.springframework.http.HttpMethod; +import org.springframework.web.servlet.config.annotation.CorsRegistry; +import org.springframework.web.servlet.config.annotation.WebMvcConfigurer; + +@Configuration +public class CorsConfig implements WebMvcConfigurer { + @Override + public void addCorsMappings(CorsRegistry registry) { + registry.addMapping("/api/**") + .allowedOrigins("*") + .allowedMethods(HttpMethod.GET.name(), HttpMethod.POST.name(), HttpMethod.HEAD.name(), + HttpMethod.PUT.name(), HttpMethod.DELETE.name(), HttpMethod.OPTIONS.name()); + } +} \ No newline at end of file diff --git a/be/issue/src/main/java/codesquad/issueTracker/global/config/OauthConfig.java b/be/issue/src/main/java/codesquad/issueTracker/global/config/OauthConfig.java index 2414a1361..f373a27c2 100644 --- a/be/issue/src/main/java/codesquad/issueTracker/global/config/OauthConfig.java +++ b/be/issue/src/main/java/codesquad/issueTracker/global/config/OauthConfig.java @@ -1,25 +1,27 @@ package codesquad.issueTracker.global.config; -import codesquad.issueTracker.jwt.domain.OAuthProperties; -import codesquad.issueTracker.jwt.util.InMemoryProviderRepository; -import codesquad.issueTracker.jwt.util.OauthAdapter; -import codesquad.issueTracker.jwt.util.OauthProvider; import java.util.Map; -import lombok.RequiredArgsConstructor; + import org.springframework.boot.context.properties.EnableConfigurationProperties; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; +import codesquad.issueTracker.jwt.util.InMemoryProviderRepository; +import codesquad.issueTracker.oauth.domain.OAuthProperties; +import codesquad.issueTracker.oauth.util.OauthAdapter; +import codesquad.issueTracker.oauth.util.OauthProvider; +import lombok.RequiredArgsConstructor; + @Configuration @EnableConfigurationProperties(OAuthProperties.class) @RequiredArgsConstructor public class OauthConfig { - private final OAuthProperties properties; + private final OAuthProperties properties; - @Bean - public InMemoryProviderRepository inMemoryProviderRepository() { - Map providers = OauthAdapter.getOauthProviders(properties); - return new InMemoryProviderRepository(providers); - } + @Bean + public InMemoryProviderRepository inMemoryProviderRepository() { + Map providers = OauthAdapter.getOauthProviders(properties); + return new InMemoryProviderRepository(providers); + } } 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 1789da84f..72b1b3940 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 @@ -1,5 +1,7 @@ package codesquad.issueTracker.global.exception; +import java.time.DateTimeException; + import org.springframework.http.HttpStatus; import io.jsonwebtoken.ExpiredJwtException; @@ -11,13 +13,15 @@ public enum ErrorCode implements StatusCode { REQUEST_VALIDATION_FAIL(HttpStatus.BAD_REQUEST), + // -- [Common] -- ] + DB_EXCEPTION(HttpStatus.SERVICE_UNAVAILABLE, "DB 서버 오류"), + // -- [OAuth] -- // NOT_SUPPORTED_PROVIDER(HttpStatus.BAD_REQUEST, "지원하지 않는 로그인 방식입니다."), GITHUB_LOGIN_USER(HttpStatus.BAD_REQUEST, "이미 깃허브로 로그인한 유저입니다"), - // -- [JWT] -- // - NOT_FOUND_REFRESH_TOKEN(HttpStatus.BAD_REQUEST,"해당하는 리프레시 토큰을 찾을 수 없습니다."), + NOT_FOUND_REFRESH_TOKEN(HttpStatus.BAD_REQUEST, "해당하는 리프레시 토큰을 찾을 수 없습니다."), MALFORMED_JWT_EXCEPTION(HttpStatus.UNAUTHORIZED, "잘못된 형식의 토큰입니다."), EXPIRED_JWT_EXCEPTION(HttpStatus.UNAUTHORIZED, "만료된 토큰입니다."), SIGNATURE_EXCEPTION(HttpStatus.UNAUTHORIZED, "JWT의 서명이 올바르지 않습니다."), @@ -28,7 +32,26 @@ public enum ErrorCode implements StatusCode { ALREADY_EXIST_USER(HttpStatus.BAD_REQUEST, "이미 존재하는 유저입니다."), NOT_FOUND_USER(HttpStatus.BAD_REQUEST, "해당하는 유저가 없습니다."), FAILED_LOGIN_USER(HttpStatus.BAD_REQUEST, "로그인에 실패했습니다. 아이디, 비밀번호를 다시 입력해주세요. "), - DELETE_FAIL(HttpStatus.SERVICE_UNAVAILABLE, "DB 서버 오류"); + + // -- [Comment] -- // + NOT_EXIST_COMMENT(HttpStatus.BAD_REQUEST, "존재하지 않는 댓글입니다."), + ALREADY_DELETED_COMMENT(HttpStatus.BAD_REQUEST, "이미 삭제된 댓글입니다."), + + // -- [Milestone] -- // + INVALIDATE_DATE(HttpStatus.BAD_REQUEST, "현재 날짜보다 이전 날짜 입니다."), + NOT_FOUND_DATE(HttpStatus.BAD_REQUEST, "유효하지 않은 날짜 입니다."), + NOT_FOUND_MILESTONE(HttpStatus.BAD_REQUEST, "마일스톤을 찾을 수 없습니다."), + ILLEGAL_STATUS_MILESTONE(HttpStatus.BAD_REQUEST, "올바르지 않은 상태 입력 입니다."), + + // -- [Label] -- // + + 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, "서버 오류로 라벨을 조회할 수 없습니다"); + + + private HttpStatus status; private String message; @@ -65,6 +88,9 @@ public static StatusCode from(RuntimeException e) { if (e instanceof UnsupportedJwtException) { return ErrorCode.UNSUPPORTED_JWT_EXCEPTION; } + if (e instanceof DateTimeException) { + return ErrorCode.NOT_FOUND_DATE; + } return ErrorCode.ILLEGAL_ARGUMENT_EXCEPTION; } } diff --git a/be/issue/src/main/java/codesquad/issueTracker/global/exception/GlobalExceptionHandler.java b/be/issue/src/main/java/codesquad/issueTracker/global/exception/GlobalExceptionHandler.java index 59be27b22..dc49b3445 100644 --- a/be/issue/src/main/java/codesquad/issueTracker/global/exception/GlobalExceptionHandler.java +++ b/be/issue/src/main/java/codesquad/issueTracker/global/exception/GlobalExceptionHandler.java @@ -1,5 +1,6 @@ package codesquad.issueTracker.global.exception; +import java.time.DateTimeException; import java.util.List; import org.springframework.http.ResponseEntity; @@ -8,7 +9,7 @@ import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; -import codesquad.issueTracker.global.ApiResponse; +import codesquad.issueTracker.global.common.ApiResponse; import io.jsonwebtoken.ExpiredJwtException; @RestControllerAdvice @@ -39,10 +40,9 @@ public ResponseEntity> handleMethodArgumentNotValidException .body(ApiResponse.fail(statusCode.getStatus(), errorMessage.toString())); } - @ExceptionHandler(ExpiredJwtException.class) - public ResponseEntity> handleExpiredJwtException(ExpiredJwtException e) { + @ExceptionHandler(DateTimeException.class) + public ResponseEntity> handleDateTimeException(DateTimeException e) { StatusCode statusCode = ErrorCode.from(e); - return ResponseEntity.status(statusCode.getStatus()) .body(ApiResponse.fail(statusCode.getStatus(), statusCode.getMessage())); } diff --git a/be/issue/src/main/java/codesquad/issueTracker/global/filter/AuthFilter.java b/be/issue/src/main/java/codesquad/issueTracker/global/filter/AuthFilter.java index 0e8db0097..757b055a4 100644 --- a/be/issue/src/main/java/codesquad/issueTracker/global/filter/AuthFilter.java +++ b/be/issue/src/main/java/codesquad/issueTracker/global/filter/AuthFilter.java @@ -16,7 +16,7 @@ import com.fasterxml.jackson.databind.ObjectMapper; -import codesquad.issueTracker.global.ApiResponse; +import codesquad.issueTracker.global.common.ApiResponse; import codesquad.issueTracker.global.exception.ErrorCode; import codesquad.issueTracker.global.exception.StatusCode; import codesquad.issueTracker.jwt.util.JwtProvider; @@ -42,9 +42,6 @@ public void doFilter(ServletRequest request, ServletResponse response, FilterCha HttpServletRequest httpServletRequest = (HttpServletRequest)request; HttpServletResponse httpServletResponse = (HttpServletResponse)response; - // if (httpServletRequest.getMethod().equals("GET")) { - // return; - // } if (whiteListCheck(httpServletRequest.getRequestURI())) { chain.doFilter(request, response); return; 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 deleted file mode 100644 index 515ec4042..000000000 --- a/be/issue/src/main/java/codesquad/issueTracker/issue/controller/IssueController.java +++ /dev/null @@ -1,12 +0,0 @@ -package codesquad.issueTracker.issue.controller; - -import org.springframework.web.bind.annotation.RestController; - -import codesquad.issueTracker.issue.service.IssueService; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -@RestController -public class IssueController { - private final IssueService issueService; -} 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 deleted file mode 100644 index 5fe40dc55..000000000 --- a/be/issue/src/main/java/codesquad/issueTracker/issue/domain/Issue.java +++ /dev/null @@ -1,30 +0,0 @@ -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/repository/IssueRepository.java b/be/issue/src/main/java/codesquad/issueTracker/issue/repository/IssueRepository.java deleted file mode 100644 index 50d6559c5..000000000 --- a/be/issue/src/main/java/codesquad/issueTracker/issue/repository/IssueRepository.java +++ /dev/null @@ -1,13 +0,0 @@ -package codesquad.issueTracker.issue.repository; - -import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; -import org.springframework.stereotype.Repository; - -@Repository -public class IssueRepository { - private final NamedParameterJdbcTemplate jdbcTemplate; - - public IssueRepository(NamedParameterJdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; - } -} 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 deleted file mode 100644 index fb175b54c..000000000 --- a/be/issue/src/main/java/codesquad/issueTracker/issue/service/IssueService.java +++ /dev/null @@ -1,12 +0,0 @@ -package codesquad.issueTracker.issue.service; - -import org.springframework.stereotype.Service; - -import codesquad.issueTracker.issue.repository.IssueRepository; -import lombok.RequiredArgsConstructor; - -@RequiredArgsConstructor -@Service -public class IssueService { - private final IssueRepository issueRepository; -} diff --git a/be/issue/src/main/java/codesquad/issueTracker/jwt/domain/OAuthProperties.java b/be/issue/src/main/java/codesquad/issueTracker/jwt/domain/OAuthProperties.java deleted file mode 100644 index 79b68cf2e..000000000 --- a/be/issue/src/main/java/codesquad/issueTracker/jwt/domain/OAuthProperties.java +++ /dev/null @@ -1,32 +0,0 @@ -package codesquad.issueTracker.jwt.domain; - - -import java.util.HashMap; -import java.util.Map; -import lombok.Getter; -import lombok.Setter; -import org.springframework.boot.context.properties.ConfigurationProperties; - - -@Getter -@ConfigurationProperties(prefix = "oauth2") -public class OAuthProperties { - - private final Map client = new HashMap<>(); - private final Map provider = new HashMap(); - - @Getter - @Setter - public static class Client { - private String clientId; - private String clientSecret; - private String redirectUrl; - } - - @Getter - @Setter - public static class Provider { - private String tokenUrl; - private String userInfoUrl; - } -} diff --git a/be/issue/src/main/java/codesquad/issueTracker/jwt/domain/OauthAttributes.java b/be/issue/src/main/java/codesquad/issueTracker/jwt/domain/OauthAttributes.java deleted file mode 100644 index 798b52673..000000000 --- a/be/issue/src/main/java/codesquad/issueTracker/jwt/domain/OauthAttributes.java +++ /dev/null @@ -1,35 +0,0 @@ -package codesquad.issueTracker.jwt.domain; - -import codesquad.issueTracker.global.exception.CustomException; -import codesquad.issueTracker.global.exception.ErrorCode; -import java.util.Arrays; -import java.util.Map; - -public enum OauthAttributes { - GITHUB("github") { - @Override - public UserProfile of(Map attributes) { - return UserProfile.builder() - .email((String) attributes.get("email")) - .name((String) attributes.get("login")) - .imageUrl((String) attributes.get("avatar_url")) - .build(); - } - }; - - private final String providerName; - - OauthAttributes(String name) { - this.providerName = name; - } - - public static UserProfile extract(String providerName, Map attributes) { - return Arrays.stream(values()) - .filter(provider -> providerName.equals(provider.providerName)) - .findFirst() - .orElseThrow(() -> new CustomException(ErrorCode.NOT_SUPPORTED_PROVIDER)) - .of(attributes); - } - - public abstract UserProfile of(Map attributes); -} diff --git a/be/issue/src/main/java/codesquad/issueTracker/jwt/dto/ResponseAccessToken.java b/be/issue/src/main/java/codesquad/issueTracker/jwt/dto/ResponseAccessToken.java new file mode 100644 index 000000000..25199472f --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/jwt/dto/ResponseAccessToken.java @@ -0,0 +1,10 @@ +package codesquad.issueTracker.jwt.dto; + +import lombok.AllArgsConstructor; +import lombok.Getter; + +@Getter +@AllArgsConstructor +public class ResponseAccessToken { + private String accessToken; +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/jwt/util/InMemoryProviderRepository.java b/be/issue/src/main/java/codesquad/issueTracker/jwt/util/InMemoryProviderRepository.java index f138588be..33436ed43 100644 --- a/be/issue/src/main/java/codesquad/issueTracker/jwt/util/InMemoryProviderRepository.java +++ b/be/issue/src/main/java/codesquad/issueTracker/jwt/util/InMemoryProviderRepository.java @@ -1,13 +1,15 @@ package codesquad.issueTracker.jwt.util; import java.util.Map; + +import codesquad.issueTracker.oauth.util.OauthProvider; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor public class InMemoryProviderRepository { - private final Map providers; + private final Map providers; - public OauthProvider findByProviderName(String name) { - return providers.get(name); - } + public OauthProvider findByProviderName(String name) { + return providers.get(name); + } } diff --git a/be/issue/src/main/java/codesquad/issueTracker/jwt/util/OauthAdapter.java b/be/issue/src/main/java/codesquad/issueTracker/jwt/util/OauthAdapter.java deleted file mode 100644 index b83a88139..000000000 --- a/be/issue/src/main/java/codesquad/issueTracker/jwt/util/OauthAdapter.java +++ /dev/null @@ -1,19 +0,0 @@ -package codesquad.issueTracker.jwt.util; - -import codesquad.issueTracker.jwt.domain.OAuthProperties; -import java.util.HashMap; -import java.util.Map; -import lombok.NoArgsConstructor; - -@NoArgsConstructor -public class OauthAdapter { - - public static Map getOauthProviders(OAuthProperties properties) { - Map oauthProvider = new HashMap<>(); - - properties.getClient().forEach( - (key, value) -> oauthProvider.put( - key, new OauthProvider(value, properties.getProvider().get(key)))); - return oauthProvider; - } -} diff --git a/be/issue/src/main/java/codesquad/issueTracker/jwt/util/OauthProvider.java b/be/issue/src/main/java/codesquad/issueTracker/jwt/util/OauthProvider.java deleted file mode 100644 index 31edef645..000000000 --- a/be/issue/src/main/java/codesquad/issueTracker/jwt/util/OauthProvider.java +++ /dev/null @@ -1,28 +0,0 @@ -package codesquad.issueTracker.jwt.util; - -import codesquad.issueTracker.jwt.domain.OAuthProperties; -import lombok.Builder; -import lombok.Getter; -import lombok.RequiredArgsConstructor; - -@Getter -public class OauthProvider { - private final String clientId; - private final String clientSecret; - private final String redirectUrl; - private final String tokenUrl; - private final String userInfoUrl; - - public OauthProvider(OAuthProperties.Client client, OAuthProperties.Provider provider) { - this(client.getClientId(), client.getClientSecret(), client.getRedirectUrl(), provider.getTokenUrl(), provider.getUserInfoUrl()); - } - - @Builder - public OauthProvider(String clientId, String clientSecret, String redirectUrl, String tokenUrl, String userInfoUrl) { - this.clientId = clientId; - this.clientSecret = clientSecret; - this.redirectUrl = redirectUrl; - this.tokenUrl = tokenUrl; - this.userInfoUrl = userInfoUrl; - } -} diff --git a/be/issue/src/main/java/codesquad/issueTracker/label/controller/LabelController.java b/be/issue/src/main/java/codesquad/issueTracker/label/controller/LabelController.java index 8239ef7ba..f36e29910 100644 --- a/be/issue/src/main/java/codesquad/issueTracker/label/controller/LabelController.java +++ b/be/issue/src/main/java/codesquad/issueTracker/label/controller/LabelController.java @@ -1,12 +1,51 @@ package codesquad.issueTracker.label.controller; +import static codesquad.issueTracker.global.exception.SuccessCode.*; + +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +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; import org.springframework.web.bind.annotation.RestController; +import codesquad.issueTracker.global.common.ApiResponse; +import codesquad.issueTracker.label.dto.CreateLabelResponseDto; +import codesquad.issueTracker.label.dto.LabelRequestDto; +import codesquad.issueTracker.label.dto.LabelResponseDto; import codesquad.issueTracker.label.service.LabelService; import lombok.RequiredArgsConstructor; @RequiredArgsConstructor @RestController +@RequestMapping("/api") public class LabelController { + private final LabelService labelService; + + @PostMapping("/labels") + public ApiResponse create(@RequestBody LabelRequestDto labelRequestDto) { + CreateLabelResponseDto createLabelResponseDto = labelService.save(labelRequestDto); + return ApiResponse.success(SUCCESS.getStatus(), createLabelResponseDto); + } + + @PatchMapping("labels/{labelId}") + public ApiResponse modify(@PathVariable Long labelId, @RequestBody LabelRequestDto labelRequestDto) { + labelService.modify(labelId, labelRequestDto); + return ApiResponse.success(SUCCESS.getStatus(), SUCCESS.getMessage()); + } + + @DeleteMapping("labels/{labelId}") + public ApiResponse delete(@PathVariable Long labelId) { + labelService.delete(labelId); + return ApiResponse.success(SUCCESS.getStatus(), SUCCESS.getMessage()); + } + + @GetMapping("labels") + public ApiResponse findAll() { + LabelResponseDto labelResponseDto = labelService.findAll(); + return ApiResponse.success(SUCCESS.getStatus(), labelResponseDto); + } } diff --git a/be/issue/src/main/java/codesquad/issueTracker/label/domain/Label.java b/be/issue/src/main/java/codesquad/issueTracker/label/domain/Label.java index e2f8e98ba..2a53799e7 100644 --- a/be/issue/src/main/java/codesquad/issueTracker/label/domain/Label.java +++ b/be/issue/src/main/java/codesquad/issueTracker/label/domain/Label.java @@ -1,5 +1,6 @@ package codesquad.issueTracker.label.domain; +import codesquad.issueTracker.label.dto.LabelRequestDto; import lombok.Builder; import lombok.Getter; @@ -7,16 +8,26 @@ public class Label { private Long id; private String name; - private String description; - private String backgroundColor; private String textColor; + private String backgroundColor; + private String description; @Builder - public Label(Long id, String name, String description, String backgroundColor, String textColor) { + public Label(Long id, String name, String textColor, String backgroundColor, String description) { this.id = id; this.name = name; - this.description = description; - this.backgroundColor = backgroundColor; this.textColor = textColor; + this.backgroundColor = backgroundColor; + this.description = description; } + + public static Label toEntity(LabelRequestDto labelRequestDto) { + return Label.builder() + .name(labelRequestDto.getName()) + .textColor(labelRequestDto.getTextColor()) + .backgroundColor(labelRequestDto.getBackgroundColor()) + .description(labelRequestDto.getDescription()) + .build(); + } + } diff --git a/be/issue/src/main/java/codesquad/issueTracker/label/dto/CreateLabelResponseDto.java b/be/issue/src/main/java/codesquad/issueTracker/label/dto/CreateLabelResponseDto.java new file mode 100644 index 000000000..ec5bb3417 --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/label/dto/CreateLabelResponseDto.java @@ -0,0 +1,16 @@ +package codesquad.issueTracker.label.dto; + +import lombok.Getter; + +@Getter +public class CreateLabelResponseDto { + private Long labelId; + + public CreateLabelResponseDto(Long labelId) { + this.labelId = labelId; + } + + public static CreateLabelResponseDto from(Long id) { + return new CreateLabelResponseDto(id); + } +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/label/dto/LabelRequestDto.java b/be/issue/src/main/java/codesquad/issueTracker/label/dto/LabelRequestDto.java new file mode 100644 index 000000000..236a74c94 --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/label/dto/LabelRequestDto.java @@ -0,0 +1,18 @@ +package codesquad.issueTracker.label.dto; + +import javax.validation.constraints.NotNull; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public class LabelRequestDto { + @NotNull(message = "라벨 제목을 입력해주세요") + private String name; + @NotNull(message = "텍스트 색상을 입력해주세요") + private String textColor; + @NotNull(message = "배경 색상을 입력해주세요") + private String backgroundColor; + private String description; +} diff --git a/be/issue/src/main/java/codesquad/issueTracker/label/dto/LabelResponseDto.java b/be/issue/src/main/java/codesquad/issueTracker/label/dto/LabelResponseDto.java new file mode 100644 index 000000000..e32eaf8c6 --- /dev/null +++ b/be/issue/src/main/java/codesquad/issueTracker/label/dto/LabelResponseDto.java @@ -0,0 +1,21 @@ +package codesquad.issueTracker.label.dto; + +import java.util.List; + +import codesquad.issueTracker.label.vo.LabelVo; +import lombok.Getter; + +@Getter +public class LabelResponseDto { + private List labels; + private int milestoneCount; + + public LabelResponseDto(List labels, int milestoneCount) { + this.labels = labels; + this.milestoneCount = milestoneCount; + } + + public static LabelResponseDto of(List labels, int milestoneCount) { + return new LabelResponseDto(labels, milestoneCount); + } +} 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 0be9015fd..18202cfaf 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,13 +1,90 @@ package codesquad.issueTracker.label.repository; +import java.util.List; +import java.util.Optional; + +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.support.GeneratedKeyHolder; +import org.springframework.jdbc.support.KeyHolder; import org.springframework.stereotype.Repository; +import codesquad.issueTracker.global.exception.CustomException; +import codesquad.issueTracker.global.exception.ErrorCode; +import codesquad.issueTracker.label.domain.Label; + @Repository public class LabelRepository { + private final NamedParameterJdbcTemplate jdbcTemplate; - public LabelRepository(NamedParameterJdbcTemplate jdbcTemplate) { - this.jdbcTemplate = jdbcTemplate; + public LabelRepository(JdbcTemplate jdbcTemplate) { + this.jdbcTemplate = new NamedParameterJdbcTemplate(jdbcTemplate); + } + + public Long insert(Label label) { + String sql = "INSERT INTO labels (name,text_color,background_color,description) VALUES (:name,:text_color,:background_color,:description)"; + KeyHolder keyHolder = new GeneratedKeyHolder(); + + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("name", label.getName()); + params.addValue("text_color", label.getTextColor()); + params.addValue("background_color", label.getBackgroundColor()); + params.addValue("description", label.getDescription()); + + int result = jdbcTemplate.update(sql, params, keyHolder); + if (result == 0) { + throw new CustomException(ErrorCode.LABEL_INSERT_FAILED); + } + return keyHolder.getKey().longValue(); + } + + public Long update(Long id, Label label) { + String sql = "UPDATE labels SET name = :name, text_color = :textColor, background_color = :backgroundColor, description = :description WHERE id = :id"; + MapSqlParameterSource params = new MapSqlParameterSource(); + params.addValue("id", id); + params.addValue("name", label.getName()); + params.addValue("textColor", label.getTextColor()); + params.addValue("backgroundColor", label.getBackgroundColor()); + params.addValue("description", label.getDescription()); + int result = jdbcTemplate.update(sql, params); + if (result == 0) { + throw new CustomException(ErrorCode.LABEL_UPDATE_FAILED); + } + return id; } + + public Long delete(Long id) { + String sql = "UPDATE labels SET is_deleted = TRUE where id = :id"; + int result = jdbcTemplate.update(sql, new MapSqlParameterSource() + .addValue("id", id)); + if (result == 0) { + throw new CustomException(ErrorCode.LABEL_DELETE_FAILED); + } + return id; + } + + public Optional> findAll() { + String sql = "SELECT id, name, text_color, background_color, description " + + "FROM labels " + + "WHERE is_deleted = FALSE"; + return Optional.of(jdbcTemplate.query(sql, labelRowMapper)); + } + + public Optional findMilestonesCount() { + String sql = "SELECT COUNT(*) FROM milestones WHERE is_deleted = false"; + MapSqlParameterSource params = new MapSqlParameterSource(); + + return Optional.ofNullable(jdbcTemplate.queryForObject(sql, params, Integer.class)); + } + + private final RowMapper