Commit fd33956f by wzy

Article

parent 19daf2e5
package com.wzy.tacocloud.application; package com.wzy.tacocloud.application;
import io.spring.application.data.ArticleData; import com.wzy.tacocloud.application.data.ArticleData;
import io.spring.application.data.ArticleDataList; import com.wzy.tacocloud.application.data.ArticleDataList;
import io.spring.application.data.ArticleFavoriteCount; import com.wzy.tacocloud.application.data.ArticleFavoriteCount;
import io.spring.core.user.User; import com.wzy.tacocloud.core.user.User;
import io.spring.infrastructure.mybatis.readservice.ArticleFavoritesReadService; import com.wzy.tacocloud.infrastructure.mybatis.readservice.ArticleFavoritesReadService;
import io.spring.infrastructure.mybatis.readservice.ArticleReadService; import com.wzy.tacocloud.infrastructure.mybatis.readservice.ArticleReadService;
import io.spring.infrastructure.mybatis.readservice.UserRelationshipQueryService; import com.wzy.tacocloud.infrastructure.mybatis.readservice.UserRelationshipQueryService;
import org.joda.time.DateTime; import org.joda.time.DateTime;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
......
package com.wzy.tacocloud.application;
import com.wzy.tacocloud.application.CursorPager.Direction;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
public class CursorPageParameter<T> {
private static final int MAX_LIMIT = 1000;
private int limit = 20;
private T cursor;
private Direction direction;
public CursorPageParameter(T cursor, int limit, Direction direction) {
setLimit(limit);
setCursor(cursor);
setDirection(direction);
}
public boolean isNext() {
return direction == Direction.NEXT;
}
public int getQueryLimit() {
return limit + 1;
}
private void setCursor(T cursor) {
this.cursor = cursor;
}
private void setLimit(int limit) {
if (limit > MAX_LIMIT) {
this.limit = MAX_LIMIT;
} else if (limit > 0) {
this.limit = limit;
}
}
}
package com.wzy.tacocloud.application;
import lombok.Getter;
import java.util.List;
@Getter
public class CursorPager<T extends Node> {
private final List<T> data;
private final boolean next;
private final boolean previous;
public CursorPager(List<T> data, Direction direction, boolean hasExtra) {
this.data = data;
if (direction == Direction.NEXT) {
this.previous = false;
this.next = hasExtra;
} else {
this.next = false;
this.previous = hasExtra;
}
}
public boolean hasNext() {
return next;
}
public boolean hasPrevious() {
return previous;
}
public PageCursor getStartCursor() {
return data.isEmpty() ? null : data.get(0).getCursor();
}
public PageCursor getEndCursor() {
return data.isEmpty() ? null : data.get(data.size() - 1).getCursor();
}
public enum Direction {
PREV,
NEXT
}
}
package com.wzy.tacocloud.application;
import org.joda.time.DateTime;
import org.joda.time.DateTimeZone;
public class DateTimeCursor extends PageCursor<DateTime> {
public DateTimeCursor(DateTime data) {
super(data);
}
@Override
public String toString() {
return String.valueOf(getData().getMillis());
}
public static DateTime parse(String cursor) {
if (cursor == null) {
return null;
}
return new DateTime().withMillis(Long.parseLong(cursor)).withZone(DateTimeZone.UTC);
}
}
package com.wzy.tacocloud.application;
public interface Node {
PageCursor<?> getCursor();
}
package com.wzy.tacocloud.application;
import lombok.Data;
import lombok.NoArgsConstructor;
@NoArgsConstructor
@Data
public class Page {
private static final int MAX_LIMIT = 100;
private int offset = 0;
private int limit = 20;
public Page(int offset, int limit) {
setOffset(offset);
setLimit(limit);
}
private void setOffset(int offset) {
if (offset > 0) {
this.offset = offset;
}
}
private void setLimit(int limit) {
if (limit > MAX_LIMIT) {
this.limit = MAX_LIMIT;
} else if (limit > 0) {
this.limit = limit;
}
}
}
package com.wzy.tacocloud.application;
public abstract class PageCursor<T> {
private final T data;
public PageCursor(T data) {
this.data = data;
}
public T getData() {
return data;
}
@Override
public String toString() {
return data.toString();
}
}
package com.wzy.tacocloud.application.article; package com.wzy.tacocloud.application.article;
import io.spring.core.article.Article; import com.wzy.tacocloud.core.article.Article;
import io.spring.core.article.ArticleRepository; import com.wzy.tacocloud.core.article.ArticleRepository;
import io.spring.core.user.User; import com.wzy.tacocloud.core.user.User;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import javax.validation.Valid; import javax.validation.Valid;
@Service @Service
...@@ -26,7 +25,6 @@ public class ArticleCommandService { ...@@ -26,7 +25,6 @@ public class ArticleCommandService {
newArticleParam.getTitle(), newArticleParam.getTitle(),
newArticleParam.getDescription(), newArticleParam.getDescription(),
newArticleParam.getBody(), newArticleParam.getBody(),
newArticleParam.getTagList(),
creator.getId()); creator.getId());
articleRepository.save(article); articleRepository.save(article);
return article; return article;
......
package com.wzy.tacocloud.application.article; package com.wzy.tacocloud.application.article;
import io.spring.application.ArticleQueryService; import com.wzy.tacocloud.application.ArticleQueryService;
import io.spring.core.article.Article; import com.wzy.tacocloud.core.article.Article;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import javax.validation.ConstraintValidator; import javax.validation.ConstraintValidator;
import javax.validation.ConstraintValidatorContext; import javax.validation.ConstraintValidatorContext;
class DuplicatedArticleValidator class DuplicatedArticleValidator
implements ConstraintValidator<io.spring.application.article.DuplicatedArticleConstraint, String> { implements ConstraintValidator<com.wzy.tacocloud.application.article.DuplicatedArticleConstraint, String> {
@Autowired private ArticleQueryService articleQueryService; @Autowired private ArticleQueryService articleQueryService;
......
...@@ -16,7 +16,7 @@ import java.util.List; ...@@ -16,7 +16,7 @@ import java.util.List;
@Builder @Builder
public class NewArticleParam { public class NewArticleParam {
@NotBlank(message = "can't be empty") @NotBlank(message = "can't be empty")
@io.spring.application.article.DuplicatedArticleConstraint @com.wzy.tacocloud.application.article.DuplicatedArticleConstraint
private String title; private String title;
@NotBlank(message = "can't be empty") @NotBlank(message = "can't be empty")
...@@ -24,6 +24,4 @@ public class NewArticleParam { ...@@ -24,6 +24,4 @@ public class NewArticleParam {
@NotBlank(message = "can't be empty") @NotBlank(message = "can't be empty")
private String body; private String body;
private List<String> tagList;
} }
package com.wzy.tacocloud.application.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.wzy.tacocloud.application.DateTimeCursor;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.joda.time.DateTime;
import java.util.List;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ArticleData implements com.wzy.tacocloud.application.Node {
private String id;
private String slug;
private String title;
private String description;
private String body;
private boolean favorited;
private int favoritesCount;
private DateTime createdAt;
private DateTime updatedAt;
private List<String> tagList;
@JsonProperty("author")
private ProfileData profileData;
@Override
public DateTimeCursor getCursor() {
return new DateTimeCursor(updatedAt);
}
}
package com.wzy.tacocloud.application.data;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Getter;
import java.util.List;
@Getter
public class ArticleDataList {
@JsonProperty("articles")
private final List<ArticleData> articleDatas;
@JsonProperty("articlesCount")
private final int count;
public ArticleDataList(List<ArticleData> articleDatas, int count) {
this.articleDatas = articleDatas;
this.count = count;
}
}
package com.wzy.tacocloud.application.data;
import lombok.Value;
@Value
public class ArticleFavoriteCount {
String id;
Integer count;
}
package com.wzy.tacocloud.application.data;
import com.fasterxml.jackson.annotation.JsonIgnore;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@NoArgsConstructor
@AllArgsConstructor
public class ProfileData {
@JsonIgnore
private String id;
private String username;
private String bio;
private String image;
private boolean following;
}
package com.wzy.tacocloud.controller; package com.wzy.tacocloud.controller;
import io.spring.api.exception.NoAuthorizationException; import com.wzy.tacocloud.controller.exception.NoAuthorizationException;
import io.spring.api.exception.ResourceNotFoundException; import com.wzy.tacocloud.controller.exception.ResourceNotFoundException;
import io.spring.application.ArticleQueryService; import com.wzy.tacocloud.application.ArticleQueryService;
import io.spring.application.article.ArticleCommandService; import com.wzy.tacocloud.application.article.ArticleCommandService;
import io.spring.application.article.UpdateArticleParam; import com.wzy.tacocloud.application.article.UpdateArticleParam;
import io.spring.application.data.ArticleData; import com.wzy.tacocloud.application.data.ArticleData;
import io.spring.core.article.ArticleRepository; import com.wzy.tacocloud.core.article.ArticleRepository;
import io.spring.core.service.AuthorizationService; import com.wzy.tacocloud.core.service.AuthorizationService;
import io.spring.core.user.User; import com.wzy.tacocloud.core.user.User;
import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
......
package com.wzy.tacocloud.controller.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(HttpStatus.FORBIDDEN)
public class NoAuthorizationException extends RuntimeException {
}
package com.wzy.tacocloud.controller.exception;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.ResponseStatus;
@ResponseStatus(value = HttpStatus.NOT_FOUND)
public class ResourceNotFoundException extends RuntimeException {
}
package com.wzy.tacocloud.core.article; package com.wzy.tacocloud.core.article;
import io.spring.Util; import com.wzy.tacocloud.Util;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import lombok.Getter; import lombok.Getter;
import lombok.NoArgsConstructor; import lombok.NoArgsConstructor;
...@@ -26,15 +26,14 @@ public class Article { ...@@ -26,15 +26,14 @@ public class Article {
private DateTime updatedAt; private DateTime updatedAt;
public Article( public Article(
String title, String description, String body, List<String> tagList, String userId) { String title, String description, String body, String userId) {
this(title, description, body, tagList, userId, new DateTime()); this(title, description, body, userId, new DateTime());
} }
public Article( public Article(
String title, String title,
String description, String description,
String body, String body,
List<String> tagList,
String userId, String userId,
DateTime createdAt) { DateTime createdAt) {
this.id = UUID.randomUUID().toString(); this.id = UUID.randomUUID().toString();
......
...@@ -10,6 +10,5 @@ public interface ArticleRepository { ...@@ -10,6 +10,5 @@ public interface ArticleRepository {
Optional<Article> findBySlug(String slug); Optional<Article> findBySlug(String slug);
void remove(Article article); void remove(Article article);
} }
package com.wzy.tacocloud.core.comment;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.NoArgsConstructor;
import org.joda.time.DateTime;
import java.util.UUID;
@Getter
@NoArgsConstructor
@EqualsAndHashCode(of = "id")
public class Comment {
private String id;
private String body;
private String userId;
private String articleId;
private DateTime createdAt;
public Comment(String body, String userId, String articleId) {
this.id = UUID.randomUUID().toString();
this.body = body;
this.userId = userId;
this.articleId = articleId;
this.createdAt = new DateTime();
}
}
package com.wzy.tacocloud.core.service;
import com.wzy.tacocloud.core.article.Article;
import com.wzy.tacocloud.core.comment.Comment;
import com.wzy.tacocloud.core.user.User;
public class AuthorizationService {
public static boolean canWriteArticle(User user, Article article) {
return user.getId().equals(article.getUserId());
}
public static boolean canWriteComment(User user, Article article, Comment comment) {
return user.getId().equals(article.getUserId()) || user.getId().equals(comment.getUserId());
}
}
package com.wzy.tacocloud.infrastructure.mybatis.mapper; package com.wzy.tacocloud.infrastructure.mybatis.mapper;
import io.spring.core.article.Article; import com.wzy.tacocloud.core.article.Article;
import io.spring.core.article.Tag;
import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Param;
import org.springframework.stereotype.Repository;
@Mapper @Mapper
public interface ArticleMapper { public interface ArticleMapper {
...@@ -11,8 +11,6 @@ public interface ArticleMapper { ...@@ -11,8 +11,6 @@ public interface ArticleMapper {
Article findById(@Param("id") String id); Article findById(@Param("id") String id);
void insertArticleTagRelation(@Param("articleId") String articleId, @Param("tagId") String tagId);
Article findBySlug(@Param("slug") String slug); Article findBySlug(@Param("slug") String slug);
void update(@Param("article") Article article); void update(@Param("article") Article article);
......
package com.wzy.tacocloud.infrastructure.mybatis.readservice;
import com.wzy.tacocloud.application.data.ArticleFavoriteCount;
import com.wzy.tacocloud.core.user.User;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Set;
@Mapper
public interface ArticleFavoritesReadService {
boolean isUserFavorite(@Param("userId") String userId, @Param("articleId") String articleId);
int articleFavoriteCount(@Param("articleId") String articleId);
List<ArticleFavoriteCount> articlesFavoriteCount(@Param("ids") List<String> ids);
Set<String> userFavorites(@Param("ids") List<String> ids, @Param("currentUser") User currentUser);
}
package com.wzy.tacocloud.infrastructure.mybatis.readservice;
import com.wzy.tacocloud.application.CursorPageParameter;
import com.wzy.tacocloud.application.Page;
import com.wzy.tacocloud.application.data.ArticleData;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
@Mapper
public interface ArticleReadService {
ArticleData findById(@Param("id") String id);
ArticleData findBySlug(@Param("slug") String slug);
List<String> queryArticles(
@Param("tag") String tag,
@Param("author") String author,
@Param("favoritedBy") String favoritedBy,
@Param("page") Page page);
int countArticle(
@Param("tag") String tag,
@Param("author") String author,
@Param("favoritedBy") String favoritedBy);
List<ArticleData> findArticles(@Param("articleIds") List<String> articleIds);
List<ArticleData> findArticlesOfAuthors(
@Param("authors") List<String> authors, @Param("page") Page page);
List<ArticleData> findArticlesOfAuthorsWithCursor(
@Param("authors") List<String> authors, @Param("page") CursorPageParameter page);
int countFeedSize(@Param("authors") List<String> authors);
List<String> findArticlesWithCursor(
@Param("tag") String tag,
@Param("author") String author,
@Param("favoritedBy") String favoritedBy,
@Param("page") CursorPageParameter page);
}
package com.wzy.tacocloud.infrastructure.mybatis.readservice;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Set;
@Mapper
public interface UserRelationshipQueryService {
boolean isUserFollowing(@Param("userId") String userId, @Param("anotherUserId") String anotherUserId);
Set<String> followingAuthors(@Param("userId") String userId, @Param("ids") List<String> ids);
List<String> followedUsers(@Param("userId") String userId);
}
package com.wzy.tacocloud.infrastructure.repository;
import com.wzy.tacocloud.core.article.Article;
import com.wzy.tacocloud.core.article.ArticleRepository;
import com.wzy.tacocloud.infrastructure.mybatis.mapper.ArticleMapper;
import org.springframework.stereotype.Repository;
import org.springframework.transaction.annotation.Transactional;
import java.util.Optional;
@Repository
public class MyBatisArticleRepository implements ArticleRepository {
private final ArticleMapper articleMapper;
public MyBatisArticleRepository(ArticleMapper articleMapper) {
this.articleMapper = articleMapper;
}
@Override
@Transactional
public void save(Article article) {
if (articleMapper.findById(article.getId()) == null) {
createNew(article);
} else {
articleMapper.update(article);
}
}
private void createNew(Article article) {
articleMapper.insert(article);
}
@Override
public Optional<Article> findById(String id) {
return Optional.ofNullable(articleMapper.findById(id));
}
@Override
public Optional<Article> findBySlug(String slug) {
return Optional.ofNullable(articleMapper.findBySlug(slug));
}
@Override
public void remove(Article article) {
articleMapper.delete(article.getId());
}
}
<?xml version="1.0" encoding="UTF-8" ?> <?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="io.spring.infrastructure.mybatis.mapper.ArticleMapper"> <mapper namespace="com.wzy.tacocloud.infrastructure.mybatis.mapper.ArticleMapper">
<insert id="insert"> <insert id="insert">
insert into articles(id, slug, title, description, body, user_id, created_at, updated_at) insert into articles(id, slug, title, description, body, user_id, created_at, updated_at)
values( values(
...@@ -50,7 +50,7 @@ ...@@ -50,7 +50,7 @@
where A.slug = #{slug} where A.slug = #{slug}
</select> </select>
<resultMap id="article" type="io.spring.core.article.Article"> <resultMap id="article" type="com.wzy.tacocloud.core.article.Article">
<id column="articleId" property="id"/> <id column="articleId" property="id"/>
<result column="articleUserId" property="userId"/> <result column="articleUserId" property="userId"/>
<result column="articleTitle" property="title"/> <result column="articleTitle" property="title"/>
...@@ -62,7 +62,7 @@ ...@@ -62,7 +62,7 @@
<collection property="tags" ofType="arraylist" resultMap="tag"/> <collection property="tags" ofType="arraylist" resultMap="tag"/>
</resultMap> </resultMap>
<resultMap id="tag" type="io.spring.core.article.Tag"> <resultMap id="tag" type="com.wzy.tacocloud.core.article.Tag">
<id column="tagId" property="id"/> <id column="tagId" property="id"/>
<result column="tagName" property="name"/> <result column="tagName" property="name"/>
</resultMap> </resultMap>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment