bugfix
This commit is contained in:
parent
d6bf680a97
commit
c3029aa636
@ -13,6 +13,7 @@ buildscript {
|
||||
apply plugin: 'java'
|
||||
apply plugin: 'eclipse'
|
||||
apply plugin: 'idea'
|
||||
apply plugin: 'findbugs'
|
||||
apply plugin: 'org.springframework.boot'
|
||||
|
||||
version = '0.0.1-SNAPSHOT'
|
||||
@ -22,6 +23,12 @@ repositories {
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
tasks.withType(FindBugs) {
|
||||
reports {
|
||||
xml.enabled false
|
||||
html.enabled true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile('org.springframework.boot:spring-boot-starter-data-jpa')
|
||||
|
@ -28,7 +28,6 @@ import org.springframework.web.bind.annotation.RequestMethod;
|
||||
import org.springframework.web.bind.annotation.RestController;
|
||||
|
||||
import javax.validation.Valid;
|
||||
import javax.xml.ws.Response;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
@ -67,7 +66,7 @@ public class CommentsApi {
|
||||
public ResponseEntity getComments(@PathVariable("slug") String slug,
|
||||
@AuthenticationPrincipal User user) {
|
||||
Article article = findArticle(slug);
|
||||
List<CommentData> comments = commentQueryService.findByArticleSlug(article.getSlug(), user);
|
||||
List<CommentData> comments = commentQueryService.findByArticleId(article.getId(), user);
|
||||
return ResponseEntity.ok(new HashMap<String, Object>() {{
|
||||
put("comments", comments);
|
||||
}});
|
||||
|
@ -1,6 +1,7 @@
|
||||
package io.spring.api;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonRootName;
|
||||
import io.spring.api.exception.InvalidRequestException;
|
||||
import io.spring.application.user.UserQueryService;
|
||||
import io.spring.application.user.UserWithToken;
|
||||
import io.spring.core.user.User;
|
||||
@ -22,6 +23,7 @@ import org.springframework.web.bind.annotation.RestController;
|
||||
import javax.validation.Valid;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
@RestController
|
||||
@RequestMapping(path = "/user")
|
||||
@ -43,9 +45,13 @@ public class CurrentUserApi {
|
||||
|
||||
@PutMapping
|
||||
public ResponseEntity updateProfile(@AuthenticationPrincipal User currentUser,
|
||||
@RequestHeader(value = "Authorization") String authorization,
|
||||
@Valid @RequestBody UpdateUserParam updateUserParam,
|
||||
BindingResult bindingResult) {
|
||||
if (bindingResult.hasErrors()) {
|
||||
throw new InvalidRequestException(bindingResult);
|
||||
}
|
||||
checkUniquenessOfUsernameAndEmail(currentUser, updateUserParam, bindingResult);
|
||||
|
||||
currentUser.update(
|
||||
updateUserParam.getEmail(),
|
||||
updateUserParam.getUsername(),
|
||||
@ -53,7 +59,27 @@ public class CurrentUserApi {
|
||||
updateUserParam.getBio(),
|
||||
updateUserParam.getImage());
|
||||
userRepository.save(currentUser);
|
||||
return ResponseEntity.ok(userResponse(userQueryService.fetchCurrentUser(currentUser.getUsername(), authorization.split(" ")[1])));
|
||||
return ResponseEntity.ok(userResponse(userQueryService.fetchNewAuthenticatedUser(currentUser.getUsername())));
|
||||
}
|
||||
|
||||
private void checkUniquenessOfUsernameAndEmail(User currentUser, UpdateUserParam updateUserParam, BindingResult bindingResult) {
|
||||
if (!"".equals(updateUserParam.getUsername())) {
|
||||
Optional<User> byUsername = userRepository.findByUsername(updateUserParam.getUsername());
|
||||
if (byUsername.isPresent() && !byUsername.get().equals(currentUser)) {
|
||||
bindingResult.rejectValue("username", "DUPLICATED", "username already exist");
|
||||
}
|
||||
}
|
||||
|
||||
if (!"".equals(updateUserParam.getEmail())) {
|
||||
Optional<User> byEmail = userRepository.findByEmail(updateUserParam.getEmail());
|
||||
if (byEmail.isPresent() && !byEmail.get().equals(currentUser)) {
|
||||
bindingResult.rejectValue("email", "DUPLICATED", "email already exist");
|
||||
}
|
||||
}
|
||||
|
||||
if (bindingResult.hasErrors()) {
|
||||
throw new InvalidRequestException(bindingResult);
|
||||
}
|
||||
}
|
||||
|
||||
private Map<String, Object> userResponse(UserWithToken userWithToken) {
|
||||
|
25
src/main/java/io/spring/api/security/CORSConfig.java
Normal file
25
src/main/java/io/spring/api/security/CORSConfig.java
Normal file
@ -0,0 +1,25 @@
|
||||
package io.spring.api.security;
|
||||
|
||||
import org.springframework.boot.web.servlet.FilterRegistrationBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.cors.CorsConfiguration;
|
||||
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
|
||||
import org.springframework.web.filter.CorsFilter;
|
||||
|
||||
@Configuration
|
||||
public class CORSConfig {
|
||||
@Bean
|
||||
public FilterRegistrationBean corsFilter() {
|
||||
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
|
||||
CorsConfiguration config = new CorsConfiguration();
|
||||
config.setAllowCredentials(true);
|
||||
config.addAllowedOrigin("*");
|
||||
config.addAllowedHeader("*");
|
||||
config.addAllowedMethod("*");
|
||||
source.registerCorsConfiguration("/**", config);
|
||||
FilterRegistrationBean bean = new FilterRegistrationBean(new CorsFilter(source));
|
||||
bean.setOrder(0);
|
||||
return bean;
|
||||
}
|
||||
}
|
@ -30,9 +30,9 @@ public class JwtTokenFilter extends OncePerRequestFilter {
|
||||
@Override
|
||||
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
|
||||
getTokenString(request.getHeader(header)).ifPresent(token -> {
|
||||
jwtService.getSubFromToken(token).ifPresent(username -> {
|
||||
jwtService.getSubFromToken(token).ifPresent(id -> {
|
||||
if (SecurityContextHolder.getContext().getAuthentication() == null) {
|
||||
User user = userRepository.findByUsername(username).get();
|
||||
userRepository.findById(id).ifPresent(user -> {
|
||||
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
|
||||
user,
|
||||
null,
|
||||
@ -40,6 +40,7 @@ public class JwtTokenFilter extends OncePerRequestFilter {
|
||||
);
|
||||
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
|
||||
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
|
||||
});
|
||||
}
|
||||
});
|
||||
});
|
||||
|
@ -25,9 +25,10 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
||||
.and()
|
||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
||||
.authorizeRequests()
|
||||
.antMatchers(HttpMethod.OPTIONS).permitAll()
|
||||
.antMatchers(HttpMethod.GET, "/articles/feed").authenticated()
|
||||
.antMatchers(HttpMethod.POST, "/users", "/users/login").permitAll()
|
||||
.antMatchers(HttpMethod.GET, "/articles/**", "/profiles/**").permitAll()
|
||||
.antMatchers(HttpMethod.GET, "/articles/**", "/profiles/**", "/tags").permitAll()
|
||||
.anyRequest().authenticated();
|
||||
|
||||
http.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||
|
@ -4,9 +4,10 @@ import io.spring.application.profile.UserRelationshipQueryService;
|
||||
import io.spring.core.user.User;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
@Service
|
||||
public class CommentQueryService {
|
||||
@ -31,7 +32,16 @@ public class CommentQueryService {
|
||||
return Optional.ofNullable(commentData);
|
||||
}
|
||||
|
||||
public List<CommentData> findByArticleSlug(String slug, User user) {
|
||||
return new ArrayList<>();
|
||||
public List<CommentData> findByArticleId(String articleId, User user) {
|
||||
List<CommentData> comments = commentReadService.findByArticleId(articleId);
|
||||
if (comments.size() > 0) {
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
@ -1,10 +1,15 @@
|
||||
package io.spring.application.comment;
|
||||
|
||||
import org.apache.ibatis.annotations.Mapper;
|
||||
import org.apache.ibatis.annotations.Param;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Component
|
||||
@Mapper
|
||||
public interface CommentReadService {
|
||||
CommentData findById(String id);
|
||||
CommentData findById(@Param("id") String id);
|
||||
|
||||
List<CommentData> findByArticleId(@Param("articleId") String articleId);
|
||||
}
|
||||
|
@ -53,6 +53,7 @@ public class Article {
|
||||
if (!"".equals(body)) {
|
||||
this.body = body;
|
||||
}
|
||||
this.updatedAt = new DateTime();
|
||||
}
|
||||
|
||||
private String toSlug(String title) {
|
||||
|
@ -28,7 +28,7 @@ public class DefaultJwtService implements JwtService {
|
||||
@Override
|
||||
public String toToken(UserData userData) {
|
||||
return Jwts.builder()
|
||||
.setSubject(userData.getUsername())
|
||||
.setSubject(userData.getId())
|
||||
.setExpiration(expireTimeFromNow())
|
||||
.signWith(SignatureAlgorithm.HS512, secret)
|
||||
.compact();
|
||||
|
@ -8,7 +8,7 @@
|
||||
select count(1) from article_favorites where article_id = #{articleId}
|
||||
</select>
|
||||
<select id="articlesFavoriteCount" resultMap="favoriteCount">
|
||||
select A.id, count(A.id) as favoriteCount from articles A
|
||||
select A.id, count(AF.user_id) as favoriteCount from articles A
|
||||
left join article_favorites AF on A.id = AF.article_id
|
||||
where id in
|
||||
<foreach collection="ids" item="item" separator="," open="(" close=")">
|
||||
|
@ -1,7 +1,7 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||
<mapper namespace="io.spring.application.comment.CommentReadService">
|
||||
<select id="findById" resultMap="commentData">
|
||||
<sql id="selectCommentData">
|
||||
SELECT
|
||||
C.id commentId,
|
||||
C.body commentBody,
|
||||
@ -10,8 +10,16 @@
|
||||
from comments C
|
||||
left join users U
|
||||
on C.user_id = U.id
|
||||
</sql>
|
||||
|
||||
<select id="findById" resultMap="commentData">
|
||||
<include refid="selectCommentData"/>
|
||||
where C.id = #{id}
|
||||
</select>
|
||||
<select id="findByArticleId" resultMap="commentData">
|
||||
<include refid="selectCommentData"/>
|
||||
where C.article_id = #{articleId}
|
||||
</select>
|
||||
|
||||
<resultMap id="commentData" type="io.spring.application.comment.CommentData">
|
||||
<id column="commentId" property="id"/>
|
||||
|
@ -115,7 +115,7 @@ public class CommentsApiTest extends TestWithCurrentUser {
|
||||
|
||||
@Test
|
||||
public void should_get_comments_of_article_success() throws Exception {
|
||||
when(commentQueryService.findByArticleSlug(anyString(), eq(null))).thenReturn(Arrays.asList(commentData));
|
||||
when(commentQueryService.findByArticleId(anyString(), eq(null))).thenReturn(Arrays.asList(commentData));
|
||||
RestAssured.when()
|
||||
.get("/articles/{slug}/comments", article.getSlug())
|
||||
.prettyPeek()
|
||||
|
@ -1,20 +1,29 @@
|
||||
package io.spring.api;
|
||||
|
||||
import io.restassured.RestAssured;
|
||||
import io.spring.application.JwtService;
|
||||
import io.spring.application.user.UserData;
|
||||
import io.spring.core.user.User;
|
||||
import org.junit.Before;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.context.embedded.LocalServerPort;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import static io.restassured.RestAssured.given;
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
import static org.hamcrest.CoreMatchers.not;
|
||||
import static org.hamcrest.MatcherAssert.assertThat;
|
||||
import static org.hamcrest.core.IsEqual.equalTo;
|
||||
import static org.mockito.Matchers.eq;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.boot.test.context.SpringBootTest.WebEnvironment.RANDOM_PORT;
|
||||
|
||||
@SpringBootTest(webEnvironment = RANDOM_PORT)
|
||||
@ -24,16 +33,9 @@ public class CurrentUserApiTest extends TestWithCurrentUser {
|
||||
@LocalServerPort
|
||||
private int port;
|
||||
|
||||
protected String email;
|
||||
protected String username;
|
||||
protected String defaultAvatar;
|
||||
|
||||
@Before
|
||||
public void setUp() throws Exception {
|
||||
RestAssured.port = port;
|
||||
email = "john@jacob.com";
|
||||
username = "johnjacob";
|
||||
defaultAvatar = "https://static.productionready.io/images/smiley-cyrus.jpg";
|
||||
userFixture();
|
||||
}
|
||||
|
||||
@ -51,7 +53,7 @@ public class CurrentUserApiTest extends TestWithCurrentUser {
|
||||
.body("user.email", equalTo(email))
|
||||
.body("user.username", equalTo(username))
|
||||
.body("user.bio", equalTo(""))
|
||||
.body("user.image", equalTo("https://static.productionready.io/images/smiley-cyrus.jpg"))
|
||||
.body("user.image", equalTo(defaultAvatar))
|
||||
.body("user.token", equalTo(token));
|
||||
}
|
||||
|
||||
@ -81,14 +83,18 @@ public class CurrentUserApiTest extends TestWithCurrentUser {
|
||||
public void should_update_current_user_profile() throws Exception {
|
||||
String newEmail = "newemail@example.com";
|
||||
String newBio = "updated";
|
||||
String newUsername = "newusernamee";
|
||||
|
||||
Map<String, Object> param = new HashMap<String, Object>() {{
|
||||
put("user", new HashMap<String, Object>() {{
|
||||
put("email", newEmail);
|
||||
put("bio", newBio);
|
||||
put("username", newUsername);
|
||||
}});
|
||||
}};
|
||||
|
||||
when(userReadService.findByUsername(eq(newUsername))).thenReturn(new UserData(user.getId(), newEmail, newUsername, newBio, user.getImage()));
|
||||
|
||||
given()
|
||||
.contentType("application/json")
|
||||
.header("Authorization", "Token " + token)
|
||||
@ -96,11 +102,44 @@ public class CurrentUserApiTest extends TestWithCurrentUser {
|
||||
.when()
|
||||
.put("/user")
|
||||
.then()
|
||||
.statusCode(200);
|
||||
.statusCode(200)
|
||||
.body("user.token", not(token));
|
||||
}
|
||||
|
||||
assertThat(user.getEmail(), is(newEmail));
|
||||
assertThat(user.getBio(), is(newBio));
|
||||
assertThat(user.getImage(), is(defaultAvatar));
|
||||
@Test
|
||||
public void should_get_error_if_email_exists_when_update_user_profile() throws Exception {
|
||||
String newEmail = "newemail@example.com";
|
||||
String newBio = "updated";
|
||||
String newUsername = "newusernamee";
|
||||
|
||||
Map<String, Object> param = prepareUpdateParam(newEmail, newBio, newUsername);
|
||||
|
||||
when(userRepository.findByEmail(eq(newEmail))).thenReturn(Optional.of(new User(newEmail, "username", "123", "", "")));
|
||||
when(userRepository.findByUsername(eq(newUsername))).thenReturn(Optional.empty());
|
||||
|
||||
when(userReadService.findByUsername(eq(newUsername))).thenReturn(new UserData(user.getId(), newEmail, newUsername, newBio, user.getImage()));
|
||||
|
||||
given()
|
||||
.contentType("application/json")
|
||||
.header("Authorization", "Token " + token)
|
||||
.body(param)
|
||||
.when()
|
||||
.put("/user")
|
||||
.prettyPeek()
|
||||
.then()
|
||||
.statusCode(422)
|
||||
.body("errors.email[0]", equalTo("email already exist"));
|
||||
|
||||
}
|
||||
|
||||
private HashMap<String, Object> prepareUpdateParam(final String newEmail, final String newBio, final String newUsername) {
|
||||
return new HashMap<String, Object>() {{
|
||||
put("user", new HashMap<String, Object>() {{
|
||||
put("email", newEmail);
|
||||
put("bio", newBio);
|
||||
put("username", newUsername);
|
||||
}});
|
||||
}};
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -1,9 +1,13 @@
|
||||
package io.spring.application.comment;
|
||||
|
||||
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.user.FollowRelation;
|
||||
import io.spring.core.user.User;
|
||||
import io.spring.core.user.UserRepository;
|
||||
import io.spring.infrastructure.article.MyBatisArticleRepository;
|
||||
import io.spring.infrastructure.comment.MyBatisCommentRepository;
|
||||
import io.spring.infrastructure.user.MyBatisUserRepository;
|
||||
import org.junit.Before;
|
||||
@ -14,6 +18,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.test.context.junit4.SpringRunner;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import static org.hamcrest.CoreMatchers.is;
|
||||
@ -21,7 +26,7 @@ import static org.junit.Assert.*;
|
||||
|
||||
@MybatisTest
|
||||
@RunWith(SpringRunner.class)
|
||||
@Import({MyBatisCommentRepository.class, MyBatisUserRepository.class, CommentQueryService.class})
|
||||
@Import({MyBatisCommentRepository.class, MyBatisUserRepository.class, CommentQueryService.class, MyBatisArticleRepository.class})
|
||||
public class CommentQueryServiceTest {
|
||||
@Autowired
|
||||
private CommentRepository commentRepository;
|
||||
@ -31,6 +36,10 @@ public class CommentQueryServiceTest {
|
||||
|
||||
@Autowired
|
||||
private CommentQueryService commentQueryService;
|
||||
|
||||
@Autowired
|
||||
private ArticleRepository articleRepository;
|
||||
|
||||
private User user;
|
||||
|
||||
@Before
|
||||
@ -49,4 +58,23 @@ public class CommentQueryServiceTest {
|
||||
CommentData commentData = optional.get();
|
||||
assertThat(commentData.getProfileData().getUsername(), is(user.getUsername()));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void should_read_comments_of_article() throws Exception {
|
||||
Article article = new Article("title", "desc", "body", new String[]{"java"}, user.getId());
|
||||
articleRepository.save(article);
|
||||
|
||||
User user2 = new User("user2@email.com", "user2", "123", "", "");
|
||||
userRepository.save(user2);
|
||||
userRepository.saveRelation(new FollowRelation(user.getId(), user2.getId()));
|
||||
|
||||
Comment comment1 = new Comment("content1", user.getId(), article.getId());
|
||||
commentRepository.save(comment1);
|
||||
Comment comment2 = new Comment("content2", user2.getId(), article.getId());
|
||||
commentRepository.save(comment2);
|
||||
|
||||
List<CommentData> comments = commentQueryService.findByArticleId(article.getId(), user);
|
||||
assertThat(comments.size(), is(2));
|
||||
|
||||
}
|
||||
}
|
@ -2,8 +2,6 @@ package io.spring.application.tag;
|
||||
|
||||
import io.spring.core.article.Article;
|
||||
import io.spring.core.article.ArticleRepository;
|
||||
import io.spring.core.article.Tag;
|
||||
import io.spring.infrastructure.article.ArticleMapper;
|
||||
import io.spring.infrastructure.article.MyBatisArticleRepository;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
Loading…
Reference in New Issue
Block a user