package io.spring.api; import com.fasterxml.jackson.annotation.JsonRootName; import io.spring.application.ArticleQueryService; import io.spring.application.Page; import io.spring.core.article.Article; import io.spring.core.article.ArticleRepository; import io.spring.core.user.User; import java.lang.annotation.Documented; import java.lang.annotation.ElementType; import java.lang.annotation.Retention; import java.lang.annotation.RetentionPolicy; import java.lang.annotation.Target; import java.util.HashMap; import javax.validation.Constraint; import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidatorContext; import javax.validation.Payload; import javax.validation.Valid; import javax.validation.constraints.NotBlank; import lombok.Getter; import lombok.NoArgsConstructor; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.ResponseEntity; import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.web.bind.annotation.GetMapping; 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.RequestParam; import org.springframework.web.bind.annotation.RestController; @RestController @RequestMapping(path = "/articles") public class ArticlesApi { private ArticleRepository articleRepository; private ArticleQueryService articleQueryService; @Autowired public ArticlesApi(ArticleRepository articleRepository, ArticleQueryService articleQueryService) { this.articleRepository = articleRepository; this.articleQueryService = articleQueryService; } @PostMapping public ResponseEntity createArticle( @Valid @RequestBody NewArticleParam newArticleParam, @AuthenticationPrincipal User user) { Article article = new Article( newArticleParam.getTitle(), newArticleParam.getDescription(), newArticleParam.getBody(), newArticleParam.getTagList(), user.getId()); articleRepository.save(article); return ResponseEntity.ok( new HashMap() { { put("article", articleQueryService.findById(article.getId(), user).get()); } }); } @GetMapping(path = "feed") public ResponseEntity getFeed( @RequestParam(value = "offset", defaultValue = "0") int offset, @RequestParam(value = "limit", defaultValue = "20") int limit, @AuthenticationPrincipal User user) { return ResponseEntity.ok(articleQueryService.findUserFeed(user, new Page(offset, limit))); } @GetMapping public ResponseEntity getArticles( @RequestParam(value = "offset", defaultValue = "0") int offset, @RequestParam(value = "limit", defaultValue = "20") int limit, @RequestParam(value = "tag", required = false) String tag, @RequestParam(value = "favorited", required = false) String favoritedBy, @RequestParam(value = "author", required = false) String author, @AuthenticationPrincipal User user) { return ResponseEntity.ok( articleQueryService.findRecentArticles( tag, author, favoritedBy, new Page(offset, limit), user)); } } @Getter @JsonRootName("article") @NoArgsConstructor class NewArticleParam { @NotBlank(message = "can't be empty") @DuplicatedArticleConstraint private String title; @NotBlank(message = "can't be empty") private String description; @NotBlank(message = "can't be empty") private String body; private String[] tagList; } @Documented @Constraint(validatedBy = DuplicatedArticleValidator.class) @Target({ElementType.METHOD, ElementType.FIELD, ElementType.PARAMETER, ElementType.TYPE_USE}) @Retention(RetentionPolicy.RUNTIME) @interface DuplicatedArticleConstraint { String message() default "article name exists"; Class[] groups() default {}; Class[] payload() default {}; } class DuplicatedArticleValidator implements ConstraintValidator { @Autowired private ArticleQueryService articleQueryService; @Override public boolean isValid(String value, ConstraintValidatorContext context) { return !articleQueryService.findBySlug(Article.toSlug(value), null).isPresent(); } }