728x90
이전글
Like API
Like API를 개발하면서 삭제 방식을 Hard Delete에서 Soft Delete 방식으로 모두 전환하였습니다.
DeleteAt이라는 변수를 Base Entity에 추가하여 Post, Like, Alarm, Comment 엔티티들이 모두 Soft Delete 방식을 따르도록 변경하였습니다.
이제 삭제 메서드를 사용하였을 때, Delete 쿼리가 아닌 Update 쿼리가 생성됩니다.
Likes Entity
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@Entity
@SQLDelete(sql = "UPDATE likes SET deleted_at = current_timestamp WHERE id = ?")
public class Likes extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
@JoinColumn(name = "user_id")
@ManyToOne(fetch = LAZY)
private User user;
@JoinColumn(name = "post_id")
@ManyToOne(fetch = LAZY)
private Post post;
public static Likes toEntity(Post post, User user) {
return Likes.builder()
.user(user)
.post(post)
.build();
}
}
like는 MySQL의 예약어이므로 table 명을 like로 할 경우 에러가 발생합니다.
그래서 Entity의 이름을 likes로 변경하였습니다. 또는 @Table, @Entity에 name을 지정해서 Class 명과 동일하지 않은 Table명을 명명할 수 있습니다.
좋아요 누르기
- 앤드 포인트
POST/posts/{postId}/likes
- 리턴
{
"resultCode":"SUCCESS",
"result": "좋아요를 눌렀습니다."
}
테스트 코드
@WebMvcTest(LikesRestController.class)
@WithMockUser
class LikesRestControllerTest {
@Autowired
MockMvc mockMvc;
@Autowired
ObjectMapper objectMapper;
@MockBean
LikesService likeService;
private String token;
@Value("${jwt.secret}")
private String secretKey;
public final LocalDateTime time = LocalDateTime.now();
@BeforeEach()
public void getToken() {
long expireTimeMs = 1000 * 60 * 60;
token = JwtUtil.createJwt("chordpli", secretKey, System.currentTimeMillis() + expireTimeMs);
}
@Test
@DisplayName("좋아요 누르기 성공")
void success_like() throws Exception{
User user = UserFixture.get("chordpli", "1234");
Post post = PostFixture.get(user);
given(likeService.increaseLike(any(), any())).willReturn("좋아요를 눌렀습니다.");
String url = String.format("/api/v1/posts/%d/likes", post.getId());
mockMvc.perform(post(url)
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsBytes(1)))
.andExpect(status().isOk())
.andExpect(jsonPath("$.resultCode").exists())
.andExpect(jsonPath("$.resultCode").value("SUCCESS"))
.andExpect(jsonPath("$.result").exists())
.andExpect(jsonPath("$.result").value("좋아요를 눌렀습니다."))
.andDo(print());
}
@Test
@DisplayName("좋아요 누르기 실패 - 로그인하지 않은 경우")
void fail_like_no_login() throws Exception{
User user = UserFixture.get("chordpli", "1234");
Post post = PostFixture.get(user);
given(likeService.increaseLike(any(), any())).willThrow(new SNSAppException(NOT_LOGGED_IN, NOT_LOGGED_IN.getMessage()));
String url = String.format("/api/v1/posts/%d/likes", post.getId());
mockMvc.perform(post(url)
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsBytes(1)))
.andExpect(status().isConflict())
.andDo(print());
}
@Test
@DisplayName("좋아요 누르기 실패 - 해당 포스트가 없는 경우")
void fail_like_not_exist_post() throws Exception{
User user = UserFixture.get("chordpli", "1234");
Post post = PostFixture.get(user);
given(likeService.increaseLike(any(), any())).willThrow(new SNSAppException(POST_NOT_FOUND, POST_NOT_FOUND.getMessage()));
String url = String.format("/api/v1/posts/%d/likes", post.getId());
mockMvc.perform(post(url)
.with(csrf())
.contentType(MediaType.APPLICATION_JSON)
.content(objectMapper.writeValueAsBytes(1)))
.andExpect(status().isNotFound())
.andDo(print());
}
}
API 구현
Controller
@RequestMapping("/api/v1/posts")
@RestController
@RequiredArgsConstructor
@Slf4j
public class LikesRestController {
private final LikesService likeService;
/* 좋아요 Like */
@ApiOperation("좋아요 증가")
@PostMapping("/{postId}/likes")
public Response<String> IncreaseLike(@PathVariable Integer postId,
Authentication authentication) {
String userName = authentication.getName();
return Response.success(likeService.increaseLike(postId, userName));
}
}
- 좋아요를 누를 postId와 누른 요청자가 누구인지 확인하기 위한 Authentication을 받습니다.
- userName과 postId를 매개변수로 increaseLike 메서드를 사용하고 반환합니다.
Service
@Service
@RequiredArgsConstructor
public class LikesService {
...
@Transactional
public String increaseLike(Integer postId, String userName) {
Post post = service.validateGetPostById(postId);
User user = service.validateGetUserByUserName(userName);
Optional<Likes> optionalLike = likeRepository.findLikeByUserAndPost(user, post);
Likes like;
// 좋아요 기록이 있는지 확인합니다.
if (optionalLike.isPresent()) {
like = optionalLike.get();
} else {
// 좋아요 기록이 없다면 처음 좋아요를 누른 것이므로 좋아요 기록을 저장합니다.
like = likeRepository.save(Likes.toEntity(post, user));
// 좋아요 기록이 DB에 잘 저장되었다면, Alarm을 보냅니다.
return "좋아요를 눌렀습니다";
}
// 받아온 like 기록중 getDeletedAt의 정보를 확인합니다.
if (like.getDeletedAt() == null) {
// 이미 like를 한 적이 있는데 getDeletedAt이 NULL이라면 다시 한 번 버튼을 누른 것이므로 좋아요를 취소합니다.
likeRepository.delete(like);
// like를 soft delete 처리한 후 알람 기록도 삭제합니다.
return "좋아요를 취소했습니다.";
} else {
// 이미 like를 한 기록이 있는데 getDeletedAt이 있다면, 좋아요를 취소한 상태에서 다시 좋아요 버튼을 누른 상황입니다.
// deletedAt 기록을 삭제합니다.
like.cancelDeletion();
// 다시 알람을 보냅니다.
return "좋아요를 눌렀습니다";
}
}
}
- postId로 좋아요를 누를 Post 정보를 불러옵니다.
- userName으로 좋아요를 누른 User 정보를 불러옵니다.
- findLikeByUserAndPost 메서드를 사용하여 User와 Post를 기반으로 Like를 누른 적이 있는지 기록을 확인합니다.
- 좋아요 기록이 있다면 Like 엔티티에 해당 정보를 받아옵니다.
- 기록이 없다면 처음으로 좋아요를 누른 것이므로 좋아요를 기록하고, 문자열을 반환합니다.
- 받아온 Like 정보에서 deletedAt의 정보를 확인합니다.
- 좋아요 기록이 존재하지만, deletedAt이 null이라면 현재 좋아요가 눌러져 있는 상태이므로, 좋아요를 취소합니다.
- 좋아요 기록이 존재하지만, deletedAt의 내용이 존재한다면, 좋아요가 취소되어있는 상태이므로 다시 좋아요를 기록하고 문자열을 반환합니다.
- deleteAt을 다시 null로 만들어줍니다.
Repository
public interface LikesRepository extends JpaRepository<Likes, Integer> {
Optional<Likes> findLikeByUserAndPost(User user, Post post);
}
좋아요 개수 Count
API 구현
Controller
...
public class LikesRestController {
...
@ApiOperation("좋아요 개수 확인")
@GetMapping("/{postId}/likes")
public Response<Integer> getLikeCount(@PathVariable Integer postId) {
return Response.success(likeService.getLikeCount(postId));
}
}
- 좋아요 개수를 확인할 Post의 Id를 받아옵니다.
- postId를 매개변수로 getLikeCount 메서드를 사용하여 결괏값을 반환합니다.
Service
...
public class LikesService {
...
public int getLikeCount(Integer postId) {
Post post = service.validateGetPostById(postId);
return likeRepository.countLikesByPost(post);
}
}
- postId를 사용하여 Post 정보를 받아옵니다.
- post Entity를 넘겨서 countLikesByPost 메서드를 사용합니다.
Repository
public interface LikesRepository extends JpaRepository<Likes, Integer> {
@Query("select count(l) from Likes l where l.deletedAt is null and l.post = :post")
int countLikesByPost(@Param("post") Post post);
}
- JPQL을 사용하여 객체 쿼리를 직접 입력하였습니다.
- like에 있는 deletedAt이 null이면서 post 정보가 일치하는 데이터들의 숫자를 반환하는 쿼리입니다.
- 해당 메서드의 매개변수로 Post를 받습니다.
반응형
'프로젝트 > Archive' 카테고리의 다른 글
[08] Alarm Api 개발 (0) | 2023.01.06 |
---|---|
[06] Comment Api 개발 - 2 (0) | 2023.01.05 |
[06] Comment Api 개발 - 1 (1) | 2023.01.04 |
[05] 리팩토링 - 2 (0) | 2022.12.29 |
[05] 리팩토링 - 1 (0) | 2022.12.29 |