From 81aac3914cd126528fd8a423cae1f2c460084b74 Mon Sep 17 00:00:00 2001 From: bo Date: Mon, 29 Apr 2024 23:38:11 +0900 Subject: [PATCH] =?UTF-8?q?feat(article):=20Article=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../server/article/domain/Article.java | 47 ++++++++++ .../domain/repository/ArticleRepository.java | 19 ++++ .../exception/ArticleNotFoundException.java | 11 +++ .../CannotDeleteOthersArticleException.java | 11 +++ .../presentation/ArticleController.java | 88 +++++++++++++++++++ .../dto/ArticleCreateRequest.java | 25 ++++++ .../presentation/dto/ArticleResponse.java | 26 ++++++ .../dto/ArticleUpdateRequest.java | 10 +++ .../service/CommandArticleService.java | 43 +++++++++ .../article/service/QueryArticleService.java | 31 +++++++ .../implementation/ArticleCreator.java | 18 ++++ .../implementation/ArticleDeleter.java | 17 ++++ .../service/implementation/ArticleReader.java | 30 +++++++ .../implementation/ArticleUpdater.java | 20 +++++ 14 files changed, 396 insertions(+) create mode 100644 main-server/src/main/java/com/sickgyun/server/article/domain/Article.java create mode 100644 main-server/src/main/java/com/sickgyun/server/article/domain/repository/ArticleRepository.java create mode 100644 main-server/src/main/java/com/sickgyun/server/article/exception/ArticleNotFoundException.java create mode 100644 main-server/src/main/java/com/sickgyun/server/article/exception/CannotDeleteOthersArticleException.java create mode 100644 main-server/src/main/java/com/sickgyun/server/article/presentation/ArticleController.java create mode 100644 main-server/src/main/java/com/sickgyun/server/article/presentation/dto/ArticleCreateRequest.java create mode 100644 main-server/src/main/java/com/sickgyun/server/article/presentation/dto/ArticleResponse.java create mode 100644 main-server/src/main/java/com/sickgyun/server/article/presentation/dto/ArticleUpdateRequest.java create mode 100644 main-server/src/main/java/com/sickgyun/server/article/service/CommandArticleService.java create mode 100644 main-server/src/main/java/com/sickgyun/server/article/service/QueryArticleService.java create mode 100644 main-server/src/main/java/com/sickgyun/server/article/service/implementation/ArticleCreator.java create mode 100644 main-server/src/main/java/com/sickgyun/server/article/service/implementation/ArticleDeleter.java create mode 100644 main-server/src/main/java/com/sickgyun/server/article/service/implementation/ArticleReader.java create mode 100644 main-server/src/main/java/com/sickgyun/server/article/service/implementation/ArticleUpdater.java diff --git a/main-server/src/main/java/com/sickgyun/server/article/domain/Article.java b/main-server/src/main/java/com/sickgyun/server/article/domain/Article.java new file mode 100644 index 0000000..810009d --- /dev/null +++ b/main-server/src/main/java/com/sickgyun/server/article/domain/Article.java @@ -0,0 +1,47 @@ +package com.sickgyun.server.article.domain; + +import java.time.LocalDateTime; + +import org.springframework.data.annotation.CreatedDate; + +import com.sickgyun.server.user.domain.User; + +import jakarta.persistence.Entity; +import jakarta.persistence.FetchType; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.OneToOne; +import lombok.AccessLevel; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +public class Article { + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + private String title; + private String imgUrl; + private String url; + @OneToOne(fetch = FetchType.LAZY) + private User user; + @CreatedDate + private LocalDateTime createdAt; + + public Article(String title, String imgUrl, String url, User user) { + this.title = title; + this.imgUrl = imgUrl; + this.url = url; + this.user = user; + } + + public void update(Article article) { + this.title = article.getTitle(); + this.imgUrl = article.getImgUrl(); + this.url = article.getUrl(); + this.user = article.getUser(); + } +} diff --git a/main-server/src/main/java/com/sickgyun/server/article/domain/repository/ArticleRepository.java b/main-server/src/main/java/com/sickgyun/server/article/domain/repository/ArticleRepository.java new file mode 100644 index 0000000..45214c0 --- /dev/null +++ b/main-server/src/main/java/com/sickgyun/server/article/domain/repository/ArticleRepository.java @@ -0,0 +1,19 @@ +package com.sickgyun.server.article.domain.repository; + +import java.util.List; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.sickgyun.server.article.domain.Article; +import com.sickgyun.server.article.exception.ArticleNotFoundException; +import com.sickgyun.server.user.domain.User; + +public interface ArticleRepository extends JpaRepository { + + default Article getById(Long id) { + return findById(id) + .orElseThrow(() -> new ArticleNotFoundException(id)); + } + + List
findByUser(User user); +} diff --git a/main-server/src/main/java/com/sickgyun/server/article/exception/ArticleNotFoundException.java b/main-server/src/main/java/com/sickgyun/server/article/exception/ArticleNotFoundException.java new file mode 100644 index 0000000..aadcfed --- /dev/null +++ b/main-server/src/main/java/com/sickgyun/server/article/exception/ArticleNotFoundException.java @@ -0,0 +1,11 @@ +package com.sickgyun.server.article.exception; + +import org.springframework.http.HttpStatus; + +import com.sickgyun.server.common.exception.SickgyunException; + +public class ArticleNotFoundException extends SickgyunException { + public ArticleNotFoundException(Long id) { + super(HttpStatus.NOT_FOUND, "id가 " + id + "인 아티클을 찾을 수 없어요"); + } +} diff --git a/main-server/src/main/java/com/sickgyun/server/article/exception/CannotDeleteOthersArticleException.java b/main-server/src/main/java/com/sickgyun/server/article/exception/CannotDeleteOthersArticleException.java new file mode 100644 index 0000000..3ad3883 --- /dev/null +++ b/main-server/src/main/java/com/sickgyun/server/article/exception/CannotDeleteOthersArticleException.java @@ -0,0 +1,11 @@ +package com.sickgyun.server.article.exception; + +import org.springframework.http.HttpStatus; + +import com.sickgyun.server.common.exception.SickgyunException; + +public class CannotDeleteOthersArticleException extends SickgyunException { + public CannotDeleteOthersArticleException() { + super(HttpStatus.FORBIDDEN, "다른 사람의 글을 삭제할 수 없습니다."); + } +} diff --git a/main-server/src/main/java/com/sickgyun/server/article/presentation/ArticleController.java b/main-server/src/main/java/com/sickgyun/server/article/presentation/ArticleController.java new file mode 100644 index 0000000..dd51642 --- /dev/null +++ b/main-server/src/main/java/com/sickgyun/server/article/presentation/ArticleController.java @@ -0,0 +1,88 @@ +package com.sickgyun.server.article.presentation; + +import java.util.List; + +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.PutMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import com.sickgyun.server.article.presentation.dto.ArticleCreateRequest; +import com.sickgyun.server.article.presentation.dto.ArticleResponse; +import com.sickgyun.server.article.presentation.dto.ArticleUpdateRequest; +import com.sickgyun.server.article.service.CommandArticleService; +import com.sickgyun.server.article.service.QueryArticleService; +import com.sickgyun.server.auth.annotation.LoginRequired; +import com.sickgyun.server.auth.service.implementation.AuthReader; + +import lombok.RequiredArgsConstructor; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/articles") +public class ArticleController { + private final CommandArticleService commandService; + private final QueryArticleService queryService; + private final AuthReader authReader; + private final CommandArticleService commandArticleService; + + @PostMapping + @LoginRequired + @ResponseStatus(HttpStatus.CREATED) + public void create(ArticleCreateRequest creatRequest) { + commandService.create( + creatRequest.toEntity( + authReader.getCurrentUser() + ) + ); + } + + @GetMapping + @ResponseStatus(HttpStatus.OK) + public List getAll() { + return queryService.getAll() + .stream() + .map(ArticleResponse::new) + .toList(); + } + + @GetMapping("/{id}") + @ResponseStatus(HttpStatus.OK) + public ArticleResponse getOne(@PathVariable Long id) { + return new ArticleResponse(queryService.getOne(id)); + } + + @LoginRequired + @GetMapping("/mine") + @ResponseStatus(HttpStatus.OK) + public List findMine() { + return queryService.findMine(authReader.getCurrentUser()) + .stream() + .map(ArticleResponse::new) + .toList(); + } + + @LoginRequired + @PutMapping("/{id}") + @ResponseStatus(HttpStatus.CREATED) + public void update(ArticleUpdateRequest updateRequest, @PathVariable Long id) { + commandService.update( + updateRequest.toEntity( + authReader.getCurrentUser() + ), + id + ); + } + + @DeleteMapping("/{id}") + @LoginRequired + @ResponseStatus(HttpStatus.NO_CONTENT) + public void delete(@PathVariable Long id) { + commandArticleService.delete(id, authReader.getCurrentUser()); + } +} diff --git a/main-server/src/main/java/com/sickgyun/server/article/presentation/dto/ArticleCreateRequest.java b/main-server/src/main/java/com/sickgyun/server/article/presentation/dto/ArticleCreateRequest.java new file mode 100644 index 0000000..a61b998 --- /dev/null +++ b/main-server/src/main/java/com/sickgyun/server/article/presentation/dto/ArticleCreateRequest.java @@ -0,0 +1,25 @@ +package com.sickgyun.server.article.presentation.dto; + +import com.sickgyun.server.article.domain.Article; +import com.sickgyun.server.user.domain.User; + +import jakarta.validation.constraints.NotNull; + +public record ArticleCreateRequest( + @NotNull(message = "title은 필수 값입니다.") + String title, + @NotNull(message = "imgUrl은 필수 값입니다.") + String imgUrl, + @NotNull(message = "url은 필수 값입니다.") + String url + +) { + public Article toEntity(User currentUser) { + return new Article( + title, + imgUrl, + url, + currentUser + ); + } +} diff --git a/main-server/src/main/java/com/sickgyun/server/article/presentation/dto/ArticleResponse.java b/main-server/src/main/java/com/sickgyun/server/article/presentation/dto/ArticleResponse.java new file mode 100644 index 0000000..2894d90 --- /dev/null +++ b/main-server/src/main/java/com/sickgyun/server/article/presentation/dto/ArticleResponse.java @@ -0,0 +1,26 @@ +package com.sickgyun.server.article.presentation.dto; + +import java.time.LocalDateTime; + +import com.sickgyun.server.article.domain.Article; +import com.sickgyun.server.user.presentation.dto.UserResponse; + +public record ArticleResponse( + Long id, + String title, + String imgUrl, + String url, + UserResponse user, + LocalDateTime createdAt +) { + public ArticleResponse(Article article) { + this( + article.getId(), + article.getTitle(), + article.getImgUrl(), + article.getUrl(), + UserResponse.from(article.getUser()), + article.getCreatedAt() + ); + } +} diff --git a/main-server/src/main/java/com/sickgyun/server/article/presentation/dto/ArticleUpdateRequest.java b/main-server/src/main/java/com/sickgyun/server/article/presentation/dto/ArticleUpdateRequest.java new file mode 100644 index 0000000..6a81b6b --- /dev/null +++ b/main-server/src/main/java/com/sickgyun/server/article/presentation/dto/ArticleUpdateRequest.java @@ -0,0 +1,10 @@ +package com.sickgyun.server.article.presentation.dto; + +import com.sickgyun.server.article.domain.Article; +import com.sickgyun.server.user.domain.User; + +public record ArticleUpdateRequest() { + public Article toEntity(User currentUser) { + return null; + } +} diff --git a/main-server/src/main/java/com/sickgyun/server/article/service/CommandArticleService.java b/main-server/src/main/java/com/sickgyun/server/article/service/CommandArticleService.java new file mode 100644 index 0000000..bb44e94 --- /dev/null +++ b/main-server/src/main/java/com/sickgyun/server/article/service/CommandArticleService.java @@ -0,0 +1,43 @@ +package com.sickgyun.server.article.service; + +import java.util.Objects; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.sickgyun.server.article.domain.Article; +import com.sickgyun.server.article.exception.CannotDeleteOthersArticleException; +import com.sickgyun.server.article.service.implementation.ArticleCreator; +import com.sickgyun.server.article.service.implementation.ArticleDeleter; +import com.sickgyun.server.article.service.implementation.ArticleReader; +import com.sickgyun.server.article.service.implementation.ArticleUpdater; +import com.sickgyun.server.user.domain.User; + +import lombok.RequiredArgsConstructor; + +@Service +@Transactional +@RequiredArgsConstructor +public class CommandArticleService { + private final ArticleCreator articleCreator; + private final ArticleUpdater articleUpdater; + private final ArticleReader articleReader; + private final ArticleDeleter articleDeleter; + + public void create(Article entity) { + articleCreator.create(entity); + } + + public void update(Article entity, Long updatableId) { + articleUpdater.update(entity, updatableId); + } + + public void delete(Long id, User currentUser) { + Article byId = articleReader.findById(id); + if (!Objects.equals(byId.getUser().getId(), currentUser.getId())) { + throw new CannotDeleteOthersArticleException(); + } + + articleDeleter.deleteById(id); + } +} diff --git a/main-server/src/main/java/com/sickgyun/server/article/service/QueryArticleService.java b/main-server/src/main/java/com/sickgyun/server/article/service/QueryArticleService.java new file mode 100644 index 0000000..b7f82a1 --- /dev/null +++ b/main-server/src/main/java/com/sickgyun/server/article/service/QueryArticleService.java @@ -0,0 +1,31 @@ +package com.sickgyun.server.article.service; + +import java.util.List; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.sickgyun.server.article.domain.Article; +import com.sickgyun.server.article.service.implementation.ArticleReader; +import com.sickgyun.server.user.domain.User; + +import lombok.RequiredArgsConstructor; + +@Service +@Transactional(readOnly = true) +@RequiredArgsConstructor +public class QueryArticleService { + private final ArticleReader articleReader; + + public List
getAll() { + return articleReader.findAll(); + } + + public Article getOne(Long id) { + return articleReader.findById(id); + } + + public List
findMine(User user) { + return articleReader.findMine(user); + } +} diff --git a/main-server/src/main/java/com/sickgyun/server/article/service/implementation/ArticleCreator.java b/main-server/src/main/java/com/sickgyun/server/article/service/implementation/ArticleCreator.java new file mode 100644 index 0000000..b07bffd --- /dev/null +++ b/main-server/src/main/java/com/sickgyun/server/article/service/implementation/ArticleCreator.java @@ -0,0 +1,18 @@ +package com.sickgyun.server.article.service.implementation; + +import org.springframework.stereotype.Service; + +import com.sickgyun.server.article.domain.Article; +import com.sickgyun.server.article.domain.repository.ArticleRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class ArticleCreator { + private final ArticleRepository articleRepository; + + public void create(Article entity) { + articleRepository.save(entity); + } +} diff --git a/main-server/src/main/java/com/sickgyun/server/article/service/implementation/ArticleDeleter.java b/main-server/src/main/java/com/sickgyun/server/article/service/implementation/ArticleDeleter.java new file mode 100644 index 0000000..2cb5ce1 --- /dev/null +++ b/main-server/src/main/java/com/sickgyun/server/article/service/implementation/ArticleDeleter.java @@ -0,0 +1,17 @@ +package com.sickgyun.server.article.service.implementation; + +import org.springframework.stereotype.Service; + +import com.sickgyun.server.article.domain.repository.ArticleRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class ArticleDeleter { + private final ArticleRepository articleRepository; + + public void deleteById(Long id) { + articleRepository.deleteById(id); + } +} diff --git a/main-server/src/main/java/com/sickgyun/server/article/service/implementation/ArticleReader.java b/main-server/src/main/java/com/sickgyun/server/article/service/implementation/ArticleReader.java new file mode 100644 index 0000000..9f8b7ed --- /dev/null +++ b/main-server/src/main/java/com/sickgyun/server/article/service/implementation/ArticleReader.java @@ -0,0 +1,30 @@ +package com.sickgyun.server.article.service.implementation; + +import java.util.List; + +import org.springframework.data.domain.Sort; +import org.springframework.stereotype.Service; + +import com.sickgyun.server.article.domain.Article; +import com.sickgyun.server.article.domain.repository.ArticleRepository; +import com.sickgyun.server.user.domain.User; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class ArticleReader { + private final ArticleRepository articleRepository; + + public List
findAll() { + return articleRepository.findAll(Sort.by(Sort.Direction.DESC, "createdAt")); + } + + public Article findById(Long id) { + return articleRepository.getById(id); + } + + public List
findMine(User user) { + return articleRepository.findByUser(user); + } +} diff --git a/main-server/src/main/java/com/sickgyun/server/article/service/implementation/ArticleUpdater.java b/main-server/src/main/java/com/sickgyun/server/article/service/implementation/ArticleUpdater.java new file mode 100644 index 0000000..ee71079 --- /dev/null +++ b/main-server/src/main/java/com/sickgyun/server/article/service/implementation/ArticleUpdater.java @@ -0,0 +1,20 @@ +package com.sickgyun.server.article.service.implementation; + +import org.springframework.stereotype.Service; + +import com.sickgyun.server.article.domain.Article; +import com.sickgyun.server.article.domain.repository.ArticleRepository; +import com.sickgyun.server.article.exception.ArticleNotFoundException; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class ArticleUpdater { + private final ArticleRepository articleRepository; + public void update(Article article, Long updatableId) { + Article updatableArticle = articleRepository.getById(updatableId); + + updatableArticle.update(article); + } +}