diff --git a/pom.xml b/pom.xml index 42152d9e7..d228f4a0e 100755 --- a/pom.xml +++ b/pom.xml @@ -49,6 +49,7 @@ 0.4.8 0.12.1 3.8.1 + 2.3 diff --git a/src/main/java/cc/ryanc/halo/model/dto/TagWithCountOutputDTO.java b/src/main/java/cc/ryanc/halo/model/dto/TagWithCountOutputDTO.java new file mode 100644 index 000000000..8236c8f32 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/model/dto/TagWithCountOutputDTO.java @@ -0,0 +1,16 @@ +package cc.ryanc.halo.model.dto; + +import lombok.Data; + +/** + * Tag with post count output dto. + * + * @author johnniang + * @date 3/20/19 + */ +@Data +public class TagWithCountOutputDTO extends TagOutputDTO { + + private Long postCount; + +} diff --git a/src/main/java/cc/ryanc/halo/model/entity/Attachment.java b/src/main/java/cc/ryanc/halo/model/entity/Attachment.java index bad0f29ee..c65df426a 100644 --- a/src/main/java/cc/ryanc/halo/model/entity/Attachment.java +++ b/src/main/java/cc/ryanc/halo/model/entity/Attachment.java @@ -7,7 +7,6 @@ import org.hibernate.annotations.SQLDelete; import org.hibernate.annotations.Where; import javax.persistence.*; -import java.time.Instant; /** * Attachment entity diff --git a/src/main/java/cc/ryanc/halo/model/params/TagParam.java b/src/main/java/cc/ryanc/halo/model/params/TagParam.java new file mode 100644 index 000000000..8901bfeef --- /dev/null +++ b/src/main/java/cc/ryanc/halo/model/params/TagParam.java @@ -0,0 +1,36 @@ +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.SlugUtils; +import lombok.Data; +import org.apache.commons.lang3.StringUtils; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.Size; + +/** + * Tag param. + * + * @author johnniang + * @date 3/20/19 + */ +@Data +public class TagParam implements InputConverter { + + @NotBlank(message = "Tag name must not be blank") + @Size(max = 255, message = "Length of tag name must not be more than {max}") + private String name; + + @Size(max = 255, message = "Length of tag slug name must not be more than {max}") + private String slugName; + + @Override + public Tag convertTo() { + if (StringUtils.isBlank(slugName)) { + // Handle slug name + slugName = SlugUtils.slugify(name); + } + return InputConverter.super.convertTo(); + } +} diff --git a/src/main/java/cc/ryanc/halo/repository/PostTagRepository.java b/src/main/java/cc/ryanc/halo/repository/PostTagRepository.java index 4423bded4..f4abc2a76 100644 --- a/src/main/java/cc/ryanc/halo/repository/PostTagRepository.java +++ b/src/main/java/cc/ryanc/halo/repository/PostTagRepository.java @@ -62,4 +62,5 @@ public interface PostTagRepository extends BaseRepository { */ @NonNull List findAllByPostIdIn(@NonNull Iterable postIds); + } diff --git a/src/main/java/cc/ryanc/halo/repository/TagRepository.java b/src/main/java/cc/ryanc/halo/repository/TagRepository.java index 43f7796d5..11e1881e5 100644 --- a/src/main/java/cc/ryanc/halo/repository/TagRepository.java +++ b/src/main/java/cc/ryanc/halo/repository/TagRepository.java @@ -2,6 +2,7 @@ package cc.ryanc.halo.repository; import cc.ryanc.halo.model.entity.Tag; import cc.ryanc.halo.repository.base.BaseRepository; +import org.springframework.lang.NonNull; /** * Tag repository. @@ -10,4 +11,12 @@ import cc.ryanc.halo.repository.base.BaseRepository; */ public interface TagRepository extends BaseRepository { + /** + * Count by name or slug name. + * + * @param name tag name must not be null + * @param slugName tag slug name must not be null + * @return tag count + */ + long countByNameOrSlugName(@NonNull String name, @NonNull String slugName); } diff --git a/src/main/java/cc/ryanc/halo/service/PostTagService.java b/src/main/java/cc/ryanc/halo/service/PostTagService.java index e55708be8..1b45aef1d 100644 --- a/src/main/java/cc/ryanc/halo/service/PostTagService.java +++ b/src/main/java/cc/ryanc/halo/service/PostTagService.java @@ -1,9 +1,11 @@ package cc.ryanc.halo.service; +import cc.ryanc.halo.model.dto.TagWithCountOutputDTO; import cc.ryanc.halo.model.entity.Post; import cc.ryanc.halo.model.entity.PostTag; import cc.ryanc.halo.model.entity.Tag; import cc.ryanc.halo.service.base.CrudService; +import org.springframework.data.domain.Sort; import org.springframework.lang.NonNull; import java.util.Collection; @@ -25,10 +27,19 @@ public interface PostTagService extends CrudService { * @return a list of tag */ @NonNull - List listTagBy(@NonNull Integer postId); + List listTagsBy(@NonNull Integer postId); /** - * Lists tag list map by post id. + * List tag with post count output dtos. + * + * @param sort sort info + * @return a list of tag with post count output dto + */ + @NonNull + List listTagWithCountDtos(@NonNull Sort sort); + + /** + * Lists tags list map by post id. * * @param postIds post id collection * @return tag map (key: postId, value: a list of tags) @@ -37,12 +48,12 @@ public interface PostTagService extends CrudService { Map> listTagListMapBy(Collection postIds); /** - * Lists post by tag id. + * Lists posts by tag id. * * @param tagId tag id must not be null * @return a list of post */ @NonNull - List listPostBy(@NonNull Integer tagId); + List listPostsBy(@NonNull Integer tagId); } 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 efbcd6025..c6cb01608 100644 --- a/src/main/java/cc/ryanc/halo/service/impl/PostTagServiceImpl.java +++ b/src/main/java/cc/ryanc/halo/service/impl/PostTagServiceImpl.java @@ -1,5 +1,6 @@ package cc.ryanc.halo.service.impl; +import cc.ryanc.halo.model.dto.TagWithCountOutputDTO; import cc.ryanc.halo.model.entity.Post; import cc.ryanc.halo.model.entity.PostTag; import cc.ryanc.halo.model.entity.Tag; @@ -9,11 +10,13 @@ import cc.ryanc.halo.repository.TagRepository; import cc.ryanc.halo.service.PostTagService; import cc.ryanc.halo.service.base.AbstractCrudService; import cc.ryanc.halo.utils.ServiceUtils; +import org.springframework.data.domain.Sort; import org.springframework.stereotype.Service; import org.springframework.util.Assert; import org.springframework.util.CollectionUtils; import java.util.*; +import java.util.stream.Collectors; /** * Post tag service implementation. @@ -40,7 +43,7 @@ public class PostTagServiceImpl extends AbstractCrudService im } @Override - public List listTagBy(Integer postId) { + public List listTagsBy(Integer postId) { Assert.notNull(postId, "Post id must not be null"); // Find all tag ids @@ -49,6 +52,21 @@ public class PostTagServiceImpl extends AbstractCrudService im return tagRepository.findAllById(tagIds); } + @Override + public List listTagWithCountDtos(Sort sort) { + Assert.notNull(sort, "Sort info must not be null"); + + // Find all tags + List tags = tagRepository.findAll(sort); + + // Find post count + return tags.stream().map(tag -> { + TagWithCountOutputDTO tagOutputDTO = new TagWithCountOutputDTO().convertFrom(tag); + + return tagOutputDTO; + }).collect(Collectors.toList()); + } + @Override public Map> listTagListMapBy(Collection postIds) { if (CollectionUtils.isEmpty(postIds)) { @@ -78,7 +96,7 @@ public class PostTagServiceImpl extends AbstractCrudService im @Override - public List listPostBy(Integer tagId) { + public List listPostsBy(Integer tagId) { Assert.notNull(tagId, "Tag id must not be null"); // Find all post ids diff --git a/src/main/java/cc/ryanc/halo/service/impl/TagServiceImpl.java b/src/main/java/cc/ryanc/halo/service/impl/TagServiceImpl.java index f149acbf6..78a487e52 100644 --- a/src/main/java/cc/ryanc/halo/service/impl/TagServiceImpl.java +++ b/src/main/java/cc/ryanc/halo/service/impl/TagServiceImpl.java @@ -1,9 +1,11 @@ package cc.ryanc.halo.service.impl; +import cc.ryanc.halo.exception.AlreadyExistsException; import cc.ryanc.halo.model.entity.Tag; import cc.ryanc.halo.repository.TagRepository; import cc.ryanc.halo.service.TagService; import cc.ryanc.halo.service.base.AbstractCrudService; +import lombok.extern.slf4j.Slf4j; import org.springframework.stereotype.Service; /** @@ -12,6 +14,7 @@ import org.springframework.stereotype.Service; * @author : RYAN0UP * @date : 2019-03-14 */ +@Slf4j @Service public class TagServiceImpl extends AbstractCrudService implements TagService { @@ -29,6 +32,22 @@ public class TagServiceImpl extends AbstractCrudService implements */ @Override public void remove(Integer id) { - // TODO 删除标签,以及对应的文章关系 + // TODO 删除标签,以及对应的文章关系 + } + + @Override + public Tag create(Tag tag) { + // Check if the tag is exist + long count = tagRepository.countByNameOrSlugName(tag.getName(), tag.getSlugName()); + + log.debug("Tag count: [{}]", count); + + if (count > 0) { + // If the tag has exist already + throw new AlreadyExistsException("The tag has already exist").setErrorData(tag); + } + + // Get tag name + return super.create(tag); } } diff --git a/src/main/java/cc/ryanc/halo/utils/SlugUtils.java b/src/main/java/cc/ryanc/halo/utils/SlugUtils.java new file mode 100644 index 000000000..6efd7d435 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/utils/SlugUtils.java @@ -0,0 +1,29 @@ +package cc.ryanc.halo.utils; + +import org.springframework.lang.NonNull; +import org.springframework.util.Assert; + +import java.text.Normalizer; +import java.util.Locale; +import java.util.regex.Pattern; + +/** + * Slugify utilities. + * + * @author johnniang + * @date 3/20/19 + */ +public class SlugUtils { + + private static final Pattern NON_LATIN = Pattern.compile("[^\\w-]"); + private static final Pattern WHITESPACE = Pattern.compile("[\\s]"); + + public static String slugify(@NonNull String input) { + Assert.hasText(input, "Input string must not be blank"); + + String withoutWhitespace = WHITESPACE.matcher(input).replaceAll("-"); + String normalized = Normalizer.normalize(withoutWhitespace, Normalizer.Form.NFKD); + String slug = NON_LATIN.matcher(normalized).replaceAll(""); + return slug.toLowerCase(Locale.ENGLISH); + } +} diff --git a/src/main/java/cc/ryanc/halo/web/controller/admin/api/TagController.java b/src/main/java/cc/ryanc/halo/web/controller/admin/api/TagController.java new file mode 100644 index 000000000..c896a1e79 --- /dev/null +++ b/src/main/java/cc/ryanc/halo/web/controller/admin/api/TagController.java @@ -0,0 +1,48 @@ +package cc.ryanc.halo.web.controller.admin.api; + +import cc.ryanc.halo.model.dto.TagOutputDTO; +import cc.ryanc.halo.model.entity.Tag; +import cc.ryanc.halo.model.params.TagParam; +import cc.ryanc.halo.service.TagService; +import lombok.extern.slf4j.Slf4j; +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; + +/** + * Tag controller. + * + * @author johnniang + * @date 3/20/19 + */ +@Slf4j +@RestController +@RequestMapping("/admin/api/tags") +public class TagController { + + private final TagService tagService; + + public TagController(TagService tagService) { + this.tagService = tagService; + } + + @GetMapping + public List listTags(@SortDefault(sort = "updateTime", direction = Sort.Direction.DESC) Sort sort) { + + return null; + } + + @PostMapping + public TagOutputDTO createTag(@Valid @RequestBody TagParam tagParam) { + // Convert to tag + Tag tag = tagParam.convertTo(); + + log.debug("Tag to be created: [{}]", tag); + + // Create and convert + return new TagOutputDTO().convertFrom(tagService.create(tag)); + } +} diff --git a/src/test/java/cc/ryanc/halo/utils/SlugUtilsTest.java b/src/test/java/cc/ryanc/halo/utils/SlugUtilsTest.java new file mode 100644 index 000000000..839f297d7 --- /dev/null +++ b/src/test/java/cc/ryanc/halo/utils/SlugUtilsTest.java @@ -0,0 +1,21 @@ +package cc.ryanc.halo.utils; + +import org.junit.Assert; +import org.junit.Test; + +import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.*; + +/** + * @author johnniang + * @date 3/20/19 + */ +public class SlugUtilsTest { + + @Test + public void makeSlugTest() { + String slugResult = SlugUtils.slugify("Hello World"); + + Assert.assertThat(slugResult, equalTo("hello-world")); + } +} \ No newline at end of file