diff --git a/simple_sns/src/main/java/com/example/sns/controller/PostController.java b/simple_sns/src/main/java/com/example/sns/controller/PostController.java index f198d942..088914df 100644 --- a/simple_sns/src/main/java/com/example/sns/controller/PostController.java +++ b/simple_sns/src/main/java/com/example/sns/controller/PostController.java @@ -54,4 +54,11 @@ public class PostController { public Response> my(Pageable pageable, Authentication authentication) { return Response.success(postService.my(authentication.getName(), pageable).map(PostResponse::fromPost)); } + + @PostMapping("/{postId}/likes") + public Response like(@PathVariable Integer postId, Authentication authentication) { + postService.like(postId, authentication.getName()); + return Response.success(); + } + } diff --git a/simple_sns/src/main/java/com/example/sns/exception/ErrorCode.java b/simple_sns/src/main/java/com/example/sns/exception/ErrorCode.java index 4cf55b8b..ef8b6749 100644 --- a/simple_sns/src/main/java/com/example/sns/exception/ErrorCode.java +++ b/simple_sns/src/main/java/com/example/sns/exception/ErrorCode.java @@ -14,7 +14,8 @@ public enum ErrorCode { INVALID_TOKEN(HttpStatus.UNAUTHORIZED, "Token is invalid."), POST_NOT_FOUND(HttpStatus.NOT_FOUND, "Post not founded."), INVALID_PERMISSION(HttpStatus.UNAUTHORIZED, "Permission is invalid."), - INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal server error.") + INTERNAL_SERVER_ERROR(HttpStatus.INTERNAL_SERVER_ERROR, "Internal server error."), + ALREADY_LIKED(HttpStatus.CONFLICT, "User already liked the post."), ; diff --git a/simple_sns/src/main/java/com/example/sns/model/entity/LikeEntity.java b/simple_sns/src/main/java/com/example/sns/model/entity/LikeEntity.java new file mode 100644 index 00000000..f8984785 --- /dev/null +++ b/simple_sns/src/main/java/com/example/sns/model/entity/LikeEntity.java @@ -0,0 +1,54 @@ +package com.example.sns.model.entity; + +import lombok.Getter; +import lombok.Setter; +import org.hibernate.annotations.SQLDelete; +import org.hibernate.annotations.Where; + +import javax.persistence.*; +import java.sql.Timestamp; +import java.time.Instant; + +@Entity +@Table(name = "likes") +@Getter +@Setter +@SQLDelete(sql = "UPDATE likes SET deleted_at = NOW() where id=?") +@Where(clause = "deleted_at IS NULL") +public class LikeEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Integer id; + + @ManyToOne + @JoinColumn(name = "user_id") + private UserEntity user; + + @ManyToOne + @JoinColumn(name = "post_id") + private PostEntity post; + + private Timestamp registeredAt; + private Timestamp updatedAt; + + @Column(name = "deleted_at") + private Timestamp deletedAt; + + @PrePersist + void registeredAt() { + this.registeredAt = Timestamp.from(Instant.now()); + } + + @PreUpdate + void updatedAt() { + this.updatedAt = Timestamp.from(Instant.now()); + } + + public static LikeEntity of(UserEntity userEntity, PostEntity postEntity) { + LikeEntity entity = new LikeEntity(); + entity.setUser(userEntity); + entity.setPost(postEntity); + return entity; + } +} diff --git a/simple_sns/src/main/java/com/example/sns/repository/LikeEntityRepository.java b/simple_sns/src/main/java/com/example/sns/repository/LikeEntityRepository.java new file mode 100644 index 00000000..17753d47 --- /dev/null +++ b/simple_sns/src/main/java/com/example/sns/repository/LikeEntityRepository.java @@ -0,0 +1,15 @@ +package com.example.sns.repository; + +import com.example.sns.model.entity.LikeEntity; +import com.example.sns.model.entity.PostEntity; +import com.example.sns.model.entity.UserEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface LikeEntityRepository extends JpaRepository { + + Optional findByUserAndPost(UserEntity user, PostEntity post); +} diff --git a/simple_sns/src/main/java/com/example/sns/service/PostService.java b/simple_sns/src/main/java/com/example/sns/service/PostService.java index 6aada008..4edbbbcc 100644 --- a/simple_sns/src/main/java/com/example/sns/service/PostService.java +++ b/simple_sns/src/main/java/com/example/sns/service/PostService.java @@ -2,8 +2,10 @@ package com.example.sns.service; import com.example.sns.exception.SnsApplicationException; import com.example.sns.model.Post; +import com.example.sns.model.entity.LikeEntity; import com.example.sns.model.entity.PostEntity; import com.example.sns.model.entity.UserEntity; +import com.example.sns.repository.LikeEntityRepository; import com.example.sns.repository.PostEntityRepository; import com.example.sns.repository.UserEntityRepository; import lombok.RequiredArgsConstructor; @@ -20,6 +22,7 @@ public class PostService { private final PostEntityRepository postEntityRepository; private final UserEntityRepository userEntityRepository; + private final LikeEntityRepository likeEntityRepository; @Transactional public void create(String title, String body, String username) { @@ -107,4 +110,35 @@ public class PostService { ); return postEntityRepository.findAllByUser(userEntity, pageable).map(Post::fromEntity); } + + @Transactional + public void like(Integer postId, String username) { + PostEntity postEntity = postEntityRepository.findById(postId) + .orElseThrow( + () -> new SnsApplicationException( + POST_NOT_FOUND, + String.format("%s not founded", postId) + ) + ); + + UserEntity userEntity = userEntityRepository.findByUsername(username) + .orElseThrow( + () -> new SnsApplicationException( + USER_NOT_FOUND, + String.format("%s not founded", username) + ) + ); + + // check liked + likeEntityRepository.findByUserAndPost(userEntity, postEntity) + .ifPresent(it -> { + throw new SnsApplicationException( + ALREADY_LIKED, + String.format("username %s already like post %d", username, postId) + ); + }); + + // save like + likeEntityRepository.save(LikeEntity.of(userEntity, postEntity)); + } } diff --git a/simple_sns/src/test/java/com/example/sns/controller/PostControllerTest.java b/simple_sns/src/test/java/com/example/sns/controller/PostControllerTest.java index ec6d0dac..c73a147c 100644 --- a/simple_sns/src/test/java/com/example/sns/controller/PostControllerTest.java +++ b/simple_sns/src/test/java/com/example/sns/controller/PostControllerTest.java @@ -226,4 +226,36 @@ public class PostControllerTest { ).andDo(print()) .andExpect(status().isUnauthorized()); } + + @Test + @WithMockUser + void 좋아요기능() throws Exception { + + mockMvc.perform(post("/api/v1/posts/1/likes") + .contentType(MediaType.APPLICATION_JSON) + ).andDo(print()) + .andExpect(status().isOk()); + } + + @Test + @WithAnonymousUser + void 좋아요버튼클릭시_로그인하지_않은경우() throws Exception { + + mockMvc.perform(post("/api/v1/posts/1/likes") + .contentType(MediaType.APPLICATION_JSON) + ).andDo(print()) + .andExpect(status().isUnauthorized()); + } + + @Test + @WithMockUser + void 좋아요버튼클릭시_게시글이_없는경우() throws Exception { + + doThrow(new SnsApplicationException(ErrorCode.POST_NOT_FOUND)).when(postService).like(any(), any()); + + mockMvc.perform(post("/api/v1/posts/1/likes") + .contentType(MediaType.APPLICATION_JSON) + ).andDo(print()) + .andExpect(status().isNotFound()); + } }