Skip to content

Commit

Permalink
Merge pull request #26 from TG-WinG/feature/blog
Browse files Browse the repository at this point in the history
feat(blog): implement hashtag search feature
  • Loading branch information
wwingyou authored Oct 29, 2024
2 parents 4303aaa + 192840d commit c82549e
Show file tree
Hide file tree
Showing 8 changed files with 213 additions and 12 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package kr.tgwing.tech.blog.controller;

import java.security.Principal;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.web.PageableDefault;
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.RestController;

import lombok.AllArgsConstructor;

import kr.tgwing.tech.blog.dto.HashtagQuery;
import kr.tgwing.tech.blog.dto.HashtagView;
import kr.tgwing.tech.blog.service.HashtagService;

/**
* HashtagController
*/
@RestController
@RequestMapping("/hashtag")
@AllArgsConstructor
public class HashtagController {

private final HashtagService hashtagService;

@GetMapping("")
public Page<HashtagView> getHashtags(
@ModelAttribute HashtagQuery query,
@PageableDefault Pageable pageable,
Principal principal
) {
String studentNumber = null;
if (principal != null) {
studentNumber = principal.getName();
}
return hashtagService.getHashtags(query, studentNumber, pageable);
}

}
14 changes: 14 additions & 0 deletions src/main/java/kr/tgwing/tech/blog/dto/HashtagQuery.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package kr.tgwing.tech.blog.dto;

import lombok.Data;

/**
* HashtagQuery
*/
@Data
public class HashtagQuery {

private String keyword = "";
private boolean me = false;

}
25 changes: 25 additions & 0 deletions src/main/java/kr/tgwing/tech/blog/dto/HashtagView.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
package kr.tgwing.tech.blog.dto;

import lombok.Builder;
import lombok.Data;

import kr.tgwing.tech.blog.entity.Hashtag;

/**
* HashtagView
*/
@Data
@Builder
public class HashtagView {

private Long id;
private String name;

public static HashtagView of(Hashtag hashtag) {
return HashtagView.builder()
.id(hashtag.getId())
.name(hashtag.getName())
.build();
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
package kr.tgwing.tech.blog.entity;

import jakarta.persistence.criteria.Join;

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

import kr.tgwing.tech.user.entity.User;

/**
* HashtagSpecifications
*/
public class HashtagSpecifications {

public static Specification<Hashtag> hasNameLike(String keyword) {
return (root, query, cb) ->
cb.like(root.<String>get("name"), "%" + keyword + "%");
}

public static Specification<Hashtag> hasStudentWithStudentNumber(String studentNumber) {
return (root, query, cb) -> {
Join<Hashtag, Post> hashtagPost = root.join("post");
Join<Post, User> postHashtag = hashtagPost.join("writer");
return cb.equal(postHashtag.get("studentNumber"), studentNumber);
};
}
}

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package kr.tgwing.tech.blog.repository;

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

import kr.tgwing.tech.blog.entity.Hashtag;

/**
* HashtagRepository
*/
public interface HashtagRepository extends JpaRepository<Hashtag, Long>, JpaSpecificationExecutor<Hashtag> {

}
16 changes: 16 additions & 0 deletions src/main/java/kr/tgwing/tech/blog/service/HashtagService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package kr.tgwing.tech.blog.service;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;

import kr.tgwing.tech.blog.dto.HashtagQuery;
import kr.tgwing.tech.blog.dto.HashtagView;

/**
* HashtagService
*/
public interface HashtagService {

Page<HashtagView> getHashtags(HashtagQuery query, String userStudentNumber, Pageable pageable);

}
36 changes: 36 additions & 0 deletions src/main/java/kr/tgwing/tech/blog/service/HashtagServiceImpl.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package kr.tgwing.tech.blog.service;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.stereotype.Service;

import lombok.AllArgsConstructor;

import kr.tgwing.tech.blog.dto.HashtagQuery;
import kr.tgwing.tech.blog.dto.HashtagView;
import kr.tgwing.tech.blog.entity.Hashtag;
import kr.tgwing.tech.blog.entity.HashtagSpecifications;
import kr.tgwing.tech.blog.repository.HashtagRepository;

/**
* HashtagServiceImpl
*/
@Service
@AllArgsConstructor
public class HashtagServiceImpl implements HashtagService {

private final HashtagRepository hashtagRepository;

@Override
public Page<HashtagView> getHashtags(HashtagQuery query, String userStudentNumber, Pageable pageable) {
Specification<Hashtag> spec = HashtagSpecifications.hasNameLike(query.getKeyword());

if (query.isMe()) {
spec = spec.and(HashtagSpecifications.hasStudentWithStudentNumber(userStudentNumber));
}
Page<Hashtag> result = hashtagRepository.findAll(spec, pageable);
return result.map(HashtagView::of);
}

}
52 changes: 40 additions & 12 deletions src/test/java/kr/tgwing/tech/blog/BlogIntegrationTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.ResultActions;
import org.springframework.test.web.servlet.request.MockHttpServletRequestBuilder;

import kr.tgwing.tech.annotation.IntegrationTest;
import kr.tgwing.tech.blog.entity.Comment;
Expand Down Expand Up @@ -84,7 +86,7 @@ static void prepare_test_data(
.post(post2)
.build();
Hashtag tag3 = Hashtag.builder()
.name("tag2")
.name("tag3")
.post(post1)
.build();
Comment comment1 = Comment.builder()
Expand Down Expand Up @@ -149,23 +151,49 @@ void get_posts_has_hashtag() throws Exception {

@Test
void get_my_posts() throws Exception {
mvc.perform(post("/login")
.param("username", "2018000000")
.param("password", "12345678"))
performAsUser(get("/post?me=true"))
.andExpect(status().isOk())
.andExpect(header().exists("Authorization"))
.andDo((result) -> {
String token = result.getResponse().getHeader("Authorization");
mvc.perform(get("/post?me=true").header("Authorization", token))
.andExpect(status().isOk())
.andExpect(jsonPath("$.totalElements").value(1));
});
.andExpect(jsonPath("$.totalElements").value(1));
}

@Test
void throw_when_get_my_posts_but_not_logged_in() throws Exception {
mvc.perform(get("/post?me=true"))
.andExpect(status().isBadRequest());
}


@Test
void get_list_of_all_hashtags() throws Exception {
mvc.perform(get("/hashtag"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.totalElements").value(3));
}

@Test
void get_hashtags_containing_keword() throws Exception {
final String keyword = "1";
mvc.perform(get("/hashtag").queryParam("keyword", keyword))
.andExpect(status().isOk())
.andExpect(jsonPath("$.totalElements").value(1));
}

@Test
void get_hashtags_of_post_that_i_wrote() throws Exception {
performAsUser(get("/hashtag").queryParam("me", "true"))
.andExpect(status().isOk())
.andExpect(jsonPath("$.totalElements").value(2));
}

private ResultActions performAsUser(MockHttpServletRequestBuilder builder) throws Exception {
var result = mvc.perform(post("/login")
.param("username", "2018000000")
.param("password", "12345678"))
.andExpect(status().isOk())
.andExpect(header().exists("Authorization"))
.andReturn();

String token = result.getResponse().getHeader("Authorization");
return mvc.perform(builder.header("Authorization", token));
}

}

0 comments on commit c82549e

Please sign in to comment.