read comments

This commit is contained in:
aisensiy 2017-08-15 17:52:23 +08:00
parent 445311ee1b
commit f31bcbc6e0
16 changed files with 116 additions and 45 deletions

View File

@ -22,6 +22,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
@RestController @RestController
@RequestMapping(path = "/articles/{slug}") @RequestMapping(path = "/articles/{slug}")
@ -36,13 +38,15 @@ public class ArticleApi {
} }
@GetMapping @GetMapping
public ResponseEntity<ArticleData> article(@PathVariable("slug") String slug, public ResponseEntity<?> article(@PathVariable("slug") String slug,
@AuthenticationPrincipal User user) { @AuthenticationPrincipal User user) {
return articleQueryService.findBySlug(slug, user).map(ResponseEntity::ok).orElseThrow(ResourceNotFoundException::new); return articleQueryService.findBySlug(slug, user)
.map(articleData -> ResponseEntity.ok(articleResponse(articleData)))
.orElseThrow(ResourceNotFoundException::new);
} }
@PutMapping @PutMapping
public ResponseEntity<ArticleData> updateArticle(@PathVariable("slug") String slug, public ResponseEntity<?> updateArticle(@PathVariable("slug") String slug,
@AuthenticationPrincipal User user, @AuthenticationPrincipal User user,
@Valid @RequestBody UpdateArticleParam updateArticleParam) { @Valid @RequestBody UpdateArticleParam updateArticleParam) {
return articleRepository.findBySlug(slug).map(article -> { return articleRepository.findBySlug(slug).map(article -> {
@ -54,7 +58,7 @@ public class ArticleApi {
updateArticleParam.getDescription(), updateArticleParam.getDescription(),
updateArticleParam.getBody()); updateArticleParam.getBody());
articleRepository.save(article); articleRepository.save(article);
return ResponseEntity.ok(articleQueryService.findBySlug(slug, user).get()); return ResponseEntity.ok(articleResponse(articleQueryService.findBySlug(slug, user).get()));
}).orElseThrow(ResourceNotFoundException::new); }).orElseThrow(ResourceNotFoundException::new);
} }
@ -69,6 +73,12 @@ public class ArticleApi {
return ResponseEntity.noContent().build(); return ResponseEntity.noContent().build();
}).orElseThrow(ResourceNotFoundException::new); }).orElseThrow(ResourceNotFoundException::new);
} }
private Map<String, Object> articleResponse(ArticleData articleData) {
return new HashMap<String, Object>() {{
put("article", articleData);
}};
}
} }
@Getter @Getter

View File

@ -19,6 +19,7 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.HashMap;
@RestController @RestController
@RequestMapping(path = "/articles") @RequestMapping(path = "/articles")
@ -47,7 +48,9 @@ public class ArticlesApi {
newArticleParam.getTagList(), newArticleParam.getTagList(),
user.getId()); user.getId());
articleRepository.save(article); articleRepository.save(article);
return ResponseEntity.ok(articleQueryService.findById(article.getId(), user).get()); return ResponseEntity.ok(new HashMap<String, Object>() {{
put("article", articleQueryService.findById(article.getId(), user).get());
}});
} }
} }

View File

@ -17,6 +17,7 @@ import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.security.core.annotation.AuthenticationPrincipal; import org.springframework.security.core.annotation.AuthenticationPrincipal;
import org.springframework.validation.BindingResult; import org.springframework.validation.BindingResult;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@ -25,6 +26,9 @@ import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid; import javax.validation.Valid;
import javax.xml.ws.Response; import javax.xml.ws.Response;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@RestController @RestController
@RequestMapping(path = "/articles/{slug}/comments") @RequestMapping(path = "/articles/{slug}/comments")
@ -43,7 +47,7 @@ public class CommentsApi {
} }
@PostMapping @PostMapping
public ResponseEntity<CommentData> createComment(@PathVariable("slug") String slug, public ResponseEntity<?> createComment(@PathVariable("slug") String slug,
@AuthenticationPrincipal User user, @AuthenticationPrincipal User user,
@Valid @RequestBody NewCommentParam newCommentParam, @Valid @RequestBody NewCommentParam newCommentParam,
BindingResult bindingResult) { BindingResult bindingResult) {
@ -53,12 +57,28 @@ public class CommentsApi {
} }
Comment comment = new Comment(newCommentParam.getBody(), user.getId(), article.getId()); Comment comment = new Comment(newCommentParam.getBody(), user.getId(), article.getId());
commentRepository.save(comment); commentRepository.save(comment);
return ResponseEntity.status(201).body(commentQueryService.findById(comment.getId(), user).get()); return ResponseEntity.status(201).body(commentResponse(commentQueryService.findById(comment.getId(), user).get()));
}
@GetMapping
public ResponseEntity getComments(@PathVariable("slug") String slug,
@AuthenticationPrincipal User user) {
Article article = findArticle(slug);
List<CommentData> comments = commentQueryService.findByArticleSlug(article.getSlug(), user);
return ResponseEntity.ok(new HashMap<String, Object>() {{
put("comments", comments);
}});
} }
private Article findArticle(String slug) { private Article findArticle(String slug) {
return articleRepository.findBySlug(slug).map(article -> article).orElseThrow(ResourceNotFoundException::new); return articleRepository.findBySlug(slug).map(article -> article).orElseThrow(ResourceNotFoundException::new);
} }
private Map<String, Object> commentResponse(CommentData commentData) {
return new HashMap<String, Object>() {{
put("comment", commentData);
}};
}
} }
@Getter @Getter

View File

@ -2,6 +2,7 @@ package io.spring.api;
import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.annotation.JsonRootName;
import io.spring.application.user.UserQueryService; import io.spring.application.user.UserQueryService;
import io.spring.application.user.UserWithToken;
import io.spring.core.user.User; import io.spring.core.user.User;
import io.spring.core.user.UserRepository; import io.spring.core.user.UserRepository;
import lombok.Getter; import lombok.Getter;
@ -19,6 +20,8 @@ import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
@RestController @RestController
@RequestMapping(path = "/user") @RequestMapping(path = "/user")
@ -35,7 +38,7 @@ public class CurrentUserApi {
@GetMapping @GetMapping
public ResponseEntity currentUser(@AuthenticationPrincipal User currentUser, public ResponseEntity currentUser(@AuthenticationPrincipal User currentUser,
@RequestHeader(value = "Authorization") String authorization) { @RequestHeader(value = "Authorization") String authorization) {
return ResponseEntity.ok(userQueryService.fetchCurrentUser(currentUser.getUsername(), authorization.split(" ")[1])); return ResponseEntity.ok(userResponse(userQueryService.fetchCurrentUser(currentUser.getUsername(), authorization.split(" ")[1])));
} }
@PutMapping @PutMapping
@ -50,7 +53,13 @@ public class CurrentUserApi {
updateUserParam.getBio(), updateUserParam.getBio(),
updateUserParam.getImage()); updateUserParam.getImage());
userRepository.save(currentUser); userRepository.save(currentUser);
return ResponseEntity.ok(userQueryService.fetchCurrentUser(currentUser.getUsername(), authorization.split(" ")[1])); return ResponseEntity.ok(userResponse(userQueryService.fetchCurrentUser(currentUser.getUsername(), authorization.split(" ")[1])));
}
private Map<String, Object> userResponse(UserWithToken userWithToken) {
return new HashMap<String, Object>() {{
put("user", userWithToken);
}};
} }
} }

View File

@ -2,7 +2,9 @@ package io.spring.api;
import com.fasterxml.jackson.annotation.JsonRootName; import com.fasterxml.jackson.annotation.JsonRootName;
import io.spring.api.exception.InvalidRequestException; import io.spring.api.exception.InvalidRequestException;
import io.spring.application.user.UserData;
import io.spring.application.user.UserQueryService; import io.spring.application.user.UserQueryService;
import io.spring.application.user.UserWithToken;
import io.spring.core.user.EncryptService; import io.spring.core.user.EncryptService;
import io.spring.core.user.User; import io.spring.core.user.User;
import io.spring.core.user.UserRepository; import io.spring.core.user.UserRepository;
@ -20,6 +22,8 @@ import org.springframework.web.bind.annotation.RestController;
import javax.validation.Valid; import javax.validation.Valid;
import java.util.HashMap;
import java.util.Map;
import java.util.Optional; import java.util.Optional;
import static org.springframework.web.bind.annotation.RequestMethod.POST; import static org.springframework.web.bind.annotation.RequestMethod.POST;
@ -63,19 +67,25 @@ public class UsersApi {
"", "",
defaultImage); defaultImage);
userRepository.save(user); userRepository.save(user);
return ResponseEntity.status(201).body(userQueryService.fetchNewAuthenticatedUser(user.getUsername())); return ResponseEntity.status(201).body(userResponse(userQueryService.fetchNewAuthenticatedUser(user.getUsername())));
} }
@RequestMapping(path = "/users/login", method = POST) @RequestMapping(path = "/users/login", method = POST)
public ResponseEntity userLogin(@Valid @RequestBody LoginParam loginParam, BindingResult bindingResult) { public ResponseEntity userLogin(@Valid @RequestBody LoginParam loginParam, BindingResult bindingResult) {
Optional<User> optional = userRepository.findByEmail(loginParam.getEmail()); Optional<User> optional = userRepository.findByEmail(loginParam.getEmail());
if (optional.isPresent() && encryptService.check(loginParam.getPassword(), optional.get().getPassword())) { if (optional.isPresent() && encryptService.check(loginParam.getPassword(), optional.get().getPassword())) {
return ResponseEntity.ok(userQueryService.fetchNewAuthenticatedUser(optional.get().getUsername())); return ResponseEntity.ok(userResponse(userQueryService.fetchNewAuthenticatedUser(optional.get().getUsername())));
} else { } else {
bindingResult.rejectValue("password", "INVALID", "invalid email or password"); bindingResult.rejectValue("password", "INVALID", "invalid email or password");
throw new InvalidRequestException(bindingResult); throw new InvalidRequestException(bindingResult);
} }
} }
private Map<String, Object> userResponse(UserWithToken userWithToken) {
return new HashMap<String, Object>() {{
put("user", userWithToken);
}};
}
} }
@Getter @Getter

View File

@ -15,13 +15,14 @@ public class ErrorResourceSerializer extends JsonSerializer<ErrorResource> {
@Override @Override
public void serialize(ErrorResource value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException { public void serialize(ErrorResource value, JsonGenerator gen, SerializerProvider serializers) throws IOException, JsonProcessingException {
Map<String, List<String>> json = new HashMap<>(); Map<String, List<String>> json = new HashMap<>();
gen.writeStartObject();
gen.writeObjectFieldStart("errors");
for (FieldErrorResource fieldErrorResource : value.getFieldErrors()) { for (FieldErrorResource fieldErrorResource : value.getFieldErrors()) {
if (!json.containsKey(fieldErrorResource.getField())) { if (!json.containsKey(fieldErrorResource.getField())) {
json.put(fieldErrorResource.getField(), new ArrayList<String>()); json.put(fieldErrorResource.getField(), new ArrayList<String>());
} }
json.get(fieldErrorResource.getField()).add(fieldErrorResource.getMessage()); json.get(fieldErrorResource.getField()).add(fieldErrorResource.getMessage());
} }
gen.writeStartObject();
for (Map.Entry<String, List<String>> pair : json.entrySet()) { for (Map.Entry<String, List<String>> pair : json.entrySet()) {
gen.writeArrayFieldStart(pair.getKey()); gen.writeArrayFieldStart(pair.getKey());
pair.getValue().forEach(content -> { pair.getValue().forEach(content -> {
@ -34,5 +35,6 @@ public class ErrorResourceSerializer extends JsonSerializer<ErrorResource> {
gen.writeEndArray(); gen.writeEndArray();
} }
gen.writeEndObject(); gen.writeEndObject();
gen.writeEndObject();
} }
} }

View File

@ -1,10 +1,8 @@
package io.spring.api.exception; package io.spring.api.exception;
import com.fasterxml.jackson.annotation.JsonRootName;
import org.springframework.validation.Errors; import org.springframework.validation.Errors;
@SuppressWarnings("serial") @SuppressWarnings("serial")
@JsonRootName("errors")
public class InvalidRequestException extends RuntimeException { public class InvalidRequestException extends RuntimeException {
private final Errors errors; private final Errors errors;

View File

@ -13,7 +13,6 @@ import java.util.List;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@JsonRootName("article")
public class ArticleData { public class ArticleData {
private String id; private String id;
private String slug; private String slug;

View File

@ -1,7 +1,7 @@
package io.spring.application.comment; package io.spring.application.comment;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.annotation.JsonRootName;
import io.spring.application.profile.ProfileData; import io.spring.application.profile.ProfileData;
import lombok.AllArgsConstructor; import lombok.AllArgsConstructor;
import lombok.Data; import lombok.Data;
@ -11,10 +11,10 @@ import org.joda.time.DateTime;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@JsonRootName("comment")
public class CommentData { public class CommentData {
private String id; private String id;
private String body; private String body;
@JsonIgnore
private String articleId; private String articleId;
private DateTime createdAt; private DateTime createdAt;
private DateTime updatedAt; private DateTime updatedAt;

View File

@ -4,6 +4,8 @@ import io.spring.application.profile.UserRelationshipQueryService;
import io.spring.core.user.User; import io.spring.core.user.User;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional; import java.util.Optional;
@Service @Service
@ -28,4 +30,8 @@ public class CommentQueryService {
} }
return Optional.ofNullable(commentData); return Optional.ofNullable(commentData);
} }
public List<CommentData> findByArticleSlug(String slug, User user) {
return new ArrayList<>();
}
} }

View File

@ -9,7 +9,6 @@ import lombok.NoArgsConstructor;
@Data @Data
@NoArgsConstructor @NoArgsConstructor
@AllArgsConstructor @AllArgsConstructor
@JsonRootName("user")
public class UserData { public class UserData {
private String id; private String id;
private String email; private String email;

View File

@ -25,21 +25,3 @@ public class UserQueryService {
} }
} }
@JsonRootName("user")
@Getter
class UserWithToken {
private String email;
private String username;
private String bio;
private String image;
private String token;
public UserWithToken(UserData userData, String token) {
this.email = userData.getEmail();
this.username = userData.getUsername();
this.bio = userData.getBio();
this.image = userData.getImage();
this.token = token;
}
}

View File

@ -0,0 +1,21 @@
package io.spring.application.user;
import lombok.Getter;
@Getter
public class UserWithToken {
private String email;
private String username;
private String bio;
private String image;
private String token;
public UserWithToken(UserData userData, String token) {
this.email = userData.getEmail();
this.username = userData.getUsername();
this.bio = userData.getBio();
this.image = userData.getImage();
this.token = token;
}
}

View File

@ -1,5 +1,4 @@
spring.jackson.deserialization.UNWRAP_ROOT_VALUE=true spring.jackson.deserialization.UNWRAP_ROOT_VALUE=true
spring.jackson.serialization.WRAP_ROOT_VALUE=true
image.default=https://static.productionready.io/images/smiley-cyrus.jpg image.default=https://static.productionready.io/images/smiley-cyrus.jpg
jwt.secret=nRvyYC4soFxBdZ-F-5Nnzz5USXstR1YylsTd-mA0aKtI9HUlriGrtkf-TiuDapkLiUCogO3JOK7kwZisrHp6wA jwt.secret=nRvyYC4soFxBdZ-F-5Nnzz5USXstR1YylsTd-mA0aKtI9HUlriGrtkf-TiuDapkLiUCogO3JOK7kwZisrHp6wA
jwt.sessionTime=86400 jwt.sessionTime=86400

View File

@ -111,6 +111,7 @@ public class ArticlesApiTest extends TestWithCurrentUser {
.body(param) .body(param)
.when() .when()
.post("/articles") .post("/articles")
.prettyPeek()
.then() .then()
.statusCode(422) .statusCode(422)
.body("errors.body[0]", equalTo("can't be empty")); .body("errors.body[0]", equalTo("can't be empty"));

View File

@ -16,6 +16,7 @@ import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.mock.mockito.MockBean; import org.springframework.boot.test.mock.mockito.MockBean;
import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
import java.util.Optional; import java.util.Optional;
@ -47,6 +48,7 @@ public class CommentsApiTest extends TestWithCurrentUser {
private CommentQueryService commentQueryService; private CommentQueryService commentQueryService;
private Article article; private Article article;
private CommentData commentData;
@Before @Before
public void setUp() throws Exception { public void setUp() throws Exception {
@ -58,6 +60,13 @@ public class CommentsApiTest extends TestWithCurrentUser {
article = new Article("title", "desc", "body", new String[]{"test", "java"}, user.getId()); article = new Article("title", "desc", "body", new String[]{"test", "java"}, user.getId());
when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article)); when(articleRepository.findBySlug(eq(article.getSlug()))).thenReturn(Optional.of(article));
commentData = new CommentData(
"123",
"comment",
article.getId(),
new DateTime(),
new DateTime(),
new ProfileData(user.getId(), user.getUsername(), user.getBio(), user.getImage(), false));
} }
@Test @Test
@ -68,14 +77,6 @@ public class CommentsApiTest extends TestWithCurrentUser {
}}); }});
}}; }};
CommentData commentData = new CommentData(
"123",
"comment",
article.getId(),
new DateTime(),
new DateTime(),
new ProfileData(user.getId(), user.getUsername(), user.getBio(), user.getImage(), false));
when(commentQueryService.findById(anyString(), eq(user))).thenReturn(Optional.of(commentData)); when(commentQueryService.findById(anyString(), eq(user))).thenReturn(Optional.of(commentData));
given() given()
@ -108,4 +109,15 @@ public class CommentsApiTest extends TestWithCurrentUser {
.body("errors.body[0]", equalTo("can't be empty")); .body("errors.body[0]", equalTo("can't be empty"));
} }
@Test
public void should_get_comments_of_article_success() throws Exception {
when(commentQueryService.findByArticleSlug(anyString(), eq(null))).thenReturn(Arrays.asList(commentData));
RestAssured.when()
.get("/articles/{slug}/comments", article.getSlug())
.prettyPeek()
.then()
.statusCode(200)
.body("comments[0].id", equalTo(commentData.getId()));
}
} }