add read one article
This commit is contained in:
parent
00f778c087
commit
bb08238bed
44
src/main/java/io/spring/JacksonCustomizations.java
Normal file
44
src/main/java/io/spring/JacksonCustomizations.java
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
package io.spring;
|
||||||
|
|
||||||
|
import com.fasterxml.jackson.core.JsonGenerator;
|
||||||
|
import com.fasterxml.jackson.databind.Module;
|
||||||
|
import com.fasterxml.jackson.databind.SerializerProvider;
|
||||||
|
import com.fasterxml.jackson.databind.module.SimpleModule;
|
||||||
|
import com.fasterxml.jackson.databind.ser.std.StdSerializer;
|
||||||
|
import org.joda.time.DateTime;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class JacksonCustomizations {
|
||||||
|
|
||||||
|
@Bean
|
||||||
|
public Module realWorldModules() {
|
||||||
|
return new RealWorldModules();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class RealWorldModules extends SimpleModule {
|
||||||
|
public RealWorldModules() {
|
||||||
|
addSerializer(DateTime.class, new DateTimeSerializer());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public static class DateTimeSerializer extends StdSerializer<DateTime> {
|
||||||
|
|
||||||
|
protected DateTimeSerializer() {
|
||||||
|
super(DateTime.class);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void serialize(DateTime value, JsonGenerator gen, SerializerProvider provider) throws IOException {
|
||||||
|
if (value == null) {
|
||||||
|
gen.writeNull();
|
||||||
|
} else {
|
||||||
|
gen.writeString(value.toString());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
30
src/main/java/io/spring/api/ArticleApi.java
Normal file
30
src/main/java/io/spring/api/ArticleApi.java
Normal file
@ -0,0 +1,30 @@
|
|||||||
|
package io.spring.api;
|
||||||
|
|
||||||
|
import io.spring.api.exception.ResourceNotFoundException;
|
||||||
|
import io.spring.application.article.ArticleData;
|
||||||
|
import io.spring.application.article.ArticleQueryService;
|
||||||
|
import io.spring.core.user.User;
|
||||||
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
|
import org.springframework.http.ResponseEntity;
|
||||||
|
import org.springframework.security.core.annotation.AuthenticationPrincipal;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.PathVariable;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping(path = "/articles/{slug}")
|
||||||
|
public class ArticleApi {
|
||||||
|
private ArticleQueryService articleQueryService;
|
||||||
|
|
||||||
|
@Autowired
|
||||||
|
public ArticleApi(ArticleQueryService articleQueryService) {
|
||||||
|
this.articleQueryService = articleQueryService;
|
||||||
|
}
|
||||||
|
|
||||||
|
@GetMapping
|
||||||
|
public ResponseEntity<ArticleData> article(@PathVariable("slug") String slug,
|
||||||
|
@AuthenticationPrincipal User user) {
|
||||||
|
return articleQueryService.findBySlug(slug, user).map(ResponseEntity::ok).orElseThrow(ResourceNotFoundException::new);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,8 @@
|
|||||||
|
package io.spring.api.exception;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpStatus;
|
||||||
|
import org.springframework.web.bind.annotation.ResponseStatus;
|
||||||
|
|
||||||
|
@ResponseStatus(value = HttpStatus.NOT_FOUND)
|
||||||
|
public class ResourceNotFoundException extends RuntimeException {
|
||||||
|
}
|
@ -26,6 +26,7 @@ public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
|
|||||||
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS).and()
|
||||||
.authorizeRequests()
|
.authorizeRequests()
|
||||||
.antMatchers(HttpMethod.POST, "/users", "/users/login").permitAll()
|
.antMatchers(HttpMethod.POST, "/users", "/users/login").permitAll()
|
||||||
|
.antMatchers(HttpMethod.GET, "/articles/**").permitAll()
|
||||||
.anyRequest().authenticated();
|
.anyRequest().authenticated();
|
||||||
|
|
||||||
http.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
|
http.addFilterBefore(jwtTokenFilter(), UsernamePasswordAuthenticationFilter.class);
|
||||||
|
@ -23,17 +23,35 @@ public class ArticleQueryService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Optional<ArticleData> findById(String id, User user) {
|
public Optional<ArticleData> findById(String id, User user) {
|
||||||
ArticleData articleData = articleReadService.ofId(id);
|
ArticleData articleData = articleReadService.findById(id);
|
||||||
if (articleData == null) {
|
if (articleData == null) {
|
||||||
return Optional.empty();
|
return Optional.empty();
|
||||||
} else {
|
} 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void fillExtraInfo(String id, User user, ArticleData articleData) {
|
||||||
articleData.setFavorited(articleFavoritesQueryService.isUserFavorite(user.getId(), id));
|
articleData.setFavorited(articleFavoritesQueryService.isUserFavorite(user.getId(), id));
|
||||||
articleData.setFavoritesCount(articleFavoritesQueryService.articleFavoriteCount(id));
|
articleData.setFavoritesCount(articleFavoritesQueryService.articleFavoriteCount(id));
|
||||||
articleData.getProfileData().setFollowing(
|
articleData.getProfileData().setFollowing(
|
||||||
userRelationshipQueryService.isUserFollowing(
|
userRelationshipQueryService.isUserFollowing(
|
||||||
user.getId(),
|
user.getId(),
|
||||||
articleData.getProfileData().getId()));
|
articleData.getProfileData().getId()));
|
||||||
return Optional.of(articleData);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -7,5 +7,7 @@ import org.springframework.stereotype.Component;
|
|||||||
@Component
|
@Component
|
||||||
@Mapper
|
@Mapper
|
||||||
public interface ArticleReadService {
|
public interface ArticleReadService {
|
||||||
ArticleData ofId(@Param("id") String id);
|
ArticleData findById(@Param("id") String id);
|
||||||
|
|
||||||
|
ArticleData findBySlug(@Param("slug") String slug);
|
||||||
}
|
}
|
||||||
|
@ -1,7 +1,7 @@
|
|||||||
<?xml version="1.0" encoding="UTF-8" ?>
|
<?xml version="1.0" encoding="UTF-8" ?>
|
||||||
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
|
||||||
<mapper namespace="io.spring.application.article.ArticleReadService">
|
<mapper namespace="io.spring.application.article.ArticleReadService">
|
||||||
<select id="ofId" resultMap="articleData">
|
<sql id="selectArticleData">
|
||||||
select
|
select
|
||||||
A.id articleId,
|
A.id articleId,
|
||||||
A.slug articleSlug,
|
A.slug articleSlug,
|
||||||
@ -20,8 +20,16 @@
|
|||||||
left join article_tags AT on A.id = AT.article_id
|
left join article_tags AT on A.id = AT.article_id
|
||||||
left join tags T on T.id = AT.article_id
|
left join tags T on T.id = AT.article_id
|
||||||
left join users U on U.id = A.user_id
|
left join users U on U.id = A.user_id
|
||||||
|
</sql>
|
||||||
|
|
||||||
|
<select id="findById" resultMap="articleData">
|
||||||
|
<include refid="selectArticleData"/>
|
||||||
where A.id = #{id}
|
where A.id = #{id}
|
||||||
</select>
|
</select>
|
||||||
|
<select id="findBySlug" resultMap="articleData">
|
||||||
|
<include refid="selectArticleData"/>
|
||||||
|
where A.slug = #{slug}
|
||||||
|
</select>
|
||||||
|
|
||||||
<resultMap id="articleData" type="io.spring.application.article.ArticleData">
|
<resultMap id="articleData" type="io.spring.application.article.ArticleData">
|
||||||
<id column="articleId" property="id"/>
|
<id column="articleId" property="id"/>
|
||||||
|
@ -24,6 +24,7 @@ import static io.restassured.RestAssured.given;
|
|||||||
import static org.hamcrest.core.IsEqual.equalTo;
|
import static org.hamcrest.core.IsEqual.equalTo;
|
||||||
import static org.junit.Assert.*;
|
import static org.junit.Assert.*;
|
||||||
import static org.mockito.Matchers.any;
|
import static org.mockito.Matchers.any;
|
||||||
|
import static org.mockito.Matchers.anyString;
|
||||||
import static org.mockito.Matchers.eq;
|
import static org.mockito.Matchers.eq;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@ -131,4 +132,44 @@ public class ArticlesApiTest extends TestWithCurrentUser {
|
|||||||
}});
|
}});
|
||||||
}};
|
}};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void should_read_article_success() throws Exception {
|
||||||
|
String slug = "test-new-article";
|
||||||
|
Article article = new Article(slug, "Test New Article", "Desc", "Body", new String[]{"java", "spring", "jpg"}, user.getId());
|
||||||
|
|
||||||
|
DateTime time = new DateTime();
|
||||||
|
ArticleData articleData = new ArticleData(
|
||||||
|
article.getId(),
|
||||||
|
article.getSlug(),
|
||||||
|
article.getTitle(),
|
||||||
|
article.getDescription(),
|
||||||
|
article.getBody(),
|
||||||
|
false,
|
||||||
|
0,
|
||||||
|
time,
|
||||||
|
time,
|
||||||
|
Arrays.asList("joda"),
|
||||||
|
new ProfileData(user.getId(), user.getUsername(), user.getBio(), user.getImage(), false));
|
||||||
|
|
||||||
|
when(articleQueryService.findBySlug(eq(slug), eq(null))).thenReturn(Optional.of(articleData));
|
||||||
|
|
||||||
|
RestAssured.when()
|
||||||
|
.get("/articles/{slug}", slug)
|
||||||
|
.then()
|
||||||
|
.statusCode(200)
|
||||||
|
.body("article.slug", equalTo(slug))
|
||||||
|
.body("article.body", equalTo(articleData.getBody()))
|
||||||
|
.body("article.createdAt", equalTo(time.toDateTimeISO().toString()));
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void should_404_if_article_not_found() throws Exception {
|
||||||
|
when(articleQueryService.findBySlug(anyString(), any())).thenReturn(Optional.empty());
|
||||||
|
RestAssured.when()
|
||||||
|
.get("/articles/not-exists")
|
||||||
|
.then()
|
||||||
|
.statusCode(404);
|
||||||
|
}
|
||||||
}
|
}
|
Loading…
Reference in New Issue
Block a user