From f0651f8c37017bfb566581d2d641b4b7faaa4c5b Mon Sep 17 00:00:00 2001 From: johnniang Date: Thu, 21 Mar 2019 19:26:05 +0800 Subject: [PATCH] Complete post creation api --- .../cc/ryanc/halo/model/params/PostParam.java | 73 +++++++++++++++++++ .../cc/ryanc/halo/model/params/TagParam.java | 6 ++ .../ryanc/halo/repository/PostRepository.java | 8 ++ .../halo/service/PostCategoryService.java | 11 +++ .../cc/ryanc/halo/service/PostService.java | 14 ++++ .../cc/ryanc/halo/service/PostTagService.java | 10 +++ .../service/impl/PostCategoryServiceImpl.java | 21 ++++++ .../halo/service/impl/PostServiceImpl.java | 61 ++++++++++++++-- .../halo/service/impl/PostTagServiceImpl.java | 29 ++++++-- .../java/cc/ryanc/halo/utils/HaloUtils.java | 39 +++++++++- .../java/cc/ryanc/halo/utils/SlugUtils.java | 7 ++ .../controller/admin/api/PostController.java | 33 ++++++++- 12 files changed, 296 insertions(+), 16 deletions(-) create mode 100644 src/main/java/cc/ryanc/halo/model/params/PostParam.java diff --git a/src/main/java/cc/ryanc/halo/model/params/PostParam.java b/src/main/java/cc/ryanc/halo/model/params/PostParam.java new file mode 100644 index 000000000..518be8df6 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/model/params/PostParam.java @@ -0,0 +1,73 @@ +package cc.ryanc.halo.model.params; + +import cc.ryanc.halo.model.dto.base.InputConverter; +import cc.ryanc.halo.model.entity.Post; +import cc.ryanc.halo.model.enums.PostCreateFrom; +import cc.ryanc.halo.model.enums.PostStatus; +import cc.ryanc.halo.utils.HaloUtils; +import cn.hutool.crypto.digest.BCrypt; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; + +import javax.validation.constraints.Min; +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; +import java.util.Set; + +/** + * Post param. + * + * @author johnniang + * @date 3/21/19 + */ +@Data +public class PostParam implements InputConverter { + + @NotBlank(message = "Post title must not be blank") + @Size(max = 100, message = "Length of post title must not be more than {max}") + private String title; + + private PostStatus status = PostStatus.DRAFT; + + private String url; + + @NotBlank(message = "Post original content must not be blank") + private String originalContent; + + @Size(max = 255, message = "Length of post thumbnail must not be more than {max}") + private String thumbnail; + + private Boolean disallowComment = false; + + @Size(max = 255, message = "Length of post password must not be more than {max}") + private String password; + + @Size(max = 255, message = "Length of post template must not be more than {max}") + private String template; + + @Min(value = 0, message = "Post top priority must not be less than {value}") + private Integer topPriority = 0; + + private PostCreateFrom createFrom = PostCreateFrom.ADMIN; + + private Set tagIds; + + private Set categoryIds; + + @Override + public Post convertTo() { + if (StringUtils.isBlank(url)) { + url = HaloUtils.normalizeUrl(title); + } else { + url = HaloUtils.normalizeUrl(url); + } + + url = HaloUtils.initializeUrlIfBlank(url); + + Post post = InputConverter.super.convertTo(); + // Crypt password + post.setPassword(BCrypt.hashpw(password, BCrypt.gensalt())); + + return post; + } +} diff --git a/src/main/java/cc/ryanc/halo/model/params/TagParam.java b/src/main/java/cc/ryanc/halo/model/params/TagParam.java index 8901bfeef..3576aeb83 100644 --- a/src/main/java/cc/ryanc/halo/model/params/TagParam.java +++ b/src/main/java/cc/ryanc/halo/model/params/TagParam.java @@ -2,9 +2,12 @@ package cc.ryanc.halo.model.params; import cc.ryanc.halo.model.dto.base.InputConverter; import cc.ryanc.halo.model.entity.Tag; +import cc.ryanc.halo.utils.HaloUtils; import cc.ryanc.halo.utils.SlugUtils; import lombok.Data; +import org.apache.commons.lang3.RandomUtils; import org.apache.commons.lang3.StringUtils; +import org.springframework.util.Assert; import javax.validation.constraints.NotBlank; import javax.validation.constraints.Size; @@ -31,6 +34,9 @@ public class TagParam implements InputConverter { // Handle slug name slugName = SlugUtils.slugify(name); } + + slugName = HaloUtils.initializeUrlIfBlank(slugName); + return InputConverter.super.convertTo(); } } diff --git a/src/main/java/cc/ryanc/halo/repository/PostRepository.java b/src/main/java/cc/ryanc/halo/repository/PostRepository.java index b72000d38..f911922b1 100644 --- a/src/main/java/cc/ryanc/halo/repository/PostRepository.java +++ b/src/main/java/cc/ryanc/halo/repository/PostRepository.java @@ -37,4 +37,12 @@ public interface PostRepository extends BaseRepository, JpaSpecif * @return posts count */ long countByStatusAndType(@NonNull PostStatus status, @NonNull PostType type); + + /** + * Count by post url. + * + * @param url post url must not be blank + * @return the count + */ + long countByUrl(@NonNull String url); } diff --git a/src/main/java/cc/ryanc/halo/service/PostCategoryService.java b/src/main/java/cc/ryanc/halo/service/PostCategoryService.java index 6d74cc133..4b232f4e1 100644 --- a/src/main/java/cc/ryanc/halo/service/PostCategoryService.java +++ b/src/main/java/cc/ryanc/halo/service/PostCategoryService.java @@ -9,6 +9,7 @@ import org.springframework.lang.NonNull; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; /** * Post category service interface. @@ -44,4 +45,14 @@ public interface PostCategoryService extends CrudService */ @NonNull List listPostBy(@NonNull Integer categoryId); + + /** + * Creates post categories by post id and category id set. + * + * @param postId post id must not be null + * @param categoryIds category id set + * @return a list of post category + */ + @NonNull + List createBy(@NonNull Integer postId, Set categoryIds); } diff --git a/src/main/java/cc/ryanc/halo/service/PostService.java b/src/main/java/cc/ryanc/halo/service/PostService.java index a5cde3f49..093fd2ff1 100755 --- a/src/main/java/cc/ryanc/halo/service/PostService.java +++ b/src/main/java/cc/ryanc/halo/service/PostService.java @@ -12,8 +12,10 @@ import cc.ryanc.halo.service.base.CrudService; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.lang.NonNull; +import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Set; /** * Post service. @@ -111,4 +113,16 @@ public interface PostService extends CrudService { * @return posts count */ Long countByStatus(PostStatus status, PostType type); + + /** + * Creates post by post param. + * + * @param post post must not be null + * @param tagIds tag id set + * @param categoryIds category id set + * @return post created + */ + @NonNull + @Transactional + Post createBy(@NonNull Post post, Set tagIds, Set categoryIds); } diff --git a/src/main/java/cc/ryanc/halo/service/PostTagService.java b/src/main/java/cc/ryanc/halo/service/PostTagService.java index 1b45aef1d..ed481f4a7 100644 --- a/src/main/java/cc/ryanc/halo/service/PostTagService.java +++ b/src/main/java/cc/ryanc/halo/service/PostTagService.java @@ -11,6 +11,7 @@ import org.springframework.lang.NonNull; import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Set; /** * Post tag service interface. @@ -56,4 +57,13 @@ public interface PostTagService extends CrudService { @NonNull List listPostsBy(@NonNull Integer tagId); + /** + * Creates post tags by post id and tag id set. + * + * @param postId post id must not be null + * @param tagIds tag id set + * @return a list of post tag + */ + @NonNull + List createBy(@NonNull Integer postId, Set tagIds); } diff --git a/src/main/java/cc/ryanc/halo/service/impl/PostCategoryServiceImpl.java b/src/main/java/cc/ryanc/halo/service/impl/PostCategoryServiceImpl.java index b3ad4cacf..de4ba3519 100644 --- a/src/main/java/cc/ryanc/halo/service/impl/PostCategoryServiceImpl.java +++ b/src/main/java/cc/ryanc/halo/service/impl/PostCategoryServiceImpl.java @@ -14,6 +14,7 @@ import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import java.util.*; +import java.util.stream.Collectors; /** * Post category service implementation. @@ -85,4 +86,24 @@ public class PostCategoryServiceImpl extends AbstractCrudService createBy(Integer postId, Set categoryIds) { + Assert.notNull(postId, "Post id must not be null"); + + if (CollectionUtils.isEmpty(categoryIds)) { + return Collections.emptyList(); + } + + // Build post categories + List postCategories = categoryIds.stream().map(categoryId -> { + PostCategory postCategory = new PostCategory(); + postCategory.setPostId(postId); + postCategory.setCategoryId(categoryId); + return postCategory; + }).collect(Collectors.toList()); + + // Create them + return createInBatch(postCategories); + } } diff --git a/src/main/java/cc/ryanc/halo/service/impl/PostServiceImpl.java b/src/main/java/cc/ryanc/halo/service/impl/PostServiceImpl.java index 29626b7a3..05c044c11 100644 --- a/src/main/java/cc/ryanc/halo/service/impl/PostServiceImpl.java +++ b/src/main/java/cc/ryanc/halo/service/impl/PostServiceImpl.java @@ -1,21 +1,23 @@ package cc.ryanc.halo.service.impl; +import cc.ryanc.halo.exception.AlreadyExistsException; import cc.ryanc.halo.model.dto.CategoryOutputDTO; import cc.ryanc.halo.model.dto.TagOutputDTO; import cc.ryanc.halo.model.dto.post.PostMinimalOutputDTO; import cc.ryanc.halo.model.dto.post.PostSimpleOutputDTO; -import cc.ryanc.halo.model.entity.Category; -import cc.ryanc.halo.model.entity.Post; -import cc.ryanc.halo.model.entity.Tag; +import cc.ryanc.halo.model.entity.*; import cc.ryanc.halo.model.enums.PostStatus; import cc.ryanc.halo.model.enums.PostType; +import cc.ryanc.halo.model.params.PostParam; import cc.ryanc.halo.model.vo.PostListVO; import cc.ryanc.halo.repository.PostRepository; -import cc.ryanc.halo.service.PostCategoryService; -import cc.ryanc.halo.service.PostService; -import cc.ryanc.halo.service.PostTagService; +import cc.ryanc.halo.service.*; import cc.ryanc.halo.service.base.AbstractCrudService; +import cc.ryanc.halo.utils.HaloUtils; +import cc.ryanc.halo.utils.MarkdownUtils; import cc.ryanc.halo.utils.ServiceUtils; +import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; import org.springframework.data.domain.Pageable; @@ -34,20 +36,29 @@ import java.util.stream.Collectors; * @author johnniang * @author RYAN0UP */ +@Slf4j @Service public class PostServiceImpl extends AbstractCrudService implements PostService { private final PostRepository postRepository; + private final TagService tagService; + + private final CategoryService categoryService; + private final PostTagService postTagService; private final PostCategoryService postCategoryService; public PostServiceImpl(PostRepository postRepository, + TagService tagService, + CategoryService categoryService, PostTagService postTagService, PostCategoryService postCategoryService) { super(postRepository); this.postRepository = postRepository; + this.tagService = tagService; + this.categoryService = categoryService; this.postTagService = postTagService; this.postCategoryService = postCategoryService; } @@ -155,4 +166,42 @@ public class PostServiceImpl extends AbstractCrudService implemen public Long countByStatus(PostStatus status, PostType type) { return postRepository.countByStatusAndType(status, type); } + + @Override + public Post createBy(Post post, Set tagIds, Set categoryIds) { + Assert.notNull(post, "Post param must not be null"); + + // TODO Check url + long count = postRepository.countByUrl(post.getUrl()); + + if (count > 0) { + throw new AlreadyExistsException("The post url has been exist already").setErrorData(post.getUrl()); + } + + // Render content + post.setFormatContent(MarkdownUtils.renderMarkdown(post.getOriginalContent())); + + // TODO Handle thumbnail + + // Create post + create(post); + + // List all tags + List tags = tagService.listAllByIds(tagIds); + + // List all categories + List categories = categoryService.listAllByIds(categoryIds); + + // Create post tags + List postTags = postTagService.createBy(post.getId(), ServiceUtils.fetchProperty(tags, Tag::getId)); + + log.debug("Created post tags: [{}]", postTags); + + // Create post categories + List postCategories = postCategoryService.createBy(post.getId(), ServiceUtils.fetchProperty(categories, Category::getId)); + + log.debug("Created post categories: [{}]", postCategories); + + return post; + } } diff --git a/src/main/java/cc/ryanc/halo/service/impl/PostTagServiceImpl.java b/src/main/java/cc/ryanc/halo/service/impl/PostTagServiceImpl.java index c6cb01608..eb42852c8 100644 --- a/src/main/java/cc/ryanc/halo/service/impl/PostTagServiceImpl.java +++ b/src/main/java/cc/ryanc/halo/service/impl/PostTagServiceImpl.java @@ -60,11 +60,9 @@ public class PostTagServiceImpl extends AbstractCrudService im List tags = tagRepository.findAll(sort); // Find post count - return tags.stream().map(tag -> { - TagWithCountOutputDTO tagOutputDTO = new TagWithCountOutputDTO().convertFrom(tag); - - return tagOutputDTO; - }).collect(Collectors.toList()); + return tags.stream().map( + tag -> new TagWithCountOutputDTO().convertFrom(tag) + ).collect(Collectors.toList()); } @Override @@ -104,4 +102,25 @@ public class PostTagServiceImpl extends AbstractCrudService im return postRepository.findAllById(postIds); } + + @Override + public List createBy(Integer postId, Set tagIds) { + Assert.notNull(postId, "Post id must not be null"); + + if (CollectionUtils.isEmpty(tagIds)) { + return Collections.emptyList(); + } + + // Create post tags + Set postTags = tagIds.stream().map(tagId -> { + // Build post tag + PostTag postTag = new PostTag(); + postTag.setPostId(postId); + postTag.setTagId(tagId); + return postTag; + }).collect(Collectors.toSet()); + + // Create in batch + return createInBatch(postTags); + } } diff --git a/src/main/java/cc/ryanc/halo/utils/HaloUtils.java b/src/main/java/cc/ryanc/halo/utils/HaloUtils.java index 717be2b72..73233de80 100755 --- a/src/main/java/cc/ryanc/halo/utils/HaloUtils.java +++ b/src/main/java/cc/ryanc/halo/utils/HaloUtils.java @@ -5,7 +5,9 @@ import cn.hutool.core.util.StrUtil; import com.qiniu.common.Zone; import io.github.biezhi.ome.OhMyEmail; import lombok.extern.slf4j.Slf4j; +import org.apache.commons.lang3.StringUtils; import org.springframework.lang.NonNull; +import org.springframework.lang.Nullable; import org.springframework.util.Assert; import javax.imageio.ImageIO; @@ -40,6 +42,37 @@ public class HaloUtils { public final static int DEFAULT_PAGE_SIZE = 10; + /** + * Initialize url if blank. + * + * @param url url can be blank + * @return initial url + */ + @NonNull + public static String initializeUrlIfBlank(@Nullable String url) { + if (!StringUtils.isBlank(url)) { + return url; + } + // TODO Consider to UUID + return String.valueOf(System.currentTimeMillis()); + } + + /** + * Normalize url. + * + * @param url url must not be blank + * @return normalized url + */ + @NonNull + public static String normalizeUrl(@NonNull String url) { + Assert.hasText(url, "Url must not be blank"); + + StringUtils.removeEnd(url, "html"); + StringUtils.removeEnd(url, "htm"); + + return SlugUtils.slugify(url); + } + /** * Gets machine IP address. * @@ -200,7 +233,7 @@ public class HaloUtils { final BufferedImage image = ImageIO.read(new FileInputStream(file)); return image.getWidth() + "x" + image.getHeight(); } catch (Exception e) { - throw new RuntimeException("Failed to get read image file",e); + throw new RuntimeException("Failed to get read image file", e); } } @@ -223,7 +256,7 @@ public class HaloUtils { bufferedWriter = new BufferedWriter(fileWriter); bufferedWriter.write(data); } catch (Exception e) { - throw new RuntimeException("Failed to export file",e); + throw new RuntimeException("Failed to export file", e); } finally { if (null != bufferedWriter) { bufferedWriter.close(); @@ -297,7 +330,7 @@ public class HaloUtils { result.append(line); } } catch (Exception e) { - throw new RuntimeException("Failed to push posts to baidu",e); + throw new RuntimeException("Failed to push posts to baidu", e); } finally { try { if (null != out) { diff --git a/src/main/java/cc/ryanc/halo/utils/SlugUtils.java b/src/main/java/cc/ryanc/halo/utils/SlugUtils.java index 6efd7d435..14606e924 100644 --- a/src/main/java/cc/ryanc/halo/utils/SlugUtils.java +++ b/src/main/java/cc/ryanc/halo/utils/SlugUtils.java @@ -18,6 +18,13 @@ public class SlugUtils { private static final Pattern NON_LATIN = Pattern.compile("[^\\w-]"); private static final Pattern WHITESPACE = Pattern.compile("[\\s]"); + /** + * Slugify string. + * + * @param input input string must not be blank + * @return slug string + */ + @NonNull public static String slugify(@NonNull String input) { Assert.hasText(input, "Input string must not be blank"); diff --git a/src/main/java/cc/ryanc/halo/web/controller/admin/api/PostController.java b/src/main/java/cc/ryanc/halo/web/controller/admin/api/PostController.java index e04c40645..cb25de366 100644 --- a/src/main/java/cc/ryanc/halo/web/controller/admin/api/PostController.java +++ b/src/main/java/cc/ryanc/halo/web/controller/admin/api/PostController.java @@ -1,16 +1,20 @@ package cc.ryanc.halo.web.controller.admin.api; +import cc.ryanc.halo.model.dto.post.PostDetailOutputDTO; import cc.ryanc.halo.model.dto.post.PostMinimalOutputDTO; import cc.ryanc.halo.model.dto.post.PostSimpleOutputDTO; +import cc.ryanc.halo.model.entity.Post; import cc.ryanc.halo.model.enums.PostStatus; import cc.ryanc.halo.model.enums.PostType; -import cc.ryanc.halo.service.PostService; +import cc.ryanc.halo.model.params.PostParam; +import cc.ryanc.halo.service.*; import io.swagger.annotations.ApiOperation; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; import org.springframework.data.web.PageableDefault; import org.springframework.web.bind.annotation.*; +import javax.validation.Valid; import java.util.List; import static org.springframework.data.domain.Sort.Direction.DESC; @@ -27,8 +31,24 @@ public class PostController { private final PostService postService; - public PostController(PostService postService) { + private final TagService tagService; + + private final CategoryService categoryService; + + private final PostTagService postTagService; + + private final PostCategoryService postCategoryService; + + public PostController(PostService postService, + TagService tagService, + CategoryService categoryService, + PostTagService postTagService, + PostCategoryService postCategoryService) { this.postService = postService; + this.tagService = tagService; + this.categoryService = categoryService; + this.postTagService = postTagService; + this.postCategoryService = postCategoryService; } @GetMapping("latest") @@ -43,4 +63,13 @@ public class PostController { @PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable) { return postService.pageSimpleDtoByStatus(status, PostType.POST, pageable); } + + @PostMapping + public PostDetailOutputDTO createBy(@Valid @RequestBody PostParam postParam) { + // Convert to + Post post = postParam.convertTo(); + + return new PostDetailOutputDTO().convertFrom(postService.createBy(post, postParam.getTagIds(), postParam.getCategoryIds())); + } + }