add read one article

This commit is contained in:
aisensiy 2017-08-15 11:21:55 +08:00
parent 00f778c087
commit bb08238bed
8 changed files with 177 additions and 25 deletions

View 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());
}
}
}
}

View 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);
}
}

View File

@ -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 {
}

View File

@ -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);

View File

@ -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 {
articleData.setFavorited(articleFavoritesQueryService.isUserFavorite(user.getId(), id)); if (user != null) {
articleData.setFavoritesCount(articleFavoritesQueryService.articleFavoriteCount(id)); fillExtraInfo(id, user, articleData);
articleData.getProfileData().setFollowing( }
userRelationshipQueryService.isUserFollowing(
user.getId(),
articleData.getProfileData().getId()));
return Optional.of(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.setFavoritesCount(articleFavoritesQueryService.articleFavoriteCount(id));
articleData.getProfileData().setFollowing(
userRelationshipQueryService.isUserFollowing(
user.getId(),
articleData.getProfileData().getId()));
}
} }

View File

@ -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);
} }

View File

@ -1,27 +1,35 @@
<?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,
A.title articleTitle, A.title articleTitle,
A.description articleDescription, A.description articleDescription,
A.body articleBody, A.body articleBody,
A.created_at articleCreatedAt, A.created_at articleCreatedAt,
A.updated_at articleUpdatedAt, A.updated_at articleUpdatedAt,
T.name as tagName, T.name as tagName,
U.id userId, U.id userId,
U.username userUsername, U.username userUsername,
U.bio userBio, U.bio userBio,
U.image userImage U.image userImage
from from
articles A articles A
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"/>

View File

@ -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);
}
} }