diff --git a/src/main/java/cc/ryanc/halo/model/dto/CategoryOutputDTO.java b/src/main/java/cc/ryanc/halo/model/dto/CategoryOutputDTO.java index 9391095c1..f83a32161 100644 --- a/src/main/java/cc/ryanc/halo/model/dto/CategoryOutputDTO.java +++ b/src/main/java/cc/ryanc/halo/model/dto/CategoryOutputDTO.java @@ -13,6 +13,8 @@ import lombok.Data; @Data public class CategoryOutputDTO implements OutputConverter { + private Integer id; + private String name; private String slugName; diff --git a/src/main/java/cc/ryanc/halo/model/params/CategoryParam.java b/src/main/java/cc/ryanc/halo/model/params/CategoryParam.java new file mode 100644 index 000000000..1e2154637 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/model/params/CategoryParam.java @@ -0,0 +1,58 @@ +package cc.ryanc.halo.model.params; + +import cc.ryanc.halo.model.dto.base.InputConverter; +import cc.ryanc.halo.model.entity.Category; +import cc.ryanc.halo.utils.SlugUtils; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +/** + * Category param. + * + * @author johnniang + * @date 3/21/19 + */ +@Data +public class CategoryParam implements InputConverter { + + /** + * 分类名称 + */ + @NotBlank(message = "Category name must not be blank") + @Size(max = 50, message = "Length of category name must not be more than {max}") + private String name; + + /** + * 缩略名 + */ + @Size(max = 50, message = "Length of category slug name must not be more than {max}") + private String slugName; + + /** + * 描述 + */ + @Size(max = 100, message = "Length of category description must not be more than {max}") + private String description; + + /** + * 上级目录 + */ + private Integer parentId; + + @Override + public Category convertTo() { + // Handle default value + if (StringUtils.isBlank(slugName)) { + slugName = SlugUtils.slugify(name); + } + + if (parentId == null || parentId < 0) { + parentId = 0; + } + + return InputConverter.super.convertTo(); + } +} diff --git a/src/main/java/cc/ryanc/halo/model/vo/CategoryVO.java b/src/main/java/cc/ryanc/halo/model/vo/CategoryVO.java new file mode 100644 index 000000000..32182a6dc --- /dev/null +++ b/src/main/java/cc/ryanc/halo/model/vo/CategoryVO.java @@ -0,0 +1,18 @@ +package cc.ryanc.halo.model.vo; + +import cc.ryanc.halo.model.dto.CategoryOutputDTO; +import lombok.Data; + +import java.util.List; + +/** + * Category vo. + * + * @author johnniang + * @date 3/21/19 + */ +@Data +public class CategoryVO extends CategoryOutputDTO { + + private List children; +} diff --git a/src/main/java/cc/ryanc/halo/repository/CategoryRepository.java b/src/main/java/cc/ryanc/halo/repository/CategoryRepository.java index ab6f545a5..87d4ca13a 100644 --- a/src/main/java/cc/ryanc/halo/repository/CategoryRepository.java +++ b/src/main/java/cc/ryanc/halo/repository/CategoryRepository.java @@ -2,6 +2,7 @@ package cc.ryanc.halo.repository; import cc.ryanc.halo.model.entity.Category; import cc.ryanc.halo.repository.base.BaseRepository; +import org.springframework.lang.NonNull; /** * Category repository. @@ -9,4 +10,20 @@ import cc.ryanc.halo.repository.base.BaseRepository; * @author johnniang */ public interface CategoryRepository extends BaseRepository { + + /** + * Counts by category name. + * + * @param name category name must not be blank + * @return the count + */ + long countByName(@NonNull String name); + + /** + * Counts by category id. + * + * @param id category id must not be null + * @return the count + */ + long countById(@NonNull Integer id); } diff --git a/src/main/java/cc/ryanc/halo/service/CategoryService.java b/src/main/java/cc/ryanc/halo/service/CategoryService.java index 5830f3c45..528cca9fd 100755 --- a/src/main/java/cc/ryanc/halo/service/CategoryService.java +++ b/src/main/java/cc/ryanc/halo/service/CategoryService.java @@ -1,7 +1,12 @@ package cc.ryanc.halo.service; import cc.ryanc.halo.model.entity.Category; +import cc.ryanc.halo.model.vo.CategoryVO; import cc.ryanc.halo.service.base.CrudService; +import org.springframework.data.domain.Sort; +import org.springframework.lang.NonNull; + +import java.util.List; /** * Category service. @@ -15,5 +20,15 @@ public interface CategoryService extends CrudService { * * @param id id */ - void remove(Integer id); + @Deprecated + void remove(@NonNull Integer id); + + /** + * Lists as category tree. + * + * @param sort sort info must not be null + * @return a category tree + */ + @NonNull + List listAsTree(@NonNull Sort sort); } diff --git a/src/main/java/cc/ryanc/halo/service/impl/CategoryServiceImpl.java b/src/main/java/cc/ryanc/halo/service/impl/CategoryServiceImpl.java index 1983a431e..ac4d3f130 100644 --- a/src/main/java/cc/ryanc/halo/service/impl/CategoryServiceImpl.java +++ b/src/main/java/cc/ryanc/halo/service/impl/CategoryServiceImpl.java @@ -1,10 +1,22 @@ package cc.ryanc.halo.service.impl; +import cc.ryanc.halo.exception.AlreadyExistsException; +import cc.ryanc.halo.exception.NotFoundException; import cc.ryanc.halo.model.entity.Category; +import cc.ryanc.halo.model.vo.CategoryVO; import cc.ryanc.halo.repository.CategoryRepository; import cc.ryanc.halo.service.CategoryService; import cc.ryanc.halo.service.base.AbstractCrudService; +import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Sort; +import org.springframework.lang.NonNull; import org.springframework.stereotype.Service; +import org.springframework.util.Assert; +import org.springframework.util.CollectionUtils; + +import java.util.Collections; +import java.util.LinkedList; +import java.util.List; /** * CategoryService implementation class @@ -12,6 +24,7 @@ import org.springframework.stereotype.Service; * @author : RYAN0UP * @date : 2019-03-14 */ +@Slf4j @Service public class CategoryServiceImpl extends AbstractCrudService implements CategoryService { @@ -31,4 +44,107 @@ public class CategoryServiceImpl extends AbstractCrudService public void remove(Integer id) { // TODO 删除分类,以及和文章的对应关系 } + + @Override + public Category create(Category category) { + Assert.notNull(category, "Category to create must not be null"); + + // Check the category name + long count = categoryRepository.countByName(category.getName()); + + if (count > 0) { + log.error("Category has exist already: [{}]", category); + throw new AlreadyExistsException("The category has exist already"); + } + + // Check parent id + if (category.getParentId() > 0) { + count = categoryRepository.countById(category.getParentId()); + + if (count == 0) { + log.error("Parent category with id: [{}] was not found, category: [{}]", category.getParentId(), category); + throw new NotFoundException("Parent category with id = " + category.getParentId() + " was not found"); + } + } + + // Create it + return super.create(category); + } + + @Override + public List listAsTree(Sort sort) { + Assert.notNull(sort, "Sort info must not be null"); + + // List all category + List categories = listAll(sort); + + if (CollectionUtils.isEmpty(categories)) { + return Collections.emptyList(); + } + + // Create top category + CategoryVO topLevelCategory = createTopLevelCategory(); + + // Concrete the tree + concreteTree(topLevelCategory, categories); + + return topLevelCategory.getChildren(); + } + + /** + * Concrete category tree. + * + * @param parentCategory parent category vo must not be null + * @param categories a list of category + */ + private void concreteTree(CategoryVO parentCategory, List categories) { + Assert.notNull(parentCategory, "Parent category must not be null"); + + if (CollectionUtils.isEmpty(categories)) { + return; + } + + // Create children container for removing after + List children = new LinkedList<>(); + + categories.forEach(category -> { + if (parentCategory.getId().equals(category.getParentId())) { + // Save child category + children.add(category); + + // Convert to child category vo + CategoryVO child = new CategoryVO().convertFrom(category); + + // Init children if absent + if (parentCategory.getChildren() == null) { + parentCategory.setChildren(new LinkedList<>()); + } + parentCategory.getChildren().add(child); + } + }); + + // Remove all child categories + categories.removeAll(children); + + // Foreach children vos + if (!CollectionUtils.isEmpty(parentCategory.getChildren())) { + parentCategory.getChildren().forEach(childCategory -> concreteTree(childCategory, categories)); + } + } + + /** + * Creates a top level category. + * + * @return top level category with id 0 + */ + @NonNull + private CategoryVO createTopLevelCategory() { + CategoryVO topCategory = new CategoryVO(); + // Set default value + topCategory.setId(0); + topCategory.setChildren(new LinkedList<>()); + topCategory.setParentId(-1); + + return topCategory; + } } diff --git a/src/main/java/cc/ryanc/halo/web/controller/admin/api/CategoryController.java b/src/main/java/cc/ryanc/halo/web/controller/admin/api/CategoryController.java new file mode 100644 index 000000000..e4108d62e --- /dev/null +++ b/src/main/java/cc/ryanc/halo/web/controller/admin/api/CategoryController.java @@ -0,0 +1,48 @@ +package cc.ryanc.halo.web.controller.admin.api; + +import cc.ryanc.halo.model.dto.CategoryOutputDTO; +import cc.ryanc.halo.model.entity.Category; +import cc.ryanc.halo.model.params.CategoryParam; +import cc.ryanc.halo.model.vo.CategoryVO; +import cc.ryanc.halo.service.CategoryService; +import io.swagger.annotations.ApiOperation; +import org.springframework.data.domain.Sort; +import org.springframework.data.web.SortDefault; +import org.springframework.web.bind.annotation.*; + +import javax.validation.Valid; +import java.util.List; + +import static org.springframework.data.domain.Sort.Direction.ASC; + +/** + * Category controller. + * + * @author johnniang + * @date 3/21/19 + */ +@RestController +@RequestMapping("/admin/api/categories") +public class CategoryController { + + private final CategoryService categoryService; + + public CategoryController(CategoryService categoryService) { + this.categoryService = categoryService; + } + + @GetMapping("tree") + @ApiOperation("List as category tree") + public List listAsTree(@SortDefault(sort = "name", direction = ASC) Sort sort) { + return categoryService.listAsTree(sort); + } + + @PostMapping + public CategoryOutputDTO createBy(@Valid @RequestBody CategoryParam categoryParam) { + // Convert to category + Category category = categoryParam.convertTo(); + + // Save it + return new CategoryOutputDTO().convertFrom(categoryService.create(category)); + } +} 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 5e9021196..e04c40645 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 @@ -38,7 +38,7 @@ public class PostController { } @GetMapping("status/{status}") - @ApiOperation("") + @ApiOperation("Gets a page of post by post status") public Page pageByStatus(@PathVariable(name = "status") PostStatus status, @PageableDefault(sort = "updateTime", direction = DESC) Pageable pageable) { return postService.pageSimpleDtoByStatus(status, PostType.POST, pageable);