Skip to content

과제 1-1 976520 과제제출 #4

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 15 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,10 @@ dependencies {
annotationProcessor 'org.projectlombok:lombok'
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testRuntimeOnly 'org.junit.platform:junit-platform-launcher'
implementation 'javax.validation:validation-api:2.0.1.Final'
implementation 'org.hibernate.validator:hibernate-validator:6.0.13.Final'
implementation 'org.glassfish:javax.el:3.0.0'
implementation 'org.springframework.boot:spring-boot-starter-validation'
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
package com.gsm._8th.class4.backed.task._1._1.controller;

import com.gsm._8th.class4.backed.task._1._1.domain.Article;
import com.gsm._8th.class4.backed.task._1._1.dto.ArticleRequestDTO;
import com.gsm._8th.class4.backed.task._1._1.service.ArticleService;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

import java.util.List;
import java.util.concurrent.CompletableFuture;

@RestController
@RequestMapping("/articles")
@RequiredArgsConstructor
public class ArticleController {

@Qualifier("articleServiceImpl")
private final ArticleService articleService;

@GetMapping
public CompletableFuture<ResponseEntity<List<Article>>> getAllArticles() {
return articleService.getAllArticles().thenApply(ResponseEntity::ok);
}

@GetMapping("/{articleId}")
public CompletableFuture<ResponseEntity<Article>> getArticleById(@PathVariable Long articleId) {
return articleService.getArticleById(articleId)
.thenApply(article -> article.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build()));
}

@PostMapping
public CompletableFuture<ResponseEntity<Article>> createArticle(@RequestBody ArticleRequestDTO articleDTO) {
validateArticleDTO(articleDTO);
Article article = articleDTO.toEntity();
return articleService.createArticle(article).thenApply(ResponseEntity::ok);
}

@PatchMapping("/{articleId}")
public CompletableFuture<ResponseEntity<Article>> updateArticle(@PathVariable Long articleId, @RequestBody ArticleRequestDTO articleDTO) {
validateArticleDTO(articleDTO);
Article articleDetails = articleDTO.toEntity();
return articleService.updateArticle(articleId, articleDetails)
.thenApply(updatedArticle -> updatedArticle.map(ResponseEntity::ok)
.orElseGet(() -> ResponseEntity.notFound().build()));
}

@DeleteMapping("/{articleId}")
public CompletableFuture<ResponseEntity<Void>> deleteArticle(@PathVariable Long articleId) {
return articleService.deleteArticle(articleId)
.thenApply(deleted -> deleted ? ResponseEntity.noContent().build() : ResponseEntity.notFound().build());
}
Comment on lines +50 to +54
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

보통 DELETE 요청의 대한 응답에는 메세지 Body가 없습니다!(204 No Content)
클라이언트에 true false로 알려주기 보단 비즈니스 로직 중간에서 예외를 throw 하고 그걸 GlobalExceptionHandler에서 캐치해서 응답을 생성하는 방식으로 클라이언트에 처리결과를 알려주고 성공하면 204를 줍니다

Spring에서 ResponseEntity의 status를 204로 설정하거나 그냥 메서드 반환형을 void로 하고 @ResponseStatus 어노테이션을 적용하면 됩니다


private void validateArticleDTO(ArticleRequestDTO articleDTO) {
if (articleDTO.getTitle() == null || articleDTO.getTitle().isBlank()) {
throw new IllegalArgumentException("Title에 뭐라도 써주세요...");
}
if (articleDTO.getTitle().length() > 100) {
throw new IllegalArgumentException("Title은 100자를 넘을 수 없습니다.");
}
if (articleDTO.getContent() == null || articleDTO.getContent().isBlank()) {
throw new IllegalArgumentException("Content에 뭐라도 써주세요...");
}
if (articleDTO.getContent().length() > 1000) {
throw new IllegalArgumentException("Content는 1000자를 넘을 수 없습니다.");
}
}
Comment on lines +57 to +69
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Jakarta의 @SiZe@nonblank,@NotNull 등을 쓰면 DTO 단에서 거르고 메서드를 만들기 까지는 필요가 없을 수도 있을거 같습니다

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.gsm._8th.class4.backed.task._1._1.domain;

import com.gsm._8th.class4.backed.task._1._1.global.entity.BaseIdxEntity;

import jakarta.persistence.Entity;
import jakarta.persistence.Table;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@Entity
@Table(name = "articles")
public class Article extends BaseIdxEntity {
private String title;
private String content;

@Builder
public Article(String title, String content) {
this.title = title;
this.content = content;
}

public void update(String title, String content) {
this.title = title;
this.content = content;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.gsm._8th.class4.backed.task._1._1.domain;

import lombok.AccessLevel;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.AllArgsConstructor;

@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor
public class ArticleDTO {
private String title;
private String content;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.gsm._8th.class4.backed.task._1._1.domain;

public class ArticleMapper {
public static ArticleDTO toDTO(Article article) {
return new ArticleDTO(article.getTitle(), article.getContent());
}

public static Article toEntity(ArticleDTO articleDTO) {
return Article.builder()
.title(articleDTO.getTitle())
.content(articleDTO.getContent())
.build();
}
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package com.gsm._8th.class4.backed.task._1._1.dto;

import com.gsm._8th.class4.backed.task._1._1.domain.Article;

public class ArticleRequestDTO {

private String title;

private String content;

public String getTitle() {
return title;
}

public void setTitle(String title) {
this.title = title;
}

public String getContent() {
return content;
}

public void setContent(String content) {
this.content = content;
}
Comment on lines +11 to +25
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Setter,@Getter적용해서 보일러플레이트 코드 제거 ㄱㄱ


public Article toEntity() {
return Article.builder()
.title(this.title)
.content(this.content)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.gsm._8th.class4.backed.task._1._1.global;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;

@ControllerAdvice
public class GlobalExceptionHandler {

@ExceptionHandler(Exception.class)
public ResponseEntity<String> handleException(Exception e) {
return new ResponseEntity<>(e.getMessage(), HttpStatus.INTERNAL_SERVER_ERROR);
}

}
Original file line number Diff line number Diff line change
@@ -1,19 +1,11 @@
package com.gsm._8th.class4.backed.task._1._1.global.annotation.task.info;

import jakarta.annotation.PostConstruct;
import jdk.jfr.Name;
import org.springframework.stereotype.Component;

import java.lang.annotation.Retention;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;

/**
* 과제 정보를 출력하는 클래스에 적용되는 어노테이션
*
* @Componset 어노테이션을 사용하여 빈으로 등록된 클래스에 적용
* @Slf4j 어노테이션을 사용하여 로깅을 위한 Logger 객체를 생성
* 어노테이션은 런타임에도 유지되어야 하므로 @Retention 어노테이션을 사용하여 런타임에도 유지되도록 함
*/
@Name("TaskInfo")
@Retention(RetentionPolicy.RUNTIME)
@Component
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.gsm._8th.class4.backed.task._1._1.global.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {

@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("*")
.allowedMethods("*")
.allowedHeaders("*");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

@Getter
@MappedSuperclass
abstract class BaseIdxEntity {
public abstract class BaseIdxEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "idx", nullable = false, updatable = false, insertable = false, unique = true)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
package com.gsm._8th.class4.backed.task._1._1.repository;

import com.gsm._8th.class4.backed.task._1._1.domain.Article;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ArticleRepository extends JpaRepository<Article, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.gsm._8th.class4.backed.task._1._1.service;

import com.gsm._8th.class4.backed.task._1._1.domain.Article;
import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

public interface ArticleService {

CompletableFuture<List<Article>> getAllArticles();

CompletableFuture<Optional<Article>> getArticleById(Long id);

CompletableFuture<Article> createArticle(Article article);

CompletableFuture<Optional<Article>> updateArticle(Long id, Article articleDetails);

CompletableFuture<Boolean> deleteArticle(Long id);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.gsm._8th.class4.backed.task._1._1.service.impl;

import com.gsm._8th.class4.backed.task._1._1.domain.Article;
import com.gsm._8th.class4.backed.task._1._1.repository.ArticleRepository;
import com.gsm._8th.class4.backed.task._1._1.service.ArticleService;
import com.gsm._8th.class4.backed.task._1._1.util.ExceptionHandlerUtil;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.util.List;
import java.util.Optional;
import java.util.concurrent.CompletableFuture;

@Service
public class ArticleServiceImpl implements ArticleService {

private final ArticleRepository articleRepository;

public ArticleServiceImpl(ArticleRepository articleRepository) {
this.articleRepository = articleRepository;
}

@Override
@Async
public CompletableFuture<List<Article>> getAllArticles() {
return CompletableFuture.supplyAsync(() -> articleRepository.findAll())
.exceptionally(ex -> ExceptionHandlerUtil.handleListException(ex));
}

@Override
@Async
public CompletableFuture<Optional<Article>> getArticleById(Long id) {
return CompletableFuture.supplyAsync(() -> articleRepository.findById(id))
.exceptionally(ex -> ExceptionHandlerUtil.handleOptionalException(ex));
}

@Override
@Async
public CompletableFuture<Article> createArticle(Article article) {
return CompletableFuture.supplyAsync(() -> articleRepository.save(article))
.exceptionally(ex -> ExceptionHandlerUtil.handleException(ex, null));
}

@Override
@Async
public CompletableFuture<Optional<Article>> updateArticle(Long id, Article articleDetails) {
return CompletableFuture.supplyAsync(() -> articleRepository.findById(id)
.map(existingArticle -> {
existingArticle.update(articleDetails.getTitle(), articleDetails.getContent());
return articleRepository.save(existingArticle);
}))
.exceptionally(ex -> ExceptionHandlerUtil.handleOptionalException(ex));
}

@Override
@Async
public CompletableFuture<Boolean> deleteArticle(Long id) {
return CompletableFuture.supplyAsync(() -> {
Optional<Article> article = articleRepository.findById(id);
article.ifPresent(articleRepository::delete);
return article.isPresent();
})
.exceptionally(ex -> ExceptionHandlerUtil.handleBooleanException(ex));
}
}
Loading