refactor: application level prepare for graphql and switch to sqlite
This commit is contained in:
parent
36e33e7730
commit
01fac42c64
3
.gitignore
vendored
3
.gitignore
vendored
@ -1,6 +1,7 @@
|
||||
.gradle
|
||||
/build/
|
||||
!gradle/wrapper/gradle-wrapper.jar
|
||||
*.db
|
||||
|
||||
### STS ###
|
||||
.apt_generated
|
||||
@ -22,4 +23,4 @@ build/
|
||||
nbbuild/
|
||||
dist/
|
||||
nbdist/
|
||||
.nb-gradle/
|
||||
.nb-gradle/
|
||||
|
11
build.gradle
11
build.gradle
@ -4,6 +4,7 @@ plugins {
|
||||
id 'java'
|
||||
id 'idea'
|
||||
id 'eclipse'
|
||||
id "com.netflix.dgs.codegen" version "4.2.0"
|
||||
}
|
||||
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
@ -13,6 +14,7 @@ targetCompatibility = JavaVersion.VERSION_1_8
|
||||
repositories {
|
||||
mavenLocal()
|
||||
mavenCentral()
|
||||
jcenter()
|
||||
}
|
||||
|
||||
configurations {
|
||||
@ -26,11 +28,11 @@ dependencies {
|
||||
implementation 'org.springframework.boot:spring-boot-starter-hateoas'
|
||||
implementation 'org.springframework.boot:spring-boot-starter-security'
|
||||
implementation 'org.mybatis.spring.boot:mybatis-spring-boot-starter:2.1.3'
|
||||
implementation "com.netflix.graphql.dgs:graphql-dgs-spring-boot-starter:latest.release"
|
||||
implementation 'org.flywaydb:flyway-core'
|
||||
implementation 'io.jsonwebtoken:jjwt:0.9.1'
|
||||
implementation 'joda-time:joda-time:2.10.6'
|
||||
|
||||
runtimeOnly 'com.h2database:h2'
|
||||
implementation 'org.xerial:sqlite-jdbc:3.34.0'
|
||||
|
||||
compileOnly 'org.projectlombok:lombok'
|
||||
annotationProcessor 'org.projectlombok:lombok'
|
||||
@ -47,3 +49,8 @@ dependencies {
|
||||
test {
|
||||
useJUnitPlatform()
|
||||
}
|
||||
|
||||
generateJava {
|
||||
schemaPaths = ["${projectDir}/src/main/resources/schema"] // List of directories containing schema files
|
||||
packageName = 'io.spring.graphql' // The package name to use to generate sources
|
||||
}
|
@ -2,11 +2,12 @@ package io.spring;
|
||||
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration;
|
||||
|
||||
@SpringBootApplication
|
||||
@SpringBootApplication(exclude = HibernateJpaAutoConfiguration.class)
|
||||
public class RealworldApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(RealworldApplication.class, args);
|
||||
}
|
||||
public static void main(String[] args) {
|
||||
SpringApplication.run(RealworldApplication.class, args);
|
||||
}
|
||||
}
|
||||
|
@ -21,56 +21,70 @@ import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
@EnableWebSecurity
|
||||
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
|
||||
@Value("${spring.h2.console.enabled:false}")
|
||||
private boolean h2ConsoleEnabled;
|
||||
@Value("${spring.h2.console.enabled:false}")
|
||||
private boolean h2ConsoleEnabled;
|
||||
|
||||
@Bean
|
||||
public JwtTokenFilter jwtTokenFilter() {
|
||||
return new JwtTokenFilter();
|
||||
@Bean
|
||||
public JwtTokenFilter jwtTokenFilter() {
|
||||
return new JwtTokenFilter();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
|
||||
if (h2ConsoleEnabled) {
|
||||
http.authorizeRequests()
|
||||
.antMatchers("/h2-console", "/h2-console/**")
|
||||
.permitAll()
|
||||
.and()
|
||||
.headers()
|
||||
.frameOptions()
|
||||
.sameOrigin();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void configure(HttpSecurity http) throws Exception {
|
||||
http.csrf()
|
||||
.disable()
|
||||
.cors()
|
||||
.and()
|
||||
.exceptionHandling()
|
||||
.authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
|
||||
.and()
|
||||
.sessionManagement()
|
||||
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
|
||||
.and()
|
||||
.authorizeRequests()
|
||||
.antMatchers(HttpMethod.OPTIONS)
|
||||
.permitAll()
|
||||
.antMatchers("/graphiql")
|
||||
.permitAll()
|
||||
.antMatchers("/graphql")
|
||||
.permitAll()
|
||||
.antMatchers(HttpMethod.GET, "/articles/feed")
|
||||
.authenticated()
|
||||
.antMatchers(HttpMethod.POST, "/users", "/users/login")
|
||||
.permitAll()
|
||||
.antMatchers(HttpMethod.GET, "/articles/**", "/profiles/**", "/tags")
|
||||
.permitAll()
|
||||
.anyRequest()
|
||||
.authenticated();
|
||||
|
||||
if (h2ConsoleEnabled) {
|
||||
http.authorizeRequests()
|
||||
.antMatchers("/h2-console", "/h2-console/**").permitAll()
|
||||
.and()
|
||||
.headers().frameOptions().sameOrigin();
|
||||
}
|
||||
http.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||
}
|
||||
|
||||
http.csrf().disable()
|
||||
.cors()
|
||||
.and()
|
||||
.exceptionHandling().authenticationEntryPoint(new HttpStatusEntryPoint(HttpStatus.UNAUTHORIZED))
|
||||
.and()
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
||||
.authorizeRequests()
|
||||
.antMatchers(HttpMethod.OPTIONS).permitAll()
|
||||
.antMatchers("/graphiql").permitAll()
|
||||
.antMatchers("/graphql").permitAll()
|
||||
.antMatchers(HttpMethod.GET, "/articles/feed").authenticated()
|
||||
.antMatchers(HttpMethod.POST, "/users", "/users/login").permitAll()
|
||||
.antMatchers(HttpMethod.GET, "/articles/**", "/profiles/**", "/tags").permitAll()
|
||||
.anyRequest().authenticated();
|
||||
|
||||
http.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
final CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(asList("*"));
|
||||
configuration.setAllowedMethods(asList("HEAD",
|
||||
"GET", "POST", "PUT", "DELETE", "PATCH"));
|
||||
// setAllowCredentials(true) is important, otherwise:
|
||||
// The value of the 'Access-Control-Allow-Origin' header in the response must not be the wildcard '*' when the request's credentials mode is 'include'.
|
||||
configuration.setAllowCredentials(false);
|
||||
// setAllowedHeaders is important! Without it, OPTIONS preflight request
|
||||
// will fail with 403 Invalid CORS request
|
||||
configuration.setAllowedHeaders(asList("Authorization", "Cache-Control", "Content-Type"));
|
||||
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
@Bean
|
||||
public CorsConfigurationSource corsConfigurationSource() {
|
||||
final CorsConfiguration configuration = new CorsConfiguration();
|
||||
configuration.setAllowedOrigins(asList("*"));
|
||||
configuration.setAllowedMethods(asList("HEAD", "GET", "POST", "PUT", "DELETE", "PATCH"));
|
||||
// setAllowCredentials(true) is important, otherwise:
|
||||
// The value of the 'Access-Control-Allow-Origin' header in the response must not be the
|
||||
// wildcard '*' when the request's credentials mode is 'include'.
|
||||
configuration.setAllowCredentials(false);
|
||||
// setAllowedHeaders is important! Without it, OPTIONS preflight request
|
||||
// will fail with 403 Invalid CORS request
|
||||
configuration.setAllowedHeaders(asList("Authorization", "Cache-Control", "Content-Type"));
|
||||
final UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
source.registerCorsConfiguration("/**", configuration);
|
||||
return source;
|
||||
}
|
||||
}
|
||||
|
@ -1,5 +1,7 @@
|
||||
package io.spring.application;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
|
||||
import io.spring.application.data.ArticleData;
|
||||
import io.spring.application.data.ArticleDataList;
|
||||
import io.spring.application.data.ArticleFavoriteCount;
|
||||
@ -7,126 +9,179 @@ import io.spring.core.user.User;
|
||||
import io.spring.infrastructure.mybatis.readservice.ArticleFavoritesReadService;
|
||||
import io.spring.infrastructure.mybatis.readservice.ArticleReadService;
|
||||
import io.spring.infrastructure.mybatis.readservice.UserRelationshipQueryService;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
|
||||
import static java.util.stream.Collectors.toList;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class ArticleQueryService {
|
||||
private ArticleReadService articleReadService;
|
||||
private UserRelationshipQueryService userRelationshipQueryService;
|
||||
private ArticleFavoritesReadService articleFavoritesReadService;
|
||||
private ArticleReadService articleReadService;
|
||||
private UserRelationshipQueryService userRelationshipQueryService;
|
||||
private ArticleFavoritesReadService articleFavoritesReadService;
|
||||
|
||||
@Autowired
|
||||
public ArticleQueryService(ArticleReadService articleReadService,
|
||||
UserRelationshipQueryService userRelationshipQueryService,
|
||||
ArticleFavoritesReadService articleFavoritesReadService) {
|
||||
this.articleReadService = articleReadService;
|
||||
this.userRelationshipQueryService = userRelationshipQueryService;
|
||||
this.articleFavoritesReadService = articleFavoritesReadService;
|
||||
@Autowired
|
||||
public ArticleQueryService(
|
||||
ArticleReadService articleReadService,
|
||||
UserRelationshipQueryService userRelationshipQueryService,
|
||||
ArticleFavoritesReadService articleFavoritesReadService) {
|
||||
this.articleReadService = articleReadService;
|
||||
this.userRelationshipQueryService = userRelationshipQueryService;
|
||||
this.articleFavoritesReadService = articleFavoritesReadService;
|
||||
}
|
||||
|
||||
public Optional<ArticleData> findById(String id, User user) {
|
||||
ArticleData articleData = articleReadService.findById(id);
|
||||
if (articleData == null) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
if (user != null) {
|
||||
fillExtraInfo(id, user, articleData);
|
||||
}
|
||||
return Optional.of(articleData);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<ArticleData> findById(String id, User user) {
|
||||
ArticleData articleData = articleReadService.findById(id);
|
||||
if (articleData == null) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
if (user != null) {
|
||||
fillExtraInfo(id, user, articleData);
|
||||
}
|
||||
return Optional.of(articleData);
|
||||
}
|
||||
public Optional<ArticleData> findBySlug(String slug, User user) {
|
||||
ArticleData articleData = articleReadService.findBySlug(slug);
|
||||
if (articleData == null) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
if (user != null) {
|
||||
fillExtraInfo(articleData.getId(), user, articleData);
|
||||
}
|
||||
return Optional.of(articleData);
|
||||
}
|
||||
}
|
||||
|
||||
public Optional<ArticleData> findBySlug(String slug, User user) {
|
||||
ArticleData articleData = articleReadService.findBySlug(slug);
|
||||
if (articleData == null) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
if (user != null) {
|
||||
fillExtraInfo(articleData.getId(), user, articleData);
|
||||
}
|
||||
return Optional.of(articleData);
|
||||
}
|
||||
public CursorPager<ArticleData> findRecentArticlesWithCursor(
|
||||
String tag, String author, String favoritedBy, CursorPageParameter page, User currentUser) {
|
||||
List<String> articleIds =
|
||||
articleReadService.findArticlesWithCursor(tag, author, favoritedBy, page);
|
||||
if (articleIds.size() == 0) {
|
||||
return new CursorPager<>(new ArrayList<>(), page.getDirection(), false);
|
||||
} else {
|
||||
boolean hasExtra = articleIds.size() > page.getLimit();
|
||||
if (hasExtra) {
|
||||
articleIds.remove(page.getLimit());
|
||||
}
|
||||
if (!page.isNext()) {
|
||||
Collections.reverse(articleIds);
|
||||
}
|
||||
|
||||
List<ArticleData> articles = articleReadService.findArticles(articleIds);
|
||||
fillExtraInfo(articles, currentUser);
|
||||
|
||||
return new CursorPager<>(articles, page.getDirection(), hasExtra);
|
||||
}
|
||||
}
|
||||
|
||||
public ArticleDataList findRecentArticles(String tag, String author, String favoritedBy, Page page, User currentUser) {
|
||||
List<String> articleIds = articleReadService.queryArticles(tag, author, favoritedBy, page);
|
||||
int articleCount = articleReadService.countArticle(tag, author, favoritedBy);
|
||||
if (articleIds.size() == 0) {
|
||||
return new ArticleDataList(new ArrayList<>(), articleCount);
|
||||
} else {
|
||||
List<ArticleData> articles = articleReadService.findArticles(articleIds);
|
||||
fillExtraInfo(articles, currentUser);
|
||||
return new ArticleDataList(articles, articleCount);
|
||||
}
|
||||
public CursorPager<ArticleData> findUserFeedWithCursor(User user, CursorPageParameter page) {
|
||||
List<String> followdUsers = userRelationshipQueryService.followedUsers(user.getId());
|
||||
if (followdUsers.size() == 0) {
|
||||
return new CursorPager<>(new ArrayList<>(), page.getDirection(), false);
|
||||
} else {
|
||||
List<ArticleData> articles =
|
||||
articleReadService.findArticlesOfAuthorsWithCursor(followdUsers, page);
|
||||
boolean hasExtra = articles.size() > page.getLimit();
|
||||
if (hasExtra) {
|
||||
articles.remove(page.getLimit());
|
||||
}
|
||||
if (!page.isNext()) {
|
||||
Collections.reverse(articles);
|
||||
}
|
||||
fillExtraInfo(articles, user);
|
||||
return new CursorPager<>(articles, page.getDirection(), hasExtra);
|
||||
}
|
||||
}
|
||||
|
||||
private void fillExtraInfo(List<ArticleData> articles, User currentUser) {
|
||||
setFavoriteCount(articles);
|
||||
if (currentUser != null) {
|
||||
setIsFavorite(articles, currentUser);
|
||||
setIsFollowingAuthor(articles, currentUser);
|
||||
}
|
||||
public ArticleDataList findRecentArticles(
|
||||
String tag, String author, String favoritedBy, Page page, User currentUser) {
|
||||
List<String> articleIds = articleReadService.queryArticles(tag, author, favoritedBy, page);
|
||||
int articleCount = articleReadService.countArticle(tag, author, favoritedBy);
|
||||
if (articleIds.size() == 0) {
|
||||
return new ArticleDataList(new ArrayList<>(), articleCount);
|
||||
} else {
|
||||
List<ArticleData> articles = articleReadService.findArticles(articleIds);
|
||||
fillExtraInfo(articles, currentUser);
|
||||
return new ArticleDataList(articles, articleCount);
|
||||
}
|
||||
}
|
||||
|
||||
private void setIsFollowingAuthor(List<ArticleData> articles, User currentUser) {
|
||||
Set<String> followingAuthors = userRelationshipQueryService.followingAuthors(
|
||||
public ArticleDataList findUserFeed(User user, Page page) {
|
||||
List<String> followdUsers = userRelationshipQueryService.followedUsers(user.getId());
|
||||
if (followdUsers.size() == 0) {
|
||||
return new ArticleDataList(new ArrayList<>(), 0);
|
||||
} else {
|
||||
List<ArticleData> articles = articleReadService.findArticlesOfAuthors(followdUsers, page);
|
||||
fillExtraInfo(articles, user);
|
||||
int count = articleReadService.countFeedSize(followdUsers);
|
||||
return new ArticleDataList(articles, count);
|
||||
}
|
||||
}
|
||||
|
||||
private void fillExtraInfo(List<ArticleData> articles, User currentUser) {
|
||||
setFavoriteCount(articles);
|
||||
if (currentUser != null) {
|
||||
setIsFavorite(articles, currentUser);
|
||||
setIsFollowingAuthor(articles, currentUser);
|
||||
}
|
||||
}
|
||||
|
||||
private void setIsFollowingAuthor(List<ArticleData> articles, User currentUser) {
|
||||
Set<String> followingAuthors =
|
||||
userRelationshipQueryService.followingAuthors(
|
||||
currentUser.getId(),
|
||||
articles.stream().map(articleData1 -> articleData1.getProfileData().getId()).collect(toList()));
|
||||
articles.forEach(articleData -> {
|
||||
if (followingAuthors.contains(articleData.getProfileData().getId())) {
|
||||
articleData.getProfileData().setFollowing(true);
|
||||
}
|
||||
articles.stream()
|
||||
.map(articleData1 -> articleData1.getProfileData().getId())
|
||||
.collect(toList()));
|
||||
articles.forEach(
|
||||
articleData -> {
|
||||
if (followingAuthors.contains(articleData.getProfileData().getId())) {
|
||||
articleData.getProfileData().setFollowing(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void setFavoriteCount(List<ArticleData> articles) {
|
||||
List<ArticleFavoriteCount> favoritesCounts = articleFavoritesReadService.articlesFavoriteCount(articles.stream().map(ArticleData::getId).collect(toList()));
|
||||
Map<String, Integer> countMap = new HashMap<>();
|
||||
favoritesCounts.forEach(item -> {
|
||||
countMap.put(item.getId(), item.getCount());
|
||||
private void setFavoriteCount(List<ArticleData> articles) {
|
||||
List<ArticleFavoriteCount> favoritesCounts =
|
||||
articleFavoritesReadService.articlesFavoriteCount(
|
||||
articles.stream().map(ArticleData::getId).collect(toList()));
|
||||
Map<String, Integer> countMap = new HashMap<>();
|
||||
favoritesCounts.forEach(
|
||||
item -> {
|
||||
countMap.put(item.getId(), item.getCount());
|
||||
});
|
||||
articles.forEach(articleData -> articleData.setFavoritesCount(countMap.get(articleData.getId())));
|
||||
}
|
||||
articles.forEach(
|
||||
articleData -> articleData.setFavoritesCount(countMap.get(articleData.getId())));
|
||||
}
|
||||
|
||||
private void setIsFavorite(List<ArticleData> articles, User currentUser) {
|
||||
Set<String> favoritedArticles = articleFavoritesReadService.userFavorites(articles.stream().map(articleData -> articleData.getId()).collect(toList()), currentUser);
|
||||
private void setIsFavorite(List<ArticleData> articles, User currentUser) {
|
||||
Set<String> favoritedArticles =
|
||||
articleFavoritesReadService.userFavorites(
|
||||
articles.stream().map(articleData -> articleData.getId()).collect(toList()),
|
||||
currentUser);
|
||||
|
||||
articles.forEach(articleData -> {
|
||||
if (favoritedArticles.contains(articleData.getId())) {
|
||||
articleData.setFavorited(true);
|
||||
}
|
||||
articles.forEach(
|
||||
articleData -> {
|
||||
if (favoritedArticles.contains(articleData.getId())) {
|
||||
articleData.setFavorited(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void fillExtraInfo(String id, User user, ArticleData articleData) {
|
||||
articleData.setFavorited(articleFavoritesReadService.isUserFavorite(user.getId(), id));
|
||||
articleData.setFavoritesCount(articleFavoritesReadService.articleFavoriteCount(id));
|
||||
articleData.getProfileData().setFollowing(
|
||||
private void fillExtraInfo(String id, User user, ArticleData articleData) {
|
||||
articleData.setFavorited(articleFavoritesReadService.isUserFavorite(user.getId(), id));
|
||||
articleData.setFavoritesCount(articleFavoritesReadService.articleFavoriteCount(id));
|
||||
articleData
|
||||
.getProfileData()
|
||||
.setFollowing(
|
||||
userRelationshipQueryService.isUserFollowing(
|
||||
user.getId(),
|
||||
articleData.getProfileData().getId()));
|
||||
}
|
||||
|
||||
public ArticleDataList findUserFeed(User user, Page page) {
|
||||
List<String> followdUsers = userRelationshipQueryService.followedUsers(user.getId());
|
||||
if (followdUsers.size() == 0) {
|
||||
return new ArticleDataList(new ArrayList<>(), 0);
|
||||
} else {
|
||||
List<ArticleData> articles = articleReadService.findArticlesOfAuthors(followdUsers, page);
|
||||
fillExtraInfo(articles, user);
|
||||
int count = articleReadService.countFeedSize(followdUsers);
|
||||
return new ArticleDataList(articles, count);
|
||||
}
|
||||
}
|
||||
user.getId(), articleData.getProfileData().getId()));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -1,49 +1,89 @@
|
||||
package io.spring.application;
|
||||
|
||||
import io.spring.application.data.CommentData;
|
||||
import io.spring.infrastructure.mybatis.readservice.UserRelationshipQueryService;
|
||||
import io.spring.core.user.User;
|
||||
import io.spring.infrastructure.mybatis.readservice.CommentReadService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import io.spring.infrastructure.mybatis.readservice.UserRelationshipQueryService;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Service
|
||||
public class CommentQueryService {
|
||||
private CommentReadService commentReadService;
|
||||
private UserRelationshipQueryService userRelationshipQueryService;
|
||||
private CommentReadService commentReadService;
|
||||
private UserRelationshipQueryService userRelationshipQueryService;
|
||||
|
||||
public CommentQueryService(CommentReadService commentReadService, UserRelationshipQueryService userRelationshipQueryService) {
|
||||
this.commentReadService = commentReadService;
|
||||
this.userRelationshipQueryService = userRelationshipQueryService;
|
||||
}
|
||||
public CommentQueryService(
|
||||
CommentReadService commentReadService,
|
||||
UserRelationshipQueryService userRelationshipQueryService) {
|
||||
this.commentReadService = commentReadService;
|
||||
this.userRelationshipQueryService = userRelationshipQueryService;
|
||||
}
|
||||
|
||||
public Optional<CommentData> findById(String id, User user) {
|
||||
CommentData commentData = commentReadService.findById(id);
|
||||
if (commentData == null) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
commentData.getProfileData().setFollowing(
|
||||
userRelationshipQueryService.isUserFollowing(
|
||||
user.getId(),
|
||||
commentData.getProfileData().getId()));
|
||||
}
|
||||
return Optional.ofNullable(commentData);
|
||||
public Optional<CommentData> findById(String id, User user) {
|
||||
CommentData commentData = commentReadService.findById(id);
|
||||
if (commentData == null) {
|
||||
return Optional.empty();
|
||||
} else {
|
||||
commentData
|
||||
.getProfileData()
|
||||
.setFollowing(
|
||||
userRelationshipQueryService.isUserFollowing(
|
||||
user.getId(), commentData.getProfileData().getId()));
|
||||
}
|
||||
return Optional.ofNullable(commentData);
|
||||
}
|
||||
|
||||
public List<CommentData> findByArticleId(String articleId, User user) {
|
||||
List<CommentData> comments = commentReadService.findByArticleId(articleId);
|
||||
if (comments.size() > 0 && user != null) {
|
||||
Set<String> followingAuthors = userRelationshipQueryService.followingAuthors(user.getId(), comments.stream().map(commentData -> commentData.getProfileData().getId()).collect(Collectors.toList()));
|
||||
comments.forEach(commentData -> {
|
||||
if (followingAuthors.contains(commentData.getProfileData().getId())) {
|
||||
commentData.getProfileData().setFollowing(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
return comments;
|
||||
public List<CommentData> findByArticleId(String articleId, User user) {
|
||||
List<CommentData> comments = commentReadService.findByArticleId(articleId);
|
||||
if (comments.size() > 0 && user != null) {
|
||||
Set<String> followingAuthors =
|
||||
userRelationshipQueryService.followingAuthors(
|
||||
user.getId(),
|
||||
comments.stream()
|
||||
.map(commentData -> commentData.getProfileData().getId())
|
||||
.collect(Collectors.toList()));
|
||||
comments.forEach(
|
||||
commentData -> {
|
||||
if (followingAuthors.contains(commentData.getProfileData().getId())) {
|
||||
commentData.getProfileData().setFollowing(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
return comments;
|
||||
}
|
||||
|
||||
public CursorPager<CommentData> findByArticleIdWithCursor(
|
||||
String articleId, User user, CursorPageParameter page) {
|
||||
List<CommentData> comments = commentReadService.findByArticleIdWithCursor(articleId, page);
|
||||
if (comments.isEmpty()) {
|
||||
return new CursorPager<>(new ArrayList<>(), page.getDirection(), false);
|
||||
}
|
||||
if (user != null) {
|
||||
Set<String> followingAuthors =
|
||||
userRelationshipQueryService.followingAuthors(
|
||||
user.getId(),
|
||||
comments.stream()
|
||||
.map(commentData -> commentData.getProfileData().getId())
|
||||
.collect(Collectors.toList()));
|
||||
comments.forEach(
|
||||
commentData -> {
|
||||
if (followingAuthors.contains(commentData.getProfileData().getId())) {
|
||||
commentData.getProfileData().setFollowing(true);
|
||||
}
|
||||
});
|
||||
}
|
||||
boolean hasExtra = comments.size() > page.getLimit();
|
||||
if (hasExtra) {
|
||||
comments.remove(page.getLimit());
|
||||
}
|
||||
if (!page.isNext()) {
|
||||
Collections.reverse(comments);
|
||||
}
|
||||
return new CursorPager<>(comments, page.getDirection(), hasExtra);
|
||||
}
|
||||
}
|
||||
|
44
src/main/java/io/spring/application/CursorPageParameter.java
Normal file
44
src/main/java/io/spring/application/CursorPageParameter.java
Normal file
@ -0,0 +1,44 @@
|
||||
package io.spring.application;
|
||||
|
||||
import io.spring.application.CursorPager.Direction;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
public class CursorPageParameter {
|
||||
private static final int MAX_LIMIT = 1000;
|
||||
private int limit = 20;
|
||||
private String cursor;
|
||||
private Direction direction;
|
||||
|
||||
public CursorPageParameter(String 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(String cursor) {
|
||||
if (cursor == null) {
|
||||
this.cursor = "";
|
||||
} else {
|
||||
this.cursor = cursor;
|
||||
}
|
||||
}
|
||||
|
||||
private void setLimit(int limit) {
|
||||
if (limit > MAX_LIMIT) {
|
||||
this.limit = MAX_LIMIT;
|
||||
} else if (limit > 0) {
|
||||
this.limit = limit;
|
||||
}
|
||||
}
|
||||
}
|
44
src/main/java/io/spring/application/CursorPager.java
Normal file
44
src/main/java/io/spring/application/CursorPager.java
Normal file
@ -0,0 +1,44 @@
|
||||
package io.spring.application;
|
||||
|
||||
import java.util.List;
|
||||
import lombok.Getter;
|
||||
|
||||
@Getter
|
||||
public class CursorPager<T extends Node> {
|
||||
private List<T> data;
|
||||
private boolean next;
|
||||
private 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 String getStartCursor() {
|
||||
return data.isEmpty() ? "" : data.get(0).getCursor();
|
||||
}
|
||||
|
||||
public String getEndCursor() {
|
||||
return data.isEmpty() ? "" : data.get(data.size() - 1).getCursor();
|
||||
}
|
||||
|
||||
public enum Direction {
|
||||
PREV,
|
||||
NEXT
|
||||
}
|
||||
}
|
5
src/main/java/io/spring/application/Node.java
Normal file
5
src/main/java/io/spring/application/Node.java
Normal file
@ -0,0 +1,5 @@
|
||||
package io.spring.application;
|
||||
|
||||
public interface Node {
|
||||
String getCursor();
|
||||
}
|
@ -1,33 +1,31 @@
|
||||
package io.spring.application;
|
||||
|
||||
import lombok.Data;
|
||||
import lombok.Getter;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
@NoArgsConstructor
|
||||
@Data
|
||||
@Getter
|
||||
public class Page {
|
||||
private static final int MAX_LIMIT = 100;
|
||||
private int offset = 0;
|
||||
private int limit = 20;
|
||||
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);
|
||||
}
|
||||
public Page(int offset, int limit) {
|
||||
setOffset(offset);
|
||||
setLimit(limit);
|
||||
}
|
||||
|
||||
private void setOffset(int offset) {
|
||||
if (offset > 0) {
|
||||
this.offset = offset;
|
||||
}
|
||||
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;
|
||||
}
|
||||
private void setLimit(int limit) {
|
||||
if (limit > MAX_LIMIT) {
|
||||
this.limit = MAX_LIMIT;
|
||||
} else if (limit > 0) {
|
||||
this.limit = limit;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,28 +1,32 @@
|
||||
package io.spring.application.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import java.util.List;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
import org.joda.time.DateTime;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class ArticleData {
|
||||
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;
|
||||
}
|
||||
public class ArticleData implements io.spring.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 String getCursor() {
|
||||
return String.valueOf(getUpdatedAt().getMillis());
|
||||
}
|
||||
}
|
||||
|
@ -2,6 +2,7 @@ package io.spring.application.data;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.spring.application.Node;
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
@ -10,13 +11,18 @@ import org.joda.time.DateTime;
|
||||
@Data
|
||||
@NoArgsConstructor
|
||||
@AllArgsConstructor
|
||||
public class CommentData {
|
||||
private String id;
|
||||
private String body;
|
||||
@JsonIgnore
|
||||
private String articleId;
|
||||
private DateTime createdAt;
|
||||
private DateTime updatedAt;
|
||||
@JsonProperty("author")
|
||||
private ProfileData profileData;
|
||||
public class CommentData implements Node {
|
||||
private String id;
|
||||
private String body;
|
||||
@JsonIgnore private String articleId;
|
||||
private DateTime createdAt;
|
||||
private DateTime updatedAt;
|
||||
|
||||
@JsonProperty("author")
|
||||
private ProfileData profileData;
|
||||
|
||||
@Override
|
||||
public String getCursor() {
|
||||
return String.valueOf(createdAt.getMillis());
|
||||
}
|
||||
}
|
||||
|
@ -1,25 +1,42 @@
|
||||
package io.spring.infrastructure.mybatis.readservice;
|
||||
|
||||
import io.spring.application.CursorPageParameter;
|
||||
import io.spring.application.Page;
|
||||
import io.spring.application.data.ArticleData;
|
||||
import java.util.List;
|
||||
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 findById(@Param("id") String id);
|
||||
|
||||
ArticleData findBySlug(@Param("slug") String slug);
|
||||
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);
|
||||
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);
|
||||
int countArticle(
|
||||
@Param("tag") String tag,
|
||||
@Param("author") String author,
|
||||
@Param("favoritedBy") String favoritedBy);
|
||||
|
||||
List<ArticleData> findArticles(@Param("articleIds") List<String> articleIds);
|
||||
List<ArticleData> findArticles(@Param("articleIds") List<String> articleIds);
|
||||
|
||||
List<ArticleData> findArticlesOfAuthors(@Param("authors") List<String> authors, @Param("page") Page page);
|
||||
List<ArticleData> findArticlesOfAuthors(
|
||||
@Param("authors") List<String> authors, @Param("page") Page page);
|
||||
|
||||
int countFeedSize(@Param("authors") List<String> authors);
|
||||
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);
|
||||
}
|
||||
|
@ -1,14 +1,17 @@
|
||||
package io.spring.infrastructure.mybatis.readservice;
|
||||
|
||||
import io.spring.application.CursorPageParameter;
|
||||
import io.spring.application.data.CommentData;
|
||||
import java.util.List;
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Mapper
|
||||
public interface CommentReadService {
|
||||
CommentData findById(@Param("id") String id);
|
||||
CommentData findById(@Param("id") String id);
|
||||
|
||||
List<CommentData> findByArticleId(@Param("articleId") String articleId);
|
||||
List<CommentData> findByArticleId(@Param("articleId") String articleId);
|
||||
|
||||
List<CommentData> findByArticleIdWithCursor(
|
||||
@Param("articleId") String articleId, @Param("page") CursorPageParameter page);
|
||||
}
|
||||
|
1
src/main/resources/application-test.properties
Normal file
1
src/main/resources/application-test.properties
Normal file
@ -0,0 +1 @@
|
||||
spring.datasource.url=jdbc:sqlite::memory:
|
@ -1,3 +1,7 @@
|
||||
spring.datasource.url=jdbc:sqlite:dev.db
|
||||
spring.datasource.driver-class-name=org.sqlite.JDBC
|
||||
spring.datasource.username=
|
||||
spring.datasource.password=
|
||||
spring.jackson.deserialization.UNWRAP_ROOT_VALUE=true
|
||||
image.default=https://static.productionready.io/images/smiley-cyrus.jpg
|
||||
jwt.secret=nRvyYC4soFxBdZ-F-5Nnzz5USXstR1YylsTd-mA0aKtI9HUlriGrtkf-TiuDapkLiUCogO3JOK7kwZisrHp6wA
|
||||
@ -9,5 +13,6 @@ mybatis.configuration.use-generated-keys=true
|
||||
mybatis.type-handlers-package=io.spring.infrastructure.mybatis
|
||||
mybatis.mapper-locations=mapper/*.xml
|
||||
logging.level.io.spring.infrastructure.mybatis.readservice.ArticleReadService=DEBUG
|
||||
logging.level.io.spring.infrastructure.mybatis.mapper=DEBUG
|
||||
# Uncomment the following line to enable and allow access to the h2-console
|
||||
#spring.h2.console.enabled=true
|
||||
|
@ -24,6 +24,17 @@
|
||||
left join tags T on T.id = AT.tag_id
|
||||
left join users U on U.id = A.user_id
|
||||
</sql>
|
||||
<sql id="selectArticleIds">
|
||||
select
|
||||
DISTINCT(A.id) articleId, A.created_at
|
||||
from
|
||||
articles A
|
||||
left join article_tags AT on A.id = AT.article_id
|
||||
left join tags T on T.id = AT.tag_id
|
||||
left join article_favorites AF on AF.article_id = A.id
|
||||
left join users AU on AU.id = A.user_id
|
||||
left join users AFU on AFU.id = AF.user_id
|
||||
</sql>
|
||||
|
||||
<select id="findById" resultMap="transfer.data.articleData">
|
||||
<include refid="selectArticleData"/>
|
||||
@ -34,15 +45,7 @@
|
||||
where A.slug = #{slug}
|
||||
</select>
|
||||
<select id="queryArticles" resultMap="articleId">
|
||||
select
|
||||
DISTINCT(A.id) articleId, A.created_at
|
||||
from
|
||||
articles A
|
||||
left join article_tags AT on A.id = AT.article_id
|
||||
left join tags T on T.id = AT.tag_id
|
||||
left join article_favorites AF on AF.article_id = A.id
|
||||
left join users AU on AU.id = A.user_id
|
||||
left join users AFU on AFU.id = AF.user_id
|
||||
<include refid="selectArticleIds" />
|
||||
<where>
|
||||
<if test="tag != null">
|
||||
T.name = #{tag}
|
||||
@ -101,6 +104,55 @@
|
||||
#{id}
|
||||
</foreach>
|
||||
</select>
|
||||
<select id="findArticlesWithCursor" resultType="java.lang.String">
|
||||
<include refid="selectArticleIds" />
|
||||
<where>
|
||||
<if test="tag != null">
|
||||
T.name = #{tag}
|
||||
</if>
|
||||
<if test="author != null">
|
||||
AND AU.username = #{author}
|
||||
</if>
|
||||
<if test="favoritedBy != null">
|
||||
AND AFU.username = #{favoritedBy}
|
||||
</if>
|
||||
<if test='page.cursor != "" and page.direction.name() == "NEXT"'>
|
||||
AND A.created_at < #{page.cursor}
|
||||
</if>
|
||||
<if test='page.cursor != "" and page.direction.name() == "PREV"'>
|
||||
AND A.created_at > #{page.cursor}
|
||||
</if>
|
||||
</where>
|
||||
<if test='page.direction.name() == "NEXT"'>
|
||||
order by A.created_at desc
|
||||
</if>
|
||||
<if test='page.direction.name() == "PREV"'>
|
||||
order by A.created_at asc
|
||||
</if>
|
||||
limit #{page.queryLimit}
|
||||
</select>
|
||||
<select id="findArticlesOfAuthorsWithCursor" resultMap="transfer.data.articleData">
|
||||
<include refid="selectArticleData"/>
|
||||
<where>
|
||||
A.user_id in
|
||||
<foreach index="index" collection="authors" item="id" open="(" separator="," close=")">
|
||||
#{id}
|
||||
</foreach>
|
||||
<if test='page.cursor != "" and page.direction.name() == "NEXT"'>
|
||||
AND A.created_at < #{page.cursor}
|
||||
</if>
|
||||
<if test='page.cursor != "" and page.direction.name() == "PREV"'>
|
||||
AND A.created_at > #{page.cursor}
|
||||
</if>
|
||||
<if test='page.direction.name() == "NEXT"'>
|
||||
order by A.created_at desc
|
||||
</if>
|
||||
<if test='page.direction.name() == "PREV"'>
|
||||
order by A.created_at asc
|
||||
</if>
|
||||
</where>
|
||||
limit #{page.queryLimit}
|
||||
</select>
|
||||
|
||||
<resultMap id="articleId" type="string">
|
||||
<id javaType="string" column="articleId"/>
|
||||
|
@ -21,4 +21,22 @@
|
||||
<include refid="selectCommentData"/>
|
||||
where C.article_id = #{articleId}
|
||||
</select>
|
||||
<select id="findByArticleIdWithCursor" resultMap="transfer.data.commentData">
|
||||
<include refid="selectCommentData"/>
|
||||
<where>
|
||||
C.article_id = #{articleId}
|
||||
<if test='page.cursor != "" and page.direction.name() == "NEXT"'>
|
||||
AND C.created_at < #{page.cursor}
|
||||
</if>
|
||||
<if test='page.cursor != "" and page.direction.name() == "PREV"'>
|
||||
AND C.created_at > #{page.cursor}
|
||||
</if>
|
||||
</where>
|
||||
<if test='page.direction.name() == "NEXT"'>
|
||||
order by C.created_at desc
|
||||
</if>
|
||||
<if test='page.direction.name() == "PREV"'>
|
||||
order by C.created_at asc
|
||||
</if>
|
||||
</select>
|
||||
</mapper>
|
@ -6,6 +6,9 @@ import static org.junit.Assert.assertNotNull;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
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.Page;
|
||||
import io.spring.application.data.ArticleData;
|
||||
import io.spring.application.data.ArticleDataList;
|
||||
@ -16,6 +19,7 @@ import io.spring.core.favorite.ArticleFavoriteRepository;
|
||||
import io.spring.core.user.FollowRelation;
|
||||
import io.spring.core.user.User;
|
||||
import io.spring.core.user.UserRepository;
|
||||
import io.spring.infrastructure.DbTestBase;
|
||||
import io.spring.infrastructure.repository.MyBatisArticleFavoriteRepository;
|
||||
import io.spring.infrastructure.repository.MyBatisArticleRepository;
|
||||
import io.spring.infrastructure.repository.MyBatisUserRepository;
|
||||
@ -24,21 +28,16 @@ import java.util.Optional;
|
||||
import org.joda.time.DateTime;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@MybatisTest
|
||||
@Import({
|
||||
ArticleQueryService.class,
|
||||
MyBatisUserRepository.class,
|
||||
MyBatisArticleRepository.class,
|
||||
MyBatisArticleFavoriteRepository.class
|
||||
})
|
||||
public class ArticleQueryServiceTest {
|
||||
public class ArticleQueryServiceTest extends DbTestBase {
|
||||
@Autowired private ArticleQueryService queryService;
|
||||
|
||||
@Autowired private ArticleRepository articleRepository;
|
||||
@ -111,6 +110,40 @@ public class ArticleQueryServiceTest {
|
||||
assertEquals(nodata.getArticleDatas().size(), 0);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_get_default_article_list_by_cursor() {
|
||||
Article anotherArticle =
|
||||
new Article(
|
||||
"new article",
|
||||
"desc",
|
||||
"body",
|
||||
Arrays.asList("test"),
|
||||
user.getId(),
|
||||
new DateTime().minusHours(1));
|
||||
articleRepository.save(anotherArticle);
|
||||
|
||||
CursorPager<ArticleData> recentArticles =
|
||||
queryService.findRecentArticlesWithCursor(
|
||||
null, null, null, new CursorPageParameter("", 20, Direction.NEXT), user);
|
||||
assertEquals(recentArticles.getData().size(), 2);
|
||||
assertEquals(recentArticles.getData().get(0).getId(), article.getId());
|
||||
|
||||
CursorPager<ArticleData> nodata =
|
||||
queryService.findRecentArticlesWithCursor(
|
||||
null,
|
||||
null,
|
||||
null,
|
||||
new CursorPageParameter(recentArticles.getEndCursor(), 20, Direction.NEXT),
|
||||
user);
|
||||
assertEquals(nodata.getData().size(), 0);
|
||||
assertEquals(nodata.getStartCursor(), "");
|
||||
|
||||
CursorPager<ArticleData> prevArticles =
|
||||
queryService.findRecentArticlesWithCursor(
|
||||
null, null, null, new CursorPageParameter("", 20, Direction.PREV), user);
|
||||
assertEquals(prevArticles.getData().size(), 2);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_query_article_by_author() {
|
||||
User anotherUser = new User("other@email.com", "other", "123", "", "");
|
||||
|
@ -12,6 +12,7 @@ import io.spring.core.comment.CommentRepository;
|
||||
import io.spring.core.user.FollowRelation;
|
||||
import io.spring.core.user.User;
|
||||
import io.spring.core.user.UserRepository;
|
||||
import io.spring.infrastructure.DbTestBase;
|
||||
import io.spring.infrastructure.repository.MyBatisArticleRepository;
|
||||
import io.spring.infrastructure.repository.MyBatisCommentRepository;
|
||||
import io.spring.infrastructure.repository.MyBatisUserRepository;
|
||||
@ -20,21 +21,16 @@ import java.util.List;
|
||||
import java.util.Optional;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@MybatisTest
|
||||
@RunWith(SpringRunner.class)
|
||||
@Import({
|
||||
MyBatisCommentRepository.class,
|
||||
MyBatisUserRepository.class,
|
||||
CommentQueryService.class,
|
||||
MyBatisArticleRepository.class
|
||||
})
|
||||
public class CommentQueryServiceTest {
|
||||
public class CommentQueryServiceTest extends DbTestBase {
|
||||
@Autowired private CommentRepository commentRepository;
|
||||
|
||||
@Autowired private UserRepository userRepository;
|
||||
|
@ -1,37 +1,31 @@
|
||||
package io.spring.application.profile;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
import io.spring.application.ProfileQueryService;
|
||||
import io.spring.application.data.ProfileData;
|
||||
import io.spring.core.user.User;
|
||||
import io.spring.core.user.UserRepository;
|
||||
import io.spring.infrastructure.DbTestBase;
|
||||
import io.spring.infrastructure.repository.MyBatisUserRepository;
|
||||
import java.util.Optional;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@MybatisTest
|
||||
@Import({ProfileQueryService.class, MyBatisUserRepository.class})
|
||||
public class ProfileQueryServiceTest {
|
||||
@Autowired
|
||||
private ProfileQueryService profileQueryService;
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
public class ProfileQueryServiceTest extends DbTestBase {
|
||||
@Autowired private ProfileQueryService profileQueryService;
|
||||
@Autowired private UserRepository userRepository;
|
||||
|
||||
@Test
|
||||
public void should_fetch_profile_success() {
|
||||
User currentUser = new User("a@test.com", "a", "123", "", "");
|
||||
User profileUser = new User("p@test.com", "p", "123", "", "");
|
||||
userRepository.save(profileUser);
|
||||
@Test
|
||||
public void should_fetch_profile_success() {
|
||||
User currentUser = new User("a@test.com", "a", "123", "", "");
|
||||
User profileUser = new User("p@test.com", "p", "123", "", "");
|
||||
userRepository.save(profileUser);
|
||||
|
||||
Optional<ProfileData> optional = profileQueryService.findByUsername(profileUser.getUsername(), currentUser);
|
||||
assertTrue(optional.isPresent());
|
||||
}
|
||||
}
|
||||
Optional<ProfileData> optional =
|
||||
profileQueryService.findByUsername(profileUser.getUsername(), currentUser);
|
||||
assertTrue(optional.isPresent());
|
||||
}
|
||||
}
|
||||
|
@ -5,19 +5,15 @@ import static org.junit.Assert.assertTrue;
|
||||
import io.spring.application.TagsQueryService;
|
||||
import io.spring.core.article.Article;
|
||||
import io.spring.core.article.ArticleRepository;
|
||||
import io.spring.infrastructure.DbTestBase;
|
||||
import io.spring.infrastructure.repository.MyBatisArticleRepository;
|
||||
import java.util.Arrays;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@MybatisTest
|
||||
@Import({TagsQueryService.class, MyBatisArticleRepository.class})
|
||||
public class TagsQueryServiceTest {
|
||||
public class TagsQueryServiceTest extends DbTestBase {
|
||||
@Autowired private TagsQueryService tagsQueryService;
|
||||
|
||||
@Autowired private ArticleRepository articleRepository;
|
||||
|
14
src/test/java/io/spring/infrastructure/DbTestBase.java
Normal file
14
src/test/java/io/spring/infrastructure/DbTestBase.java
Normal file
@ -0,0 +1,14 @@
|
||||
package io.spring.infrastructure;
|
||||
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
|
||||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
|
||||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase.Replace;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@ActiveProfiles("test")
|
||||
@AutoConfigureTestDatabase(replace = Replace.NONE)
|
||||
@MybatisTest
|
||||
@RunWith(SpringRunner.class)
|
||||
public abstract class DbTestBase {}
|
@ -13,11 +13,13 @@ import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.jdbc.AutoConfigureTestDatabase;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.test.context.ActiveProfiles;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@ActiveProfiles("test")
|
||||
@SpringBootTest
|
||||
@AutoConfigureTestDatabase
|
||||
@AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
|
||||
@RunWith(SpringRunner.class)
|
||||
public class ArticleRepositoryTransactionTest {
|
||||
@Autowired private ArticleRepository articleRepository;
|
||||
|
||||
|
@ -10,22 +10,18 @@ import io.spring.core.article.ArticleRepository;
|
||||
import io.spring.core.article.Tag;
|
||||
import io.spring.core.user.User;
|
||||
import io.spring.core.user.UserRepository;
|
||||
import io.spring.infrastructure.DbTestBase;
|
||||
import io.spring.infrastructure.repository.MyBatisArticleRepository;
|
||||
import io.spring.infrastructure.repository.MyBatisUserRepository;
|
||||
import java.util.Arrays;
|
||||
import java.util.Optional;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
@MybatisTest
|
||||
@RunWith(SpringRunner.class)
|
||||
@Import({MyBatisArticleRepository.class, MyBatisUserRepository.class})
|
||||
public class MyBatisArticleRepositoryTest {
|
||||
public class MyBatisArticleRepositoryTest extends DbTestBase {
|
||||
@Autowired private ArticleRepository articleRepository;
|
||||
|
||||
@Autowired private UserRepository userRepository;
|
||||
|
@ -1,34 +1,28 @@
|
||||
package io.spring.infrastructure.comment;
|
||||
|
||||
import io.spring.core.comment.Comment;
|
||||
import io.spring.core.comment.CommentRepository;
|
||||
import io.spring.infrastructure.repository.MyBatisCommentRepository;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@MybatisTest
|
||||
@RunWith(SpringRunner.class)
|
||||
import io.spring.core.comment.Comment;
|
||||
import io.spring.core.comment.CommentRepository;
|
||||
import io.spring.infrastructure.DbTestBase;
|
||||
import io.spring.infrastructure.repository.MyBatisCommentRepository;
|
||||
import java.util.Optional;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
@Import({MyBatisCommentRepository.class})
|
||||
public class MyBatisCommentRepositoryTest {
|
||||
@Autowired
|
||||
private CommentRepository commentRepository;
|
||||
public class MyBatisCommentRepositoryTest extends DbTestBase {
|
||||
@Autowired private CommentRepository commentRepository;
|
||||
|
||||
@Test
|
||||
public void should_create_and_fetch_comment_success() {
|
||||
Comment comment = new Comment("content", "123", "456");
|
||||
commentRepository.save(comment);
|
||||
@Test
|
||||
public void should_create_and_fetch_comment_success() {
|
||||
Comment comment = new Comment("content", "123", "456");
|
||||
commentRepository.save(comment);
|
||||
|
||||
Optional<Comment> optional = commentRepository.findById("456", comment.getId());
|
||||
assertTrue(optional.isPresent());
|
||||
assertEquals(optional.get(), comment);
|
||||
}
|
||||
}
|
||||
Optional<Comment> optional = commentRepository.findById("456", comment.getId());
|
||||
assertTrue(optional.isPresent());
|
||||
assertEquals(optional.get(), comment);
|
||||
}
|
||||
}
|
||||
|
@ -1,40 +1,36 @@
|
||||
package io.spring.infrastructure.favorite;
|
||||
|
||||
import io.spring.core.favorite.ArticleFavorite;
|
||||
import io.spring.core.favorite.ArticleFavoriteRepository;
|
||||
import io.spring.infrastructure.repository.MyBatisArticleFavoriteRepository;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertNotNull;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@MybatisTest
|
||||
import io.spring.core.favorite.ArticleFavorite;
|
||||
import io.spring.core.favorite.ArticleFavoriteRepository;
|
||||
import io.spring.infrastructure.DbTestBase;
|
||||
import io.spring.infrastructure.repository.MyBatisArticleFavoriteRepository;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
@Import({MyBatisArticleFavoriteRepository.class})
|
||||
public class MyBatisArticleFavoriteRepositoryTest {
|
||||
@Autowired
|
||||
private ArticleFavoriteRepository articleFavoriteRepository;
|
||||
public class MyBatisArticleFavoriteRepositoryTest extends DbTestBase {
|
||||
@Autowired private ArticleFavoriteRepository articleFavoriteRepository;
|
||||
|
||||
@Autowired
|
||||
private io.spring.infrastructure.mybatis.mapper.ArticleFavoriteMapper articleFavoriteMapper;
|
||||
@Autowired
|
||||
private io.spring.infrastructure.mybatis.mapper.ArticleFavoriteMapper articleFavoriteMapper;
|
||||
|
||||
@Test
|
||||
public void should_save_and_fetch_articleFavorite_success() {
|
||||
ArticleFavorite articleFavorite = new ArticleFavorite("123", "456");
|
||||
articleFavoriteRepository.save(articleFavorite);
|
||||
assertNotNull(articleFavoriteMapper.find(articleFavorite.getArticleId(), articleFavorite.getUserId()));
|
||||
}
|
||||
@Test
|
||||
public void should_save_and_fetch_articleFavorite_success() {
|
||||
ArticleFavorite articleFavorite = new ArticleFavorite("123", "456");
|
||||
articleFavoriteRepository.save(articleFavorite);
|
||||
assertNotNull(
|
||||
articleFavoriteMapper.find(articleFavorite.getArticleId(), articleFavorite.getUserId()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_remove_favorite_success() {
|
||||
ArticleFavorite articleFavorite = new ArticleFavorite("123", "456");
|
||||
articleFavoriteRepository.save(articleFavorite);
|
||||
articleFavoriteRepository.remove(articleFavorite);
|
||||
assertFalse(articleFavoriteRepository.find("123", "456").isPresent());
|
||||
}
|
||||
}
|
||||
@Test
|
||||
public void should_remove_favorite_success() {
|
||||
ArticleFavorite articleFavorite = new ArticleFavorite("123", "456");
|
||||
articleFavoriteRepository.save(articleFavorite);
|
||||
articleFavoriteRepository.remove(articleFavorite);
|
||||
assertFalse(articleFavoriteRepository.find("123", "456").isPresent());
|
||||
}
|
||||
}
|
||||
|
@ -1,82 +1,76 @@
|
||||
package io.spring.infrastructure.user;
|
||||
|
||||
import io.spring.core.user.FollowRelation;
|
||||
import io.spring.core.user.User;
|
||||
import io.spring.core.user.UserRepository;
|
||||
import io.spring.infrastructure.repository.MyBatisUserRepository;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mybatis.spring.boot.test.autoconfigure.MybatisTest;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.junit.Assert.assertTrue;
|
||||
|
||||
@RunWith(SpringRunner.class)
|
||||
@MybatisTest
|
||||
import io.spring.core.user.FollowRelation;
|
||||
import io.spring.core.user.User;
|
||||
import io.spring.core.user.UserRepository;
|
||||
import io.spring.infrastructure.DbTestBase;
|
||||
import io.spring.infrastructure.repository.MyBatisUserRepository;
|
||||
import java.util.Optional;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
@Import(MyBatisUserRepository.class)
|
||||
public class MyBatisUserRepositoryTest {
|
||||
@Autowired
|
||||
private UserRepository userRepository;
|
||||
private User user;
|
||||
public class MyBatisUserRepositoryTest extends DbTestBase {
|
||||
@Autowired private UserRepository userRepository;
|
||||
private User user;
|
||||
|
||||
@Before
|
||||
public void setUp() {
|
||||
user = new User("aisensiy@163.com", "aisensiy", "123", "", "default");
|
||||
}
|
||||
@Before
|
||||
public void setUp() {
|
||||
user = new User("aisensiy@163.com", "aisensiy", "123", "", "default");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_save_and_fetch_user_success() {
|
||||
userRepository.save(user);
|
||||
Optional<User> userOptional = userRepository.findByUsername("aisensiy");
|
||||
assertEquals(userOptional.get(), user);
|
||||
Optional<User> userOptional2 = userRepository.findByEmail("aisensiy@163.com");
|
||||
assertEquals(userOptional2.get(), user);
|
||||
}
|
||||
@Test
|
||||
public void should_save_and_fetch_user_success() {
|
||||
userRepository.save(user);
|
||||
Optional<User> userOptional = userRepository.findByUsername("aisensiy");
|
||||
assertEquals(userOptional.get(), user);
|
||||
Optional<User> userOptional2 = userRepository.findByEmail("aisensiy@163.com");
|
||||
assertEquals(userOptional2.get(), user);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_update_user_success() {
|
||||
String newEmail = "newemail@email.com";
|
||||
user.update(newEmail, "", "", "", "");
|
||||
userRepository.save(user);
|
||||
Optional<User> optional = userRepository.findByUsername(user.getUsername());
|
||||
assertTrue(optional.isPresent());
|
||||
assertEquals(optional.get().getEmail(), newEmail);
|
||||
@Test
|
||||
public void should_update_user_success() {
|
||||
String newEmail = "newemail@email.com";
|
||||
user.update(newEmail, "", "", "", "");
|
||||
userRepository.save(user);
|
||||
Optional<User> optional = userRepository.findByUsername(user.getUsername());
|
||||
assertTrue(optional.isPresent());
|
||||
assertEquals(optional.get().getEmail(), newEmail);
|
||||
|
||||
String newUsername = "newUsername";
|
||||
user.update("", newUsername, "", "", "");
|
||||
userRepository.save(user);
|
||||
optional = userRepository.findByEmail(user.getEmail());
|
||||
assertTrue(optional.isPresent());
|
||||
assertEquals(optional.get().getUsername(), newUsername);
|
||||
assertEquals(optional.get().getImage(), user.getImage());
|
||||
}
|
||||
String newUsername = "newUsername";
|
||||
user.update("", newUsername, "", "", "");
|
||||
userRepository.save(user);
|
||||
optional = userRepository.findByEmail(user.getEmail());
|
||||
assertTrue(optional.isPresent());
|
||||
assertEquals(optional.get().getUsername(), newUsername);
|
||||
assertEquals(optional.get().getImage(), user.getImage());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_create_new_user_follow_success() {
|
||||
User other = new User("other@example.com", "other", "123", "", "");
|
||||
userRepository.save(other);
|
||||
@Test
|
||||
public void should_create_new_user_follow_success() {
|
||||
User other = new User("other@example.com", "other", "123", "", "");
|
||||
userRepository.save(other);
|
||||
|
||||
FollowRelation followRelation = new FollowRelation(user.getId(), other.getId());
|
||||
userRepository.saveRelation(followRelation);
|
||||
assertTrue(userRepository.findRelation(user.getId(), other.getId()).isPresent());
|
||||
}
|
||||
FollowRelation followRelation = new FollowRelation(user.getId(), other.getId());
|
||||
userRepository.saveRelation(followRelation);
|
||||
assertTrue(userRepository.findRelation(user.getId(), other.getId()).isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_unfollow_user_success() {
|
||||
User other = new User("other@example.com", "other", "123", "", "");
|
||||
userRepository.save(other);
|
||||
@Test
|
||||
public void should_unfollow_user_success() {
|
||||
User other = new User("other@example.com", "other", "123", "", "");
|
||||
userRepository.save(other);
|
||||
|
||||
FollowRelation followRelation = new FollowRelation(user.getId(), other.getId());
|
||||
userRepository.saveRelation(followRelation);
|
||||
FollowRelation followRelation = new FollowRelation(user.getId(), other.getId());
|
||||
userRepository.saveRelation(followRelation);
|
||||
|
||||
userRepository.removeRelation(followRelation);
|
||||
assertFalse(userRepository.findRelation(user.getId(), other.getId()).isPresent());
|
||||
}
|
||||
}
|
||||
userRepository.removeRelation(followRelation);
|
||||
assertFalse(userRepository.findRelation(user.getId(), other.getId()).isPresent());
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user