feat: add graphql
This commit is contained in:
parent
01fac42c64
commit
44a70270c8
@ -0,0 +1,3 @@
|
||||
package io.spring.graphql;
|
||||
|
||||
public class AuthenticationException extends RuntimeException {}
|
386
src/main/java/io/spring/graphql/users/ArticleDatafetcher.java
Normal file
386
src/main/java/io/spring/graphql/users/ArticleDatafetcher.java
Normal file
@ -0,0 +1,386 @@
|
||||
package io.spring.graphql.users;
|
||||
|
||||
import com.netflix.graphql.dgs.DgsComponent;
|
||||
import com.netflix.graphql.dgs.DgsData;
|
||||
import com.netflix.graphql.dgs.DgsDataFetchingEnvironment;
|
||||
import com.netflix.graphql.dgs.InputArgument;
|
||||
import graphql.execution.DataFetcherResult;
|
||||
import graphql.relay.DefaultConnectionCursor;
|
||||
import graphql.relay.DefaultPageInfo;
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
import io.spring.Util;
|
||||
import io.spring.api.exception.ResourceNotFoundException;
|
||||
import io.spring.application.ArticleQueryService;
|
||||
import io.spring.application.CursorPageParameter;
|
||||
import io.spring.application.CursorPager;
|
||||
import io.spring.application.CursorPager.Direction;
|
||||
import io.spring.application.data.ArticleData;
|
||||
import io.spring.application.data.CommentData;
|
||||
import io.spring.core.user.User;
|
||||
import io.spring.core.user.UserRepository;
|
||||
import io.spring.graphql.DgsConstants;
|
||||
import io.spring.graphql.DgsConstants.ARTICLEPAYLOAD;
|
||||
import io.spring.graphql.DgsConstants.COMMENT;
|
||||
import io.spring.graphql.DgsConstants.PROFILE;
|
||||
import io.spring.graphql.DgsConstants.QUERY;
|
||||
import io.spring.graphql.types.Article;
|
||||
import io.spring.graphql.types.ArticleEdge;
|
||||
import io.spring.graphql.types.ArticlesConnection;
|
||||
import io.spring.graphql.types.Profile;
|
||||
import java.util.HashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@DgsComponent
|
||||
public class ArticleDatafetcher {
|
||||
|
||||
private ArticleQueryService articleQueryService;
|
||||
private UserRepository userRepository;
|
||||
|
||||
@Autowired
|
||||
public ArticleDatafetcher(
|
||||
ArticleQueryService articleQueryService, UserRepository userRepository) {
|
||||
this.articleQueryService = articleQueryService;
|
||||
this.userRepository = userRepository;
|
||||
}
|
||||
|
||||
@DgsData(parentType = DgsConstants.QUERY_TYPE, field = QUERY.Feed)
|
||||
public DataFetcherResult<ArticlesConnection> getFeed(
|
||||
@InputArgument("first") Integer first,
|
||||
@InputArgument("after") String after,
|
||||
@InputArgument("last") Integer last,
|
||||
@InputArgument("before") String before,
|
||||
DgsDataFetchingEnvironment dfe) {
|
||||
if (first == null && last == null) {
|
||||
throw new IllegalArgumentException("first 和 last 必须只存在一个");
|
||||
}
|
||||
|
||||
User current = SecurityUtil.getCurrentUser().orElse(null);
|
||||
|
||||
CursorPager<ArticleData> articles;
|
||||
if (first != null) {
|
||||
articles =
|
||||
articleQueryService.findUserFeedWithCursor(
|
||||
current, new CursorPageParameter(after, first, Direction.NEXT));
|
||||
} else {
|
||||
articles =
|
||||
articleQueryService.findUserFeedWithCursor(
|
||||
current, new CursorPageParameter(before, last, Direction.PREV));
|
||||
}
|
||||
graphql.relay.PageInfo pageInfo = buildArticlePageInfo(articles);
|
||||
ArticlesConnection articlesConnection =
|
||||
ArticlesConnection.newBuilder()
|
||||
.pageInfo(pageInfo)
|
||||
.edges(
|
||||
articles.getData().stream()
|
||||
.map(
|
||||
a ->
|
||||
ArticleEdge.newBuilder()
|
||||
.cursor(a.getCursor())
|
||||
.node(buildArticleResult(a))
|
||||
.build())
|
||||
.collect(Collectors.toList()))
|
||||
.build();
|
||||
return DataFetcherResult.<ArticlesConnection>newResult()
|
||||
.data(articlesConnection)
|
||||
.localContext(
|
||||
articles.getData().stream().collect(Collectors.toMap(ArticleData::getSlug, a -> a)))
|
||||
.build();
|
||||
}
|
||||
|
||||
@DgsData(parentType = PROFILE.TYPE_NAME, field = PROFILE.Feed)
|
||||
public DataFetcherResult<ArticlesConnection> userFeed(
|
||||
@InputArgument("first") Integer first,
|
||||
@InputArgument("after") String after,
|
||||
@InputArgument("last") Integer last,
|
||||
@InputArgument("before") String before,
|
||||
DgsDataFetchingEnvironment dfe) {
|
||||
if (first == null && last == null) {
|
||||
throw new IllegalArgumentException("first 和 last 必须只存在一个");
|
||||
}
|
||||
|
||||
Profile profile = dfe.getSource();
|
||||
User target =
|
||||
userRepository
|
||||
.findByUsername(profile.getUsername())
|
||||
.orElseThrow(ResourceNotFoundException::new);
|
||||
|
||||
CursorPager<ArticleData> articles;
|
||||
if (first != null) {
|
||||
articles =
|
||||
articleQueryService.findUserFeedWithCursor(
|
||||
target, new CursorPageParameter(after, first, Direction.NEXT));
|
||||
} else {
|
||||
articles =
|
||||
articleQueryService.findUserFeedWithCursor(
|
||||
target, new CursorPageParameter(before, last, Direction.PREV));
|
||||
}
|
||||
graphql.relay.PageInfo pageInfo = buildArticlePageInfo(articles);
|
||||
ArticlesConnection articlesConnection =
|
||||
ArticlesConnection.newBuilder()
|
||||
.pageInfo(pageInfo)
|
||||
.edges(
|
||||
articles.getData().stream()
|
||||
.map(
|
||||
a ->
|
||||
ArticleEdge.newBuilder()
|
||||
.cursor(a.getCursor())
|
||||
.node(buildArticleResult(a))
|
||||
.build())
|
||||
.collect(Collectors.toList()))
|
||||
.build();
|
||||
return DataFetcherResult.<ArticlesConnection>newResult()
|
||||
.data(articlesConnection)
|
||||
.localContext(
|
||||
articles.getData().stream().collect(Collectors.toMap(ArticleData::getSlug, a -> a)))
|
||||
.build();
|
||||
}
|
||||
|
||||
@DgsData(parentType = PROFILE.TYPE_NAME, field = PROFILE.Favorites)
|
||||
public DataFetcherResult<ArticlesConnection> userFavorites(
|
||||
@InputArgument("first") Integer first,
|
||||
@InputArgument("after") String after,
|
||||
@InputArgument("last") Integer last,
|
||||
@InputArgument("before") String before,
|
||||
DgsDataFetchingEnvironment dfe) {
|
||||
if (first == null && last == null) {
|
||||
throw new IllegalArgumentException("first 和 last 必须只存在一个");
|
||||
}
|
||||
|
||||
User current = SecurityUtil.getCurrentUser().orElse(null);
|
||||
Profile profile = dfe.getSource();
|
||||
|
||||
CursorPager<ArticleData> articles;
|
||||
if (first != null) {
|
||||
articles =
|
||||
articleQueryService.findRecentArticlesWithCursor(
|
||||
null,
|
||||
null,
|
||||
profile.getUsername(),
|
||||
new CursorPageParameter(after, first, Direction.NEXT),
|
||||
current);
|
||||
} else {
|
||||
articles =
|
||||
articleQueryService.findRecentArticlesWithCursor(
|
||||
null,
|
||||
null,
|
||||
profile.getUsername(),
|
||||
new CursorPageParameter(before, last, Direction.PREV),
|
||||
current);
|
||||
}
|
||||
graphql.relay.PageInfo pageInfo = buildArticlePageInfo(articles);
|
||||
|
||||
ArticlesConnection articlesConnection =
|
||||
ArticlesConnection.newBuilder()
|
||||
.pageInfo(pageInfo)
|
||||
.edges(
|
||||
articles.getData().stream()
|
||||
.map(
|
||||
a ->
|
||||
ArticleEdge.newBuilder()
|
||||
.cursor(a.getCursor())
|
||||
.node(buildArticleResult(a))
|
||||
.build())
|
||||
.collect(Collectors.toList()))
|
||||
.build();
|
||||
return DataFetcherResult.<ArticlesConnection>newResult()
|
||||
.data(articlesConnection)
|
||||
.localContext(
|
||||
articles.getData().stream().collect(Collectors.toMap(ArticleData::getSlug, a -> a)))
|
||||
.build();
|
||||
}
|
||||
|
||||
@DgsData(parentType = PROFILE.TYPE_NAME, field = PROFILE.Articles)
|
||||
public DataFetcherResult<ArticlesConnection> userArticles(
|
||||
@InputArgument("first") Integer first,
|
||||
@InputArgument("after") String after,
|
||||
@InputArgument("last") Integer last,
|
||||
@InputArgument("before") String before,
|
||||
DgsDataFetchingEnvironment dfe) {
|
||||
if (first == null && last == null) {
|
||||
throw new IllegalArgumentException("first 和 last 必须只存在一个");
|
||||
}
|
||||
|
||||
User current = SecurityUtil.getCurrentUser().orElse(null);
|
||||
Profile profile = dfe.getSource();
|
||||
|
||||
CursorPager<ArticleData> articles;
|
||||
if (first != null) {
|
||||
articles =
|
||||
articleQueryService.findRecentArticlesWithCursor(
|
||||
null,
|
||||
profile.getUsername(),
|
||||
null,
|
||||
new CursorPageParameter(after, first, Direction.NEXT),
|
||||
current);
|
||||
} else {
|
||||
articles =
|
||||
articleQueryService.findRecentArticlesWithCursor(
|
||||
null,
|
||||
profile.getUsername(),
|
||||
null,
|
||||
new CursorPageParameter(before, last, Direction.PREV),
|
||||
current);
|
||||
}
|
||||
graphql.relay.PageInfo pageInfo = buildArticlePageInfo(articles);
|
||||
ArticlesConnection articlesConnection =
|
||||
ArticlesConnection.newBuilder()
|
||||
.pageInfo(pageInfo)
|
||||
.edges(
|
||||
articles.getData().stream()
|
||||
.map(
|
||||
a ->
|
||||
ArticleEdge.newBuilder()
|
||||
.cursor(a.getCursor())
|
||||
.node(buildArticleResult(a))
|
||||
.build())
|
||||
.collect(Collectors.toList()))
|
||||
.build();
|
||||
return DataFetcherResult.<ArticlesConnection>newResult()
|
||||
.data(articlesConnection)
|
||||
.localContext(
|
||||
articles.getData().stream().collect(Collectors.toMap(ArticleData::getSlug, a -> a)))
|
||||
.build();
|
||||
}
|
||||
|
||||
@DgsData(parentType = DgsConstants.QUERY_TYPE, field = QUERY.Articles)
|
||||
public DataFetcherResult<ArticlesConnection> getArticles(
|
||||
@InputArgument("first") Integer first,
|
||||
@InputArgument("after") String after,
|
||||
@InputArgument("last") Integer last,
|
||||
@InputArgument("before") String before,
|
||||
@InputArgument("authoredBy") String authoredBy,
|
||||
@InputArgument("favoritedBy") String favoritedBy,
|
||||
@InputArgument("withTag") String withTag,
|
||||
DgsDataFetchingEnvironment dfe) {
|
||||
if (first == null && last == null) {
|
||||
throw new IllegalArgumentException("first 和 last 必须只存在一个");
|
||||
}
|
||||
|
||||
User current = SecurityUtil.getCurrentUser().orElse(null);
|
||||
|
||||
CursorPager<ArticleData> articles;
|
||||
if (first != null) {
|
||||
articles =
|
||||
articleQueryService.findRecentArticlesWithCursor(
|
||||
withTag,
|
||||
authoredBy,
|
||||
favoritedBy,
|
||||
new CursorPageParameter(after, first, Direction.NEXT),
|
||||
current);
|
||||
} else {
|
||||
articles =
|
||||
articleQueryService.findRecentArticlesWithCursor(
|
||||
withTag,
|
||||
authoredBy,
|
||||
favoritedBy,
|
||||
new CursorPageParameter(before, last, Direction.PREV),
|
||||
current);
|
||||
}
|
||||
graphql.relay.PageInfo pageInfo = buildArticlePageInfo(articles);
|
||||
ArticlesConnection articlesConnection =
|
||||
ArticlesConnection.newBuilder()
|
||||
.pageInfo(pageInfo)
|
||||
.edges(
|
||||
articles.getData().stream()
|
||||
.map(
|
||||
a ->
|
||||
ArticleEdge.newBuilder()
|
||||
.cursor(a.getCursor())
|
||||
.node(buildArticleResult(a))
|
||||
.build())
|
||||
.collect(Collectors.toList()))
|
||||
.build();
|
||||
return DataFetcherResult.<ArticlesConnection>newResult()
|
||||
.data(articlesConnection)
|
||||
.localContext(
|
||||
articles.getData().stream().collect(Collectors.toMap(ArticleData::getSlug, a -> a)))
|
||||
.build();
|
||||
}
|
||||
|
||||
@DgsData(parentType = ARTICLEPAYLOAD.TYPE_NAME, field = ARTICLEPAYLOAD.Article)
|
||||
public DataFetcherResult<Article> getArticle(DataFetchingEnvironment dfe) {
|
||||
io.spring.core.article.Article article = dfe.getLocalContext();
|
||||
|
||||
User current = SecurityUtil.getCurrentUser().orElse(null);
|
||||
ArticleData articleData =
|
||||
articleQueryService
|
||||
.findById(article.getId(), current)
|
||||
.orElseThrow(ResourceNotFoundException::new);
|
||||
Article articleResult = buildArticleResult(articleData);
|
||||
return DataFetcherResult.<Article>newResult()
|
||||
.localContext(
|
||||
new HashMap<String, Object>() {
|
||||
{
|
||||
put(articleData.getSlug(), articleData);
|
||||
}
|
||||
})
|
||||
.data(articleResult)
|
||||
.build();
|
||||
}
|
||||
|
||||
@DgsData(parentType = COMMENT.TYPE_NAME, field = COMMENT.Article)
|
||||
public DataFetcherResult<Article> getCommentArticle(
|
||||
DataFetchingEnvironment dataFetchingEnvironment) {
|
||||
CommentData comment = dataFetchingEnvironment.getLocalContext();
|
||||
User current = SecurityUtil.getCurrentUser().orElse(null);
|
||||
ArticleData articleData =
|
||||
articleQueryService
|
||||
.findById(comment.getArticleId(), current)
|
||||
.orElseThrow(ResourceNotFoundException::new);
|
||||
Article articleResult = buildArticleResult(articleData);
|
||||
return DataFetcherResult.<Article>newResult()
|
||||
.localContext(
|
||||
new HashMap<String, Object>() {
|
||||
{
|
||||
put(articleData.getSlug(), articleData);
|
||||
}
|
||||
})
|
||||
.data(articleResult)
|
||||
.build();
|
||||
}
|
||||
|
||||
@DgsData(parentType = DgsConstants.QUERY_TYPE, field = QUERY.Article)
|
||||
public DataFetcherResult<Article> findArticleBySlug(@InputArgument("slug") String slug) {
|
||||
User current = SecurityUtil.getCurrentUser().orElse(null);
|
||||
ArticleData articleData =
|
||||
articleQueryService.findBySlug(slug, current).orElseThrow(ResourceNotFoundException::new);
|
||||
Article articleResult = buildArticleResult(articleData);
|
||||
return DataFetcherResult.<Article>newResult()
|
||||
.localContext(
|
||||
new HashMap<String, Object>() {
|
||||
{
|
||||
put(articleData.getSlug(), articleData);
|
||||
}
|
||||
})
|
||||
.data(articleResult)
|
||||
.build();
|
||||
}
|
||||
|
||||
private DefaultPageInfo buildArticlePageInfo(CursorPager<ArticleData> articles) {
|
||||
return new DefaultPageInfo(
|
||||
Util.isEmpty(articles.getStartCursor())
|
||||
? null
|
||||
: new DefaultConnectionCursor(articles.getStartCursor()),
|
||||
Util.isEmpty(articles.getEndCursor())
|
||||
? null
|
||||
: new DefaultConnectionCursor(articles.getEndCursor()),
|
||||
articles.hasPrevious(),
|
||||
articles.hasNext());
|
||||
}
|
||||
|
||||
private Article buildArticleResult(ArticleData articleData) {
|
||||
return Article.newBuilder()
|
||||
.body(articleData.getBody())
|
||||
.createdAt(ISODateTimeFormat.dateTime().withZoneUTC().print(articleData.getCreatedAt()))
|
||||
.description(articleData.getDescription())
|
||||
.favorited(articleData.isFavorited())
|
||||
.favoritesCount(articleData.getFavoritesCount())
|
||||
.slug(articleData.getSlug())
|
||||
.tagList(articleData.getTagList())
|
||||
.title(articleData.getTitle())
|
||||
.updatedAt(ISODateTimeFormat.dateTime().withZoneUTC().print(articleData.getUpdatedAt()))
|
||||
.build();
|
||||
}
|
||||
}
|
124
src/main/java/io/spring/graphql/users/ArticleMutation.java
Normal file
124
src/main/java/io/spring/graphql/users/ArticleMutation.java
Normal file
@ -0,0 +1,124 @@
|
||||
package io.spring.graphql.users;
|
||||
|
||||
import com.netflix.graphql.dgs.DgsComponent;
|
||||
import com.netflix.graphql.dgs.DgsData;
|
||||
import com.netflix.graphql.dgs.InputArgument;
|
||||
import graphql.execution.DataFetcherResult;
|
||||
import io.spring.api.exception.NoAuthorizationException;
|
||||
import io.spring.api.exception.ResourceNotFoundException;
|
||||
import io.spring.application.article.ArticleCommandService;
|
||||
import io.spring.application.article.NewArticleParam;
|
||||
import io.spring.application.article.UpdateArticleParam;
|
||||
import io.spring.core.article.Article;
|
||||
import io.spring.core.article.ArticleRepository;
|
||||
import io.spring.core.favorite.ArticleFavorite;
|
||||
import io.spring.core.favorite.ArticleFavoriteRepository;
|
||||
import io.spring.core.service.AuthorizationService;
|
||||
import io.spring.core.user.User;
|
||||
import io.spring.graphql.AuthenticationException;
|
||||
import io.spring.graphql.DgsConstants.MUTATION;
|
||||
import io.spring.graphql.types.ArticlePayload;
|
||||
import io.spring.graphql.types.CreateArticleInput;
|
||||
import io.spring.graphql.types.DeletionStatus;
|
||||
import io.spring.graphql.types.UpdateArticleInput;
|
||||
import java.util.Collections;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@DgsComponent
|
||||
public class ArticleMutation {
|
||||
|
||||
private ArticleCommandService articleCommandService;
|
||||
private ArticleFavoriteRepository articleFavoriteRepository;
|
||||
private ArticleRepository articleRepository;
|
||||
|
||||
@Autowired
|
||||
public ArticleMutation(
|
||||
ArticleCommandService articleCommandService,
|
||||
ArticleFavoriteRepository articleFavoriteRepository,
|
||||
ArticleRepository articleRepository) {
|
||||
this.articleCommandService = articleCommandService;
|
||||
this.articleFavoriteRepository = articleFavoriteRepository;
|
||||
this.articleRepository = articleRepository;
|
||||
}
|
||||
|
||||
@DgsData(parentType = MUTATION.TYPE_NAME, field = MUTATION.CreateArticle)
|
||||
public DataFetcherResult<ArticlePayload> createArticle(
|
||||
@InputArgument("input") CreateArticleInput input) {
|
||||
User user = SecurityUtil.getCurrentUser().orElseThrow(AuthenticationException::new);
|
||||
NewArticleParam newArticleParam =
|
||||
NewArticleParam.builder()
|
||||
.title(input.getTitle())
|
||||
.description(input.getDescription())
|
||||
.body(input.getBody())
|
||||
.tagList(input.getTagList() == null ? Collections.emptyList() : input.getTagList())
|
||||
.build();
|
||||
Article article = articleCommandService.createArticle(newArticleParam, user);
|
||||
return DataFetcherResult.<ArticlePayload>newResult()
|
||||
.data(ArticlePayload.newBuilder().build())
|
||||
.localContext(article)
|
||||
.build();
|
||||
}
|
||||
|
||||
@DgsData(parentType = MUTATION.TYPE_NAME, field = MUTATION.UpdateArticle)
|
||||
public DataFetcherResult<ArticlePayload> updateArticle(
|
||||
@InputArgument("slug") String slug, @InputArgument("changes") UpdateArticleInput params) {
|
||||
Article article =
|
||||
articleRepository.findBySlug(slug).orElseThrow(ResourceNotFoundException::new);
|
||||
User user = SecurityUtil.getCurrentUser().orElseThrow(AuthenticationException::new);
|
||||
if (!AuthorizationService.canWriteArticle(user, article)) {
|
||||
throw new NoAuthorizationException();
|
||||
}
|
||||
article =
|
||||
articleCommandService.updateArticle(
|
||||
article,
|
||||
new UpdateArticleParam(params.getTitle(), params.getBody(), params.getDescription()));
|
||||
return DataFetcherResult.<ArticlePayload>newResult()
|
||||
.data(ArticlePayload.newBuilder().build())
|
||||
.localContext(article)
|
||||
.build();
|
||||
}
|
||||
|
||||
@DgsData(parentType = MUTATION.TYPE_NAME, field = MUTATION.FavoriteArticle)
|
||||
public DataFetcherResult<ArticlePayload> favoriteArticle(@InputArgument("slug") String slug) {
|
||||
User user = SecurityUtil.getCurrentUser().orElseThrow(AuthenticationException::new);
|
||||
Article article =
|
||||
articleRepository.findBySlug(slug).orElseThrow(ResourceNotFoundException::new);
|
||||
ArticleFavorite articleFavorite = new ArticleFavorite(article.getId(), user.getId());
|
||||
articleFavoriteRepository.save(articleFavorite);
|
||||
return DataFetcherResult.<ArticlePayload>newResult()
|
||||
.data(ArticlePayload.newBuilder().build())
|
||||
.localContext(article)
|
||||
.build();
|
||||
}
|
||||
|
||||
@DgsData(parentType = MUTATION.TYPE_NAME, field = MUTATION.UnfavoriteArticle)
|
||||
public DataFetcherResult<ArticlePayload> unfavoriteArticle(@InputArgument("slug") String slug) {
|
||||
User user = SecurityUtil.getCurrentUser().orElseThrow(AuthenticationException::new);
|
||||
Article article =
|
||||
articleRepository.findBySlug(slug).orElseThrow(ResourceNotFoundException::new);
|
||||
articleFavoriteRepository
|
||||
.find(article.getId(), user.getId())
|
||||
.ifPresent(
|
||||
favorite -> {
|
||||
articleFavoriteRepository.remove(favorite);
|
||||
});
|
||||
return DataFetcherResult.<ArticlePayload>newResult()
|
||||
.data(ArticlePayload.newBuilder().build())
|
||||
.localContext(article)
|
||||
.build();
|
||||
}
|
||||
|
||||
@DgsData(parentType = MUTATION.TYPE_NAME, field = MUTATION.DeleteArticle)
|
||||
public DeletionStatus deleteArticle(@InputArgument("slug") String slug) {
|
||||
User user = SecurityUtil.getCurrentUser().orElseThrow(AuthenticationException::new);
|
||||
Article article =
|
||||
articleRepository.findBySlug(slug).orElseThrow(ResourceNotFoundException::new);
|
||||
|
||||
if (!AuthorizationService.canWriteArticle(user, article)) {
|
||||
throw new NoAuthorizationException();
|
||||
}
|
||||
|
||||
articleRepository.remove(article);
|
||||
return DeletionStatus.newBuilder().success(true).build();
|
||||
}
|
||||
}
|
118
src/main/java/io/spring/graphql/users/CommentDatafetcher.java
Normal file
118
src/main/java/io/spring/graphql/users/CommentDatafetcher.java
Normal file
@ -0,0 +1,118 @@
|
||||
package io.spring.graphql.users;
|
||||
|
||||
import com.netflix.graphql.dgs.DgsComponent;
|
||||
import com.netflix.graphql.dgs.DgsData;
|
||||
import com.netflix.graphql.dgs.DgsDataFetchingEnvironment;
|
||||
import com.netflix.graphql.dgs.InputArgument;
|
||||
import graphql.execution.DataFetcherResult;
|
||||
import graphql.relay.DefaultConnectionCursor;
|
||||
import graphql.relay.DefaultPageInfo;
|
||||
import io.spring.Util;
|
||||
import io.spring.application.CommentQueryService;
|
||||
import io.spring.application.CursorPageParameter;
|
||||
import io.spring.application.CursorPager;
|
||||
import io.spring.application.CursorPager.Direction;
|
||||
import io.spring.application.data.ArticleData;
|
||||
import io.spring.application.data.CommentData;
|
||||
import io.spring.core.user.User;
|
||||
import io.spring.graphql.DgsConstants.ARTICLE;
|
||||
import io.spring.graphql.DgsConstants.COMMENTPAYLOAD;
|
||||
import io.spring.graphql.types.Comment;
|
||||
import io.spring.graphql.types.CommentEdge;
|
||||
import io.spring.graphql.types.CommentsConnection;
|
||||
import java.util.HashMap;
|
||||
import java.util.stream.Collectors;
|
||||
import org.joda.time.format.ISODateTimeFormat;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@DgsComponent
|
||||
public class CommentDatafetcher {
|
||||
private CommentQueryService commentQueryService;
|
||||
|
||||
@Autowired
|
||||
public CommentDatafetcher(CommentQueryService commentQueryService) {
|
||||
this.commentQueryService = commentQueryService;
|
||||
}
|
||||
|
||||
@DgsData(parentType = COMMENTPAYLOAD.TYPE_NAME, field = COMMENTPAYLOAD.Comment)
|
||||
public DataFetcherResult<Comment> getComment(DgsDataFetchingEnvironment dfe) {
|
||||
CommentData comment = dfe.getLocalContext();
|
||||
Comment commentResult = buildCommentResult(comment);
|
||||
return DataFetcherResult.<Comment>newResult()
|
||||
.data(commentResult)
|
||||
.localContext(
|
||||
new HashMap<String, Object>() {
|
||||
{
|
||||
put(comment.getId(), comment);
|
||||
}
|
||||
})
|
||||
.build();
|
||||
}
|
||||
|
||||
@DgsData(parentType = ARTICLE.TYPE_NAME, field = ARTICLE.Comments)
|
||||
public DataFetcherResult<CommentsConnection> articleComments(
|
||||
@InputArgument("first") Integer first,
|
||||
@InputArgument("after") String after,
|
||||
@InputArgument("last") Integer last,
|
||||
@InputArgument("before") String before,
|
||||
DgsDataFetchingEnvironment dfe) {
|
||||
|
||||
if (first == null && last == null) {
|
||||
throw new IllegalArgumentException("first 和 last 必须只存在一个");
|
||||
}
|
||||
|
||||
User current = SecurityUtil.getCurrentUser().orElse(null);
|
||||
ArticleData articleData = dfe.getLocalContext();
|
||||
|
||||
CursorPager<CommentData> comments;
|
||||
if (first != null) {
|
||||
comments =
|
||||
commentQueryService.findByArticleIdWithCursor(
|
||||
articleData.getId(), current, new CursorPageParameter(after, first, Direction.NEXT));
|
||||
} else {
|
||||
comments =
|
||||
commentQueryService.findByArticleIdWithCursor(
|
||||
articleData.getId(), current, new CursorPageParameter(before, last, Direction.PREV));
|
||||
}
|
||||
graphql.relay.PageInfo pageInfo = buildCommentPageInfo(comments);
|
||||
CommentsConnection result =
|
||||
CommentsConnection.newBuilder()
|
||||
.pageInfo(pageInfo)
|
||||
.edges(
|
||||
comments.getData().stream()
|
||||
.map(
|
||||
a ->
|
||||
CommentEdge.newBuilder()
|
||||
.cursor(a.getCursor())
|
||||
.node(buildCommentResult(a))
|
||||
.build())
|
||||
.collect(Collectors.toList()))
|
||||
.build();
|
||||
return DataFetcherResult.<CommentsConnection>newResult()
|
||||
.data(result)
|
||||
.localContext(
|
||||
comments.getData().stream().collect(Collectors.toMap(CommentData::getId, c -> c)))
|
||||
.build();
|
||||
}
|
||||
|
||||
private DefaultPageInfo buildCommentPageInfo(CursorPager<CommentData> comments) {
|
||||
return new DefaultPageInfo(
|
||||
Util.isEmpty(comments.getStartCursor())
|
||||
? null
|
||||
: new DefaultConnectionCursor(comments.getStartCursor()),
|
||||
Util.isEmpty(comments.getEndCursor())
|
||||
? null
|
||||
: new DefaultConnectionCursor(comments.getEndCursor()),
|
||||
comments.hasPrevious(),
|
||||
comments.hasNext());
|
||||
}
|
||||
|
||||
private Comment buildCommentResult(CommentData comment) {
|
||||
return Comment.newBuilder()
|
||||
.id(comment.getId())
|
||||
.body(comment.getBody())
|
||||
.updatedAt(ISODateTimeFormat.dateTime().withZoneUTC().print(comment.getCreatedAt()))
|
||||
.createdAt(ISODateTimeFormat.dateTime().withZoneUTC().print(comment.getCreatedAt()))
|
||||
.build();
|
||||
}
|
||||
}
|
77
src/main/java/io/spring/graphql/users/CommentMutation.java
Normal file
77
src/main/java/io/spring/graphql/users/CommentMutation.java
Normal file
@ -0,0 +1,77 @@
|
||||
package io.spring.graphql.users;
|
||||
|
||||
import com.netflix.graphql.dgs.DgsComponent;
|
||||
import com.netflix.graphql.dgs.DgsData;
|
||||
import com.netflix.graphql.dgs.InputArgument;
|
||||
import graphql.execution.DataFetcherResult;
|
||||
import io.spring.api.exception.NoAuthorizationException;
|
||||
import io.spring.api.exception.ResourceNotFoundException;
|
||||
import io.spring.application.CommentQueryService;
|
||||
import io.spring.application.data.CommentData;
|
||||
import io.spring.core.article.Article;
|
||||
import io.spring.core.article.ArticleRepository;
|
||||
import io.spring.core.comment.Comment;
|
||||
import io.spring.core.comment.CommentRepository;
|
||||
import io.spring.core.service.AuthorizationService;
|
||||
import io.spring.core.user.User;
|
||||
import io.spring.graphql.AuthenticationException;
|
||||
import io.spring.graphql.DgsConstants.MUTATION;
|
||||
import io.spring.graphql.types.CommentPayload;
|
||||
import io.spring.graphql.types.DeletionStatus;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@DgsComponent
|
||||
public class CommentMutation {
|
||||
|
||||
private ArticleRepository articleRepository;
|
||||
private CommentRepository commentRepository;
|
||||
private CommentQueryService commentQueryService;
|
||||
|
||||
@Autowired
|
||||
public CommentMutation(
|
||||
ArticleRepository articleRepository,
|
||||
CommentRepository commentRepository,
|
||||
CommentQueryService commentQueryService) {
|
||||
this.articleRepository = articleRepository;
|
||||
this.commentRepository = commentRepository;
|
||||
this.commentQueryService = commentQueryService;
|
||||
}
|
||||
|
||||
@DgsData(parentType = MUTATION.TYPE_NAME, field = MUTATION.AddComment)
|
||||
public DataFetcherResult<CommentPayload> createComment(
|
||||
@InputArgument("slug") String slug, @InputArgument("body") String body) {
|
||||
User user = SecurityUtil.getCurrentUser().orElseThrow(AuthenticationException::new);
|
||||
Article article =
|
||||
articleRepository.findBySlug(slug).orElseThrow(ResourceNotFoundException::new);
|
||||
Comment comment = new Comment(body, user.getId(), article.getId());
|
||||
commentRepository.save(comment);
|
||||
CommentData commentData =
|
||||
commentQueryService
|
||||
.findById(comment.getId(), user)
|
||||
.orElseThrow(ResourceNotFoundException::new);
|
||||
return DataFetcherResult.<CommentPayload>newResult()
|
||||
.localContext(commentData)
|
||||
.data(CommentPayload.newBuilder().build())
|
||||
.build();
|
||||
}
|
||||
|
||||
@DgsData(parentType = MUTATION.TYPE_NAME, field = MUTATION.DeleteComment)
|
||||
public DeletionStatus removeComment(
|
||||
@InputArgument("slug") String slug, @InputArgument("id") String commentId) {
|
||||
User user = SecurityUtil.getCurrentUser().orElseThrow(AuthenticationException::new);
|
||||
|
||||
Article article =
|
||||
articleRepository.findBySlug(slug).orElseThrow(ResourceNotFoundException::new);
|
||||
return commentRepository
|
||||
.findById(article.getId(), commentId)
|
||||
.map(
|
||||
comment -> {
|
||||
if (!AuthorizationService.canWriteComment(user, article, comment)) {
|
||||
throw new NoAuthorizationException();
|
||||
}
|
||||
commentRepository.remove(comment);
|
||||
return DeletionStatus.newBuilder().success(true).build();
|
||||
})
|
||||
.orElseThrow(ResourceNotFoundException::new);
|
||||
}
|
||||
}
|
67
src/main/java/io/spring/graphql/users/MeDatafetcher.java
Normal file
67
src/main/java/io/spring/graphql/users/MeDatafetcher.java
Normal file
@ -0,0 +1,67 @@
|
||||
package io.spring.graphql.users;
|
||||
|
||||
import com.netflix.graphql.dgs.DgsComponent;
|
||||
import com.netflix.graphql.dgs.DgsData;
|
||||
import graphql.execution.DataFetcherResult;
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
import io.spring.api.exception.ResourceNotFoundException;
|
||||
import io.spring.application.UserQueryService;
|
||||
import io.spring.application.data.UserData;
|
||||
import io.spring.application.data.UserWithToken;
|
||||
import io.spring.core.service.JwtService;
|
||||
import io.spring.graphql.DgsConstants;
|
||||
import io.spring.graphql.DgsConstants.QUERY;
|
||||
import io.spring.graphql.DgsConstants.USERPAYLOAD;
|
||||
import io.spring.graphql.types.User;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
import org.springframework.web.bind.annotation.RequestHeader;
|
||||
|
||||
@DgsComponent
|
||||
public class MeDatafetcher {
|
||||
private UserQueryService userQueryService;
|
||||
private JwtService jwtService;
|
||||
|
||||
@Autowired
|
||||
public MeDatafetcher(UserQueryService userQueryService, JwtService jwtService) {
|
||||
this.userQueryService = userQueryService;
|
||||
this.jwtService = jwtService;
|
||||
}
|
||||
|
||||
@DgsData(parentType = DgsConstants.QUERY_TYPE, field = QUERY.Me)
|
||||
public DataFetcherResult<User> getMe(
|
||||
@RequestHeader(value = "Authorization") String authorization,
|
||||
DataFetchingEnvironment dataFetchingEnvironment) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication instanceof AnonymousAuthenticationToken
|
||||
|| authentication.getPrincipal() == null) {
|
||||
return null;
|
||||
}
|
||||
io.spring.core.user.User user = (io.spring.core.user.User) authentication.getPrincipal();
|
||||
UserData userData =
|
||||
userQueryService.findById(user.getId()).orElseThrow(ResourceNotFoundException::new);
|
||||
UserWithToken userWithToken = new UserWithToken(userData, authorization.split(" ")[1]);
|
||||
User result =
|
||||
User.newBuilder()
|
||||
.email(userWithToken.getEmail())
|
||||
.username(userWithToken.getUsername())
|
||||
.token(userWithToken.getToken())
|
||||
.build();
|
||||
return DataFetcherResult.<User>newResult().data(result).localContext(user).build();
|
||||
}
|
||||
|
||||
@DgsData(parentType = USERPAYLOAD.TYPE_NAME, field = USERPAYLOAD.User)
|
||||
public DataFetcherResult<User> getUserPayloadUser(
|
||||
DataFetchingEnvironment dataFetchingEnvironment) {
|
||||
io.spring.core.user.User user = dataFetchingEnvironment.getLocalContext();
|
||||
User result =
|
||||
User.newBuilder()
|
||||
.email(user.getEmail())
|
||||
.username(user.getUsername())
|
||||
.token(jwtService.toToken(user))
|
||||
.build();
|
||||
return DataFetcherResult.<User>newResult().data(result).localContext(user).build();
|
||||
}
|
||||
}
|
@ -0,0 +1,76 @@
|
||||
package io.spring.graphql.users;
|
||||
|
||||
import com.netflix.graphql.dgs.DgsComponent;
|
||||
import com.netflix.graphql.dgs.DgsData;
|
||||
import com.netflix.graphql.dgs.InputArgument;
|
||||
import graphql.schema.DataFetchingEnvironment;
|
||||
import io.spring.api.exception.ResourceNotFoundException;
|
||||
import io.spring.application.ProfileQueryService;
|
||||
import io.spring.application.data.ArticleData;
|
||||
import io.spring.application.data.CommentData;
|
||||
import io.spring.application.data.ProfileData;
|
||||
import io.spring.core.user.User;
|
||||
import io.spring.graphql.DgsConstants;
|
||||
import io.spring.graphql.DgsConstants.ARTICLE;
|
||||
import io.spring.graphql.DgsConstants.COMMENT;
|
||||
import io.spring.graphql.DgsConstants.QUERY;
|
||||
import io.spring.graphql.DgsConstants.USER;
|
||||
import io.spring.graphql.types.Article;
|
||||
import io.spring.graphql.types.Comment;
|
||||
import io.spring.graphql.types.Profile;
|
||||
import io.spring.graphql.types.ProfilePayload;
|
||||
import java.util.Map;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@DgsComponent
|
||||
public class ProfileDatafetcher {
|
||||
|
||||
private ProfileQueryService profileQueryService;
|
||||
|
||||
@Autowired
|
||||
public ProfileDatafetcher(ProfileQueryService profileQueryService) {
|
||||
this.profileQueryService = profileQueryService;
|
||||
}
|
||||
|
||||
@DgsData(parentType = USER.TYPE_NAME, field = USER.Profile)
|
||||
public Profile getUserProfile(DataFetchingEnvironment dataFetchingEnvironment) {
|
||||
User user = dataFetchingEnvironment.getLocalContext();
|
||||
String username = user.getUsername();
|
||||
return queryProfile(username);
|
||||
}
|
||||
|
||||
@DgsData(parentType = ARTICLE.TYPE_NAME, field = ARTICLE.Author)
|
||||
public Profile getAuthor(DataFetchingEnvironment dataFetchingEnvironment) {
|
||||
Map<String, ArticleData> map = dataFetchingEnvironment.getLocalContext();
|
||||
Article article = dataFetchingEnvironment.getSource();
|
||||
return queryProfile(map.get(article.getSlug()).getProfileData().getUsername());
|
||||
}
|
||||
|
||||
@DgsData(parentType = COMMENT.TYPE_NAME, field = COMMENT.Author)
|
||||
public Profile getCommentAuthor(DataFetchingEnvironment dataFetchingEnvironment) {
|
||||
Comment comment = dataFetchingEnvironment.getSource();
|
||||
Map<String, CommentData> map = dataFetchingEnvironment.getLocalContext();
|
||||
return queryProfile(map.get(comment.getId()).getProfileData().getUsername());
|
||||
}
|
||||
|
||||
@DgsData(parentType = DgsConstants.QUERY_TYPE, field = QUERY.Profile)
|
||||
public ProfilePayload queryProfile(
|
||||
@InputArgument("username") String username, DataFetchingEnvironment dataFetchingEnvironment) {
|
||||
Profile profile = queryProfile(dataFetchingEnvironment.getArgument("username"));
|
||||
return ProfilePayload.newBuilder().profile(profile).build();
|
||||
}
|
||||
|
||||
private Profile queryProfile(String username) {
|
||||
User current = SecurityUtil.getCurrentUser().orElse(null);
|
||||
ProfileData profileData =
|
||||
profileQueryService
|
||||
.findByUsername(username, current)
|
||||
.orElseThrow(ResourceNotFoundException::new);
|
||||
return Profile.newBuilder()
|
||||
.username(profileData.getUsername())
|
||||
.bio(profileData.getBio())
|
||||
.image(profileData.getImage())
|
||||
.following(profileData.isFollowing())
|
||||
.build();
|
||||
}
|
||||
}
|
70
src/main/java/io/spring/graphql/users/RelationMutation.java
Normal file
70
src/main/java/io/spring/graphql/users/RelationMutation.java
Normal file
@ -0,0 +1,70 @@
|
||||
package io.spring.graphql.users;
|
||||
|
||||
import com.netflix.graphql.dgs.DgsComponent;
|
||||
import com.netflix.graphql.dgs.DgsData;
|
||||
import com.netflix.graphql.dgs.InputArgument;
|
||||
import io.spring.api.exception.ResourceNotFoundException;
|
||||
import io.spring.application.ProfileQueryService;
|
||||
import io.spring.application.data.ProfileData;
|
||||
import io.spring.core.user.FollowRelation;
|
||||
import io.spring.core.user.User;
|
||||
import io.spring.core.user.UserRepository;
|
||||
import io.spring.graphql.AuthenticationException;
|
||||
import io.spring.graphql.DgsConstants.MUTATION;
|
||||
import io.spring.graphql.types.Profile;
|
||||
import io.spring.graphql.types.ProfilePayload;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
|
||||
@DgsComponent
|
||||
public class RelationMutation {
|
||||
|
||||
private UserRepository userRepository;
|
||||
private ProfileQueryService profileQueryService;
|
||||
|
||||
@Autowired
|
||||
public RelationMutation(UserRepository userRepository, ProfileQueryService profileQueryService) {
|
||||
this.userRepository = userRepository;
|
||||
this.profileQueryService = profileQueryService;
|
||||
}
|
||||
|
||||
@DgsData(parentType = MUTATION.TYPE_NAME, field = MUTATION.FollowUser)
|
||||
public ProfilePayload follow(@InputArgument("username") String username) {
|
||||
User user = SecurityUtil.getCurrentUser().orElseThrow(AuthenticationException::new);
|
||||
return userRepository
|
||||
.findByUsername(username)
|
||||
.map(
|
||||
target -> {
|
||||
FollowRelation followRelation = new FollowRelation(user.getId(), target.getId());
|
||||
userRepository.saveRelation(followRelation);
|
||||
Profile profile = buildProfile(username, user);
|
||||
return ProfilePayload.newBuilder().profile(profile).build();
|
||||
})
|
||||
.orElseThrow(ResourceNotFoundException::new);
|
||||
}
|
||||
|
||||
@DgsData(parentType = MUTATION.TYPE_NAME, field = MUTATION.UnfollowUser)
|
||||
public ProfilePayload unfollow(@InputArgument("username") String username) {
|
||||
User user = SecurityUtil.getCurrentUser().orElseThrow(AuthenticationException::new);
|
||||
User target =
|
||||
userRepository.findByUsername(username).orElseThrow(ResourceNotFoundException::new);
|
||||
return userRepository
|
||||
.findRelation(user.getId(), target.getId())
|
||||
.map(
|
||||
relation -> {
|
||||
userRepository.removeRelation(relation);
|
||||
Profile profile = buildProfile(username, user);
|
||||
return ProfilePayload.newBuilder().profile(profile).build();
|
||||
})
|
||||
.orElseThrow(ResourceNotFoundException::new);
|
||||
}
|
||||
|
||||
private Profile buildProfile(@InputArgument("username") String username, User current) {
|
||||
ProfileData profileData = profileQueryService.findByUsername(username, current).get();
|
||||
return Profile.newBuilder()
|
||||
.username(profileData.getUsername())
|
||||
.bio(profileData.getBio())
|
||||
.image(profileData.getImage())
|
||||
.following(profileData.isFollowing())
|
||||
.build();
|
||||
}
|
||||
}
|
19
src/main/java/io/spring/graphql/users/SecurityUtil.java
Normal file
19
src/main/java/io/spring/graphql/users/SecurityUtil.java
Normal file
@ -0,0 +1,19 @@
|
||||
package io.spring.graphql.users;
|
||||
|
||||
import io.spring.core.user.User;
|
||||
import java.util.Optional;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
public class SecurityUtil {
|
||||
public static Optional<User> getCurrentUser() {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication instanceof AnonymousAuthenticationToken
|
||||
|| authentication.getPrincipal() == null) {
|
||||
return Optional.empty();
|
||||
}
|
||||
io.spring.core.user.User currentUser = (io.spring.core.user.User) authentication.getPrincipal();
|
||||
return Optional.of(currentUser);
|
||||
}
|
||||
}
|
90
src/main/java/io/spring/graphql/users/UserMutation.java
Normal file
90
src/main/java/io/spring/graphql/users/UserMutation.java
Normal file
@ -0,0 +1,90 @@
|
||||
package io.spring.graphql.users;
|
||||
|
||||
import com.netflix.graphql.dgs.DgsComponent;
|
||||
import com.netflix.graphql.dgs.DgsData;
|
||||
import com.netflix.graphql.dgs.InputArgument;
|
||||
import graphql.execution.DataFetcherResult;
|
||||
import io.spring.api.exception.InvalidAuthenticationException;
|
||||
import io.spring.application.user.RegisterParam;
|
||||
import io.spring.application.user.UpdateUserCommand;
|
||||
import io.spring.application.user.UpdateUserParam;
|
||||
import io.spring.application.user.UserService;
|
||||
import io.spring.core.user.EncryptService;
|
||||
import io.spring.core.user.User;
|
||||
import io.spring.core.user.UserRepository;
|
||||
import io.spring.graphql.DgsConstants.MUTATION;
|
||||
import io.spring.graphql.types.CreateUserInput;
|
||||
import io.spring.graphql.types.UpdateUserInput;
|
||||
import io.spring.graphql.types.UserPayload;
|
||||
import java.util.Optional;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.security.authentication.AnonymousAuthenticationToken;
|
||||
import org.springframework.security.core.Authentication;
|
||||
import org.springframework.security.core.context.SecurityContextHolder;
|
||||
|
||||
@DgsComponent
|
||||
public class UserMutation {
|
||||
|
||||
private UserRepository userRepository;
|
||||
private EncryptService encryptService;
|
||||
private UserService userService;
|
||||
|
||||
@Autowired
|
||||
public UserMutation(
|
||||
UserRepository userRepository, EncryptService encryptService, UserService userService) {
|
||||
this.userRepository = userRepository;
|
||||
this.encryptService = encryptService;
|
||||
this.userService = userService;
|
||||
}
|
||||
|
||||
@DgsData(parentType = MUTATION.TYPE_NAME, field = MUTATION.CreateUser)
|
||||
public DataFetcherResult<UserPayload> createUser(@InputArgument("input") CreateUserInput input) {
|
||||
RegisterParam registerParam =
|
||||
new RegisterParam(input.getEmail(), input.getUsername(), input.getPassword());
|
||||
User user = userService.createUser(registerParam);
|
||||
|
||||
return DataFetcherResult.<UserPayload>newResult()
|
||||
.data(UserPayload.newBuilder().build())
|
||||
.localContext(user)
|
||||
.build();
|
||||
}
|
||||
|
||||
@DgsData(parentType = MUTATION.TYPE_NAME, field = MUTATION.Login)
|
||||
public DataFetcherResult<UserPayload> login(
|
||||
@InputArgument("password") String password, @InputArgument("email") String email) {
|
||||
Optional<User> optional = userRepository.findByEmail(email);
|
||||
if (optional.isPresent() && encryptService.check(password, optional.get().getPassword())) {
|
||||
return DataFetcherResult.<UserPayload>newResult()
|
||||
.data(UserPayload.newBuilder().build())
|
||||
.localContext(optional.get())
|
||||
.build();
|
||||
} else {
|
||||
throw new InvalidAuthenticationException();
|
||||
}
|
||||
}
|
||||
|
||||
@DgsData(parentType = MUTATION.TYPE_NAME, field = MUTATION.UpdateUser)
|
||||
public DataFetcherResult<UserPayload> updateUser(
|
||||
@InputArgument("changes") UpdateUserInput updateUserInput) {
|
||||
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
|
||||
if (authentication instanceof AnonymousAuthenticationToken
|
||||
|| authentication.getPrincipal() == null) {
|
||||
return null;
|
||||
}
|
||||
io.spring.core.user.User currentUser = (io.spring.core.user.User) authentication.getPrincipal();
|
||||
UpdateUserParam param =
|
||||
UpdateUserParam.builder()
|
||||
.username(updateUserInput.getUsername())
|
||||
.email(updateUserInput.getEmail())
|
||||
.bio(updateUserInput.getBio())
|
||||
.password(updateUserInput.getPassword())
|
||||
.image(updateUserInput.getImage())
|
||||
.build();
|
||||
|
||||
userService.updateUser(new UpdateUserCommand(currentUser, param));
|
||||
return DataFetcherResult.<UserPayload>newResult()
|
||||
.data(UserPayload.newBuilder().build())
|
||||
.localContext(currentUser)
|
||||
.build();
|
||||
}
|
||||
}
|
164
src/main/resources/schema/schema.graphqls
Normal file
164
src/main/resources/schema/schema.graphqls
Normal file
@ -0,0 +1,164 @@
|
||||
# Build the schema.
|
||||
type Query {
|
||||
article(slug: String!): Article
|
||||
articles(
|
||||
first: Int,
|
||||
after: String,
|
||||
last: Int,
|
||||
before: String,
|
||||
authoredBy: String
|
||||
favoritedBy: String
|
||||
withTag: String
|
||||
): ArticlesConnection
|
||||
me: User
|
||||
feed(first: Int, after: String, last: Int, before: String): ArticlesConnection
|
||||
profile(username: String!): ProfilePayload
|
||||
tags: [String]
|
||||
}
|
||||
|
||||
type Mutation {
|
||||
### User & Profile
|
||||
createUser(input: CreateUserInput): UserPayload
|
||||
login(password: String!, email: String!): UserPayload
|
||||
updateUser(changes: UpdateUserInput!): UserPayload
|
||||
followUser(username: String!): ProfilePayload
|
||||
unfollowUser(username: String!): ProfilePayload
|
||||
|
||||
### Article
|
||||
createArticle(input: CreateArticleInput!): ArticlePayload
|
||||
updateArticle(slug: String!, changes: UpdateArticleInput!): ArticlePayload
|
||||
favoriteArticle(slug: String!): ArticlePayload
|
||||
unfavoriteArticle(slug: String!): ArticlePayload
|
||||
deleteArticle(slug: String!): DeletionStatus
|
||||
|
||||
### Comment
|
||||
addComment(slug: String!, body: String!): CommentPayload
|
||||
deleteComment(slug: String!, id: ID!): DeletionStatus
|
||||
}
|
||||
|
||||
schema {
|
||||
query: Query
|
||||
mutation: Mutation
|
||||
}
|
||||
|
||||
### Articles
|
||||
type Article {
|
||||
author: Profile!
|
||||
body: String!
|
||||
comments(first: Int, after: String, last: Int, before: String): CommentsConnection
|
||||
createdAt: String!
|
||||
description: String!
|
||||
favorited: Boolean!
|
||||
favoritesCount: Int!
|
||||
slug: String!
|
||||
tagList: [String],
|
||||
title: String!
|
||||
updatedAt: String!
|
||||
}
|
||||
|
||||
type ArticleEdge {
|
||||
cursor: String!
|
||||
node: Article
|
||||
}
|
||||
|
||||
type ArticlesConnection {
|
||||
edges: [ArticleEdge]
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
### Comments
|
||||
type Comment {
|
||||
id: ID!
|
||||
author: Profile!
|
||||
article: Article!
|
||||
body: String!
|
||||
createdAt: String!
|
||||
updatedAt: String!
|
||||
}
|
||||
|
||||
type CommentEdge {
|
||||
cursor: String!
|
||||
node: Comment
|
||||
}
|
||||
|
||||
type CommentsConnection {
|
||||
edges: [CommentEdge]
|
||||
pageInfo: PageInfo!
|
||||
}
|
||||
|
||||
type DeletionStatus {
|
||||
success: Boolean!
|
||||
}
|
||||
|
||||
type PageInfo {
|
||||
endCursor: String
|
||||
hasNextPage: Boolean!
|
||||
hasPreviousPage: Boolean!
|
||||
startCursor: String
|
||||
}
|
||||
|
||||
### Profile
|
||||
type Profile {
|
||||
username: String!
|
||||
bio: String
|
||||
following: Boolean!
|
||||
image: String
|
||||
articles(first: Int, after: String, last: Int, before: String): ArticlesConnection
|
||||
favorites(first: Int, after: String, last: Int, before: String): ArticlesConnection
|
||||
feed(first: Int, after: String, last: Int, before: String): ArticlesConnection
|
||||
}
|
||||
|
||||
### User
|
||||
type User {
|
||||
email: String!
|
||||
profile: Profile!
|
||||
token: String!
|
||||
username: String!
|
||||
}
|
||||
|
||||
## Mutations
|
||||
|
||||
# Input types.
|
||||
input UpdateArticleInput {
|
||||
body: String
|
||||
description: String
|
||||
title: String
|
||||
}
|
||||
|
||||
input CreateArticleInput {
|
||||
body: String!
|
||||
description: String!
|
||||
tagList: [String]
|
||||
title: String!
|
||||
}
|
||||
|
||||
type ArticlePayload {
|
||||
article: Article
|
||||
}
|
||||
|
||||
type CommentPayload {
|
||||
comment: Comment
|
||||
}
|
||||
|
||||
input CreateUserInput {
|
||||
email: String!
|
||||
username: String!
|
||||
password: String!
|
||||
}
|
||||
|
||||
input UpdateUserInput {
|
||||
email: String
|
||||
username: String
|
||||
password: String
|
||||
image: String
|
||||
bio: String
|
||||
}
|
||||
|
||||
type UserPayload {
|
||||
user: User
|
||||
}
|
||||
|
||||
type ProfilePayload {
|
||||
profile: Profile
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user