Skip to content

Commit

Permalink
add specification
Browse files Browse the repository at this point in the history
Signed-off-by: Kirill Mokevnin <[email protected]>
  • Loading branch information
mokevnin committed Oct 14, 2023
1 parent 11f5972 commit 3c93cb0
Show file tree
Hide file tree
Showing 12 changed files with 280 additions and 11 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package io.hexlet.blog.controller.api;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.PageRequest;
import org.springframework.http.HttpStatus;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import io.hexlet.blog.dto.PostCommentParamsDTO;
import io.hexlet.blog.model.PostComment;
import io.hexlet.blog.repository.PostCommentRepository;
import io.hexlet.blog.specification.PostCommentSpecification;

@RestController
@RequestMapping("/api")
public class PostsCommentsController {
@Autowired
private PostCommentRepository repository;

@Autowired
private PostCommentSpecification specBuilder;

@GetMapping("/posts_comments")
@ResponseStatus(HttpStatus.OK)
Page<PostComment> index(PostCommentParamsDTO params, @RequestParam(defaultValue = "1") int page) {
var spec = specBuilder.build(params);
var comments = repository.findAll(spec, PageRequest.of(page - 1, 10));

return comments;
}
}

11 changes: 6 additions & 5 deletions src/main/java/io/hexlet/blog/controller/api/PostsController.java
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
import org.springframework.web.bind.annotation.ResponseStatus;
import org.springframework.web.bind.annotation.RestController;

import io.hexlet.blog.service.PostService;
import io.hexlet.blog.dto.PostCreateDTO;
import io.hexlet.blog.dto.PostDTO;
import io.hexlet.blog.dto.PostUpdateDTO;
Expand All @@ -36,17 +37,17 @@ public class PostsController {
@Autowired
private UserUtils userUtils;

@Autowired
private PostService postService;

@GetMapping("/posts")
@ResponseStatus(HttpStatus.OK)
ResponseEntity<List<PostDTO>> index() {
var posts = repository.findAll();
var result = posts.stream()
.map(postMapper::map)
.toList();
var posts = postService.getAll();

return ResponseEntity.ok()
.header("X-Total-Count", String.valueOf(posts.size()))
.body(result);
.body(posts);
}

@PostMapping("/posts")
Expand Down
15 changes: 15 additions & 0 deletions src/main/java/io/hexlet/blog/dto/PostCommentParamsDTO.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package io.hexlet.blog.dto;

import java.util.Date;

import lombok.Getter;
import lombok.Setter;

@Setter
@Getter
public class PostCommentParamsDTO {
private String nameCont;
private Long authorId;
private Long postId;
private Date createdAtGt;
}
3 changes: 1 addition & 2 deletions src/main/java/io/hexlet/blog/model/Page.java
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package io.hexlet.blog.model;

import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;

@NoArgsConstructor
// @NoArgsConstructor
@Setter
@Getter
public class Page {
Expand Down
4 changes: 3 additions & 1 deletion src/main/java/io/hexlet/blog/model/Post.java
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
import org.springframework.data.annotation.LastModifiedDate;
import org.springframework.data.jpa.domain.support.AuditingEntityListener;

import com.fasterxml.jackson.annotation.JsonIgnore;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EntityListeners;
import jakarta.persistence.FetchType;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.ManyToOne;
Expand All @@ -37,6 +38,7 @@ public class Post implements BaseEntity {
@EqualsAndHashCode.Include
private Long id;

@JsonIgnore
@ManyToOne(optional = false)
// @NotNull
private User author;
Expand Down
6 changes: 4 additions & 2 deletions src/main/java/io/hexlet/blog/model/PostComment.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,11 @@

import java.util.Date;

import org.springframework.data.annotation.CreatedBy;
import org.springframework.data.annotation.CreatedDate;
import org.springframework.data.annotation.LastModifiedDate;

import com.fasterxml.jackson.annotation.JsonIgnore;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
Expand All @@ -32,7 +33,8 @@ public class PostComment implements BaseEntity {
@GeneratedValue(strategy = IDENTITY)
private Long id;

@CreatedBy
@JsonIgnore
@ManyToOne(optional = false)
private User author;

@NotNull
Expand Down
17 changes: 17 additions & 0 deletions src/main/java/io/hexlet/blog/repository/PostCommentRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package io.hexlet.blog.repository;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

import io.hexlet.blog.model.Post;
import io.hexlet.blog.model.PostComment;

@Repository
public interface PostCommentRepository extends JpaRepository<PostComment, Long>, JpaSpecificationExecutor<PostComment> {
// Page<Post> findAll(Specification<Post> spec, Pageable pageable);
}

3 changes: 2 additions & 1 deletion src/main/java/io/hexlet/blog/repository/PostRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,12 @@
import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;

import io.hexlet.blog.model.Post;

@Repository
public interface PostRepository extends JpaRepository<Post, Long> {
public interface PostRepository extends JpaRepository<Post, Long>, JpaSpecificationExecutor<Post> {
Optional<Post> findBySlug(String slug);
}
62 changes: 62 additions & 0 deletions src/main/java/io/hexlet/blog/service/PostService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
package io.hexlet.blog.service;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import io.hexlet.blog.dto.PostCreateDTO;
import io.hexlet.blog.dto.PostDTO;
import io.hexlet.blog.dto.PostUpdateDTO;
import io.hexlet.blog.exception.ResourceNotFoundException;
import io.hexlet.blog.mapper.PostMapper;
import io.hexlet.blog.repository.PostRepository;
import io.hexlet.blog.util.UserUtils;

@Service
public class PostService {
@Autowired
private PostRepository repository;

@Autowired
private PostMapper postMapper;

@Autowired
private UserUtils userUtils;

public List<PostDTO> getAll() {
var posts = repository.findAll();
var result = posts.stream()
.map(postMapper::map)
.toList();
return result;
}

PostDTO create(PostCreateDTO postData) {
var post = postMapper.map(postData);
post.setAuthor(userUtils.getCurrentUser());
repository.save(post);
var postDTO = postMapper.map(post);
return postDTO;
}

PostDTO findById(Long id) {
var post = repository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Not Found: " + id));
var postDTO = postMapper.map(post);
return postDTO;
}

PostDTO update(PostUpdateDTO postData, Long id) {
var post = repository.findById(id)
.orElseThrow(() -> new ResourceNotFoundException("Not Found"));
postMapper.update(postData, post);
repository.save(post);
var postDTO = postMapper.map(post);
return postDTO;
}

void delete(Long id) {
repository.deleteById(id);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package io.hexlet.blog.specification;

import java.util.Date;

import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Component;

import io.hexlet.blog.dto.PostCommentParamsDTO;
import io.hexlet.blog.model.PostComment;

/**
* PostCommentSpecification
*/
@Component
public class PostCommentSpecification {
public Specification<PostComment> build(PostCommentParamsDTO params) {
Specification<PostComment> spec = Specification.where(null);
return spec
.and(withPostId(params.getPostId()))
.and(withCreatedAtGt(params.getCreatedAtGt()));
}

private Specification<PostComment> withPostId(Long postId) {
return (root, query, cb) -> postId == null ? cb.conjunction() : cb.equal(root.get("post").get("id"), postId);
}

private Specification<PostComment> withCreatedAtGt(Date date) {
return (root, query, cb) -> date == null ? cb.conjunction() : cb.greaterThan(root.get("created_at"), date);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package io.hexlet.blog.controller.api;

import static net.javacrumbs.jsonunit.assertj.JsonAssertions.assertThatJson;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.jwt;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status;

import org.instancio.Instancio;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.autoconfigure.web.servlet.AutoConfigureMockMvc;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.JwtRequestPostProcessor;
import org.springframework.test.web.servlet.MockMvc;

import io.hexlet.blog.model.Post;
import io.hexlet.blog.repository.PostCommentRepository;
import io.hexlet.blog.repository.PostRepository;
import io.hexlet.blog.util.ModelGenerator;
import io.hexlet.blog.util.UserUtils;
import jakarta.transaction.Transactional;

@SpringBootTest
@Transactional
@AutoConfigureMockMvc
public class PostsCommentsControllerTest {

@Autowired
private MockMvc mockMvc;

@Autowired
private ModelGenerator modelGenerator;

@Autowired
private PostRepository postRepository;

@Autowired
private PostCommentRepository postCommentRepository;

@Autowired
private UserUtils userUtils;

private JwtRequestPostProcessor token;

private Post testPost;

@BeforeEach
public void setUp() {
token = jwt().jwt(builder -> builder.subject("[email protected]"));
testPost = Instancio.of(modelGenerator.getPostModel())
.create();
testPost.setAuthor(userUtils.getTestUser());
postRepository.save(testPost);

var testPost2 = Instancio.of(modelGenerator.getPostModel())
.create();
testPost2.setAuthor(userUtils.getTestUser());
postRepository.save(testPost2);

var testPostComment = Instancio.of(modelGenerator.getPostCommentModel()).create();
testPostComment.setPost(testPost);
testPostComment.setAuthor(userUtils.getTestUser());
postCommentRepository.save(testPostComment);

var testPostComment2 = Instancio.of(modelGenerator.getPostCommentModel()).create();
testPostComment2.setPost(testPost2);
testPostComment2.setAuthor(userUtils.getTestUser());
postCommentRepository.save(testPostComment2);
}

@Test
public void testIndex() throws Exception {
var result = mockMvc.perform(get("/api/posts_comments").with(token))
.andExpect(status().isOk())
.andReturn();
var body = result.getResponse().getContentAsString();
assertThatJson(body)
.node("content")
.isArray()
.hasSize(2);
}

@Test
public void testFilteredIndex() throws Exception {
var result = mockMvc.perform(get("/api/posts_comments?post_id=" + testPost.getId()).with(token))
.andExpect(status().isOk())
.andReturn();
var body = result.getResponse().getContentAsString();
assertThatJson(body)
.node("content")
.isArray()
.hasSize(1);
}
}

7 changes: 7 additions & 0 deletions src/test/java/io/hexlet/blog/util/ModelGenerator.java
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import org.springframework.stereotype.Component;

import io.hexlet.blog.model.Post;
import io.hexlet.blog.model.PostComment;
import io.hexlet.blog.model.User;
import jakarta.annotation.PostConstruct;
import lombok.Getter;
Expand All @@ -17,6 +18,7 @@
public class ModelGenerator {
private Model<Post> postModel;
private Model<User> userModel;
private Model<PostComment> postCommentModel;

@Autowired
private Faker faker;
Expand All @@ -29,6 +31,11 @@ private void init() {
.supply(Select.field(Post::getBody), () -> faker.gameOfThrones().quote())
.toModel();

postCommentModel = Instancio.of(PostComment.class)
.ignore(Select.field(PostComment::getId))
.supply(Select.field(Post::getBody), () -> faker.gameOfThrones().quote())
.toModel();

userModel = Instancio.of(User.class)
.ignore(Select.field(User::getId))
.ignore(Select.field(User::getPosts))
Expand Down

0 comments on commit 3c93cb0

Please sign in to comment.