diff --git a/src/main/java/com/codeit/todo/common/exception/likes/LikesNotFoundException.java b/src/main/java/com/codeit/todo/common/exception/likes/LikesNotFoundException.java new file mode 100644 index 0000000..a97b8ef --- /dev/null +++ b/src/main/java/com/codeit/todo/common/exception/likes/LikesNotFoundException.java @@ -0,0 +1,14 @@ +package com.codeit.todo.common.exception.likes; + +import com.codeit.todo.common.exception.EntityNotFoundException; + +public class LikesNotFoundException extends EntityNotFoundException { + private static final String ENTITY_TYPE = "likes"; + + /** + * @param request 엔티티를 찾기 위해 요청한 값 + */ + public LikesNotFoundException(String request) { + super(request, ENTITY_TYPE); + } +} \ No newline at end of file diff --git a/src/main/java/com/codeit/todo/domain/Likes.java b/src/main/java/com/codeit/todo/domain/Likes.java index 1146652..84d7206 100644 --- a/src/main/java/com/codeit/todo/domain/Likes.java +++ b/src/main/java/com/codeit/todo/domain/Likes.java @@ -2,6 +2,7 @@ import jakarta.persistence.*; import lombok.AccessLevel; +import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; @@ -20,4 +21,18 @@ public class Likes { @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "complete_id", nullable = false) private Complete complete; + + @Builder + public Likes(int likesId, User user, Complete complete) { + this.likesId = likesId; + this.user = user; + this.complete = complete; + } + + public static Likes toEntity(User user, Complete complete) { + return Likes.builder() + .user(user) + .complete(complete) + .build(); + } } diff --git a/src/main/java/com/codeit/todo/repository/LikesRepository.java b/src/main/java/com/codeit/todo/repository/LikesRepository.java index 06c988c..6e179a8 100644 --- a/src/main/java/com/codeit/todo/repository/LikesRepository.java +++ b/src/main/java/com/codeit/todo/repository/LikesRepository.java @@ -3,6 +3,10 @@ import com.codeit.todo.domain.Likes; import org.springframework.data.jpa.repository.JpaRepository; +import java.util.Optional; + public interface LikesRepository extends JpaRepository { Boolean existsByUser_UserIdAndComplete_CompleteId(int userId, int completeId); + + Optional findByComplete_CompleteIdAndUser_UserId(int completeId, int userId); } diff --git a/src/main/java/com/codeit/todo/service/likes/LikesService.java b/src/main/java/com/codeit/todo/service/likes/LikesService.java new file mode 100644 index 0000000..a6eb7b1 --- /dev/null +++ b/src/main/java/com/codeit/todo/service/likes/LikesService.java @@ -0,0 +1,10 @@ +package com.codeit.todo.service.likes; + +import com.codeit.todo.web.dto.response.likes.CreateLikeResponse; +import com.codeit.todo.web.dto.response.likes.DeleteLikeResponse; + +public interface LikesService { + CreateLikeResponse createLikes(int userId, int completeId); + + DeleteLikeResponse deleteLikes(int userId, int completeId); +} diff --git a/src/main/java/com/codeit/todo/service/likes/impl/LikesServiceImpl.java b/src/main/java/com/codeit/todo/service/likes/impl/LikesServiceImpl.java new file mode 100644 index 0000000..c3fb8ea --- /dev/null +++ b/src/main/java/com/codeit/todo/service/likes/impl/LikesServiceImpl.java @@ -0,0 +1,69 @@ +package com.codeit.todo.service.likes.impl; + +import com.codeit.todo.common.exception.auth.AuthorizationDeniedException; +import com.codeit.todo.common.exception.complete.CompleteNotFoundException; +import com.codeit.todo.common.exception.likes.LikesNotFoundException; +import com.codeit.todo.common.exception.user.UserNotFoundException; +import com.codeit.todo.domain.Complete; +import com.codeit.todo.domain.Likes; +import com.codeit.todo.domain.User; +import com.codeit.todo.repository.CompleteRepository; +import com.codeit.todo.repository.LikesRepository; +import com.codeit.todo.repository.UserRepository; +import com.codeit.todo.service.likes.LikesService; +import com.codeit.todo.web.dto.response.likes.CreateLikeResponse; +import com.codeit.todo.web.dto.response.likes.DeleteLikeResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +@Transactional +public class LikesServiceImpl implements LikesService { + + private final UserRepository userRepository; + private final CompleteRepository completeRepository; + private final LikesRepository likesRepository; + + @Override + public CreateLikeResponse createLikes(int userId, int completeId) { + if (likesRepository.existsByUser_UserIdAndComplete_CompleteId(userId, completeId)) { + throw new AuthorizationDeniedException("이미 좋아요를 눌렀습니다."); + } + + Complete complete = getComplete(completeId); + + if (complete.getTodo().getGoal().getUser().getUserId() == userId) { + throw new AuthorizationDeniedException("자신의 게시글에 좋아요를 누를 수 없습니다."); + } + + User user = getUser(userId); + Likes likes = Likes.toEntity(user, complete); + Likes createdLikes = likesRepository.save(likes); + + return CreateLikeResponse.fromEntity(createdLikes); + } + + @Override + public DeleteLikeResponse deleteLikes(int userId, int completeId) { + Complete complete = getComplete(completeId); + User user = getUser(userId); + + Likes likes = likesRepository.findByComplete_CompleteIdAndUser_UserId(complete.getCompleteId(), user.getUserId()) + .orElseThrow(() -> new LikesNotFoundException("좋아요를 누른 기록이 없습니다.")); + likesRepository.delete(likes); + + return DeleteLikeResponse.fromEntity(complete); + } + + private Complete getComplete(int completeId) { + return completeRepository.findById(completeId) + .orElseThrow(() -> new CompleteNotFoundException(String.valueOf(completeId))); + } + + private User getUser(int userId) { + return userRepository.findById(userId) + .orElseThrow(() -> new UserNotFoundException(String.valueOf(userId), "User")); + } +} diff --git a/src/main/java/com/codeit/todo/web/controller/CompleteController.java b/src/main/java/com/codeit/todo/web/controller/CompleteController.java index 95be0dd..5bf66b7 100644 --- a/src/main/java/com/codeit/todo/web/controller/CompleteController.java +++ b/src/main/java/com/codeit/todo/web/controller/CompleteController.java @@ -5,7 +5,6 @@ import com.codeit.todo.web.dto.request.complete.UpdateCompleteRequest; import com.codeit.todo.web.dto.response.Response; import com.codeit.todo.web.dto.response.complete.ReadCompleteDetailResponse; -import com.codeit.todo.web.dto.response.complete.ReadCompleteResponse; import com.codeit.todo.web.dto.response.complete.UpdateCompleteResponse; import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.responses.ApiResponse; diff --git a/src/main/java/com/codeit/todo/web/controller/LikesController.java b/src/main/java/com/codeit/todo/web/controller/LikesController.java new file mode 100644 index 0000000..f75941c --- /dev/null +++ b/src/main/java/com/codeit/todo/web/controller/LikesController.java @@ -0,0 +1,36 @@ +package com.codeit.todo.web.controller; + +import com.codeit.todo.repository.CustomUserDetails; +import com.codeit.todo.service.likes.LikesService; +import com.codeit.todo.web.dto.response.Response; +import com.codeit.todo.web.dto.response.likes.CreateLikeResponse; +import com.codeit.todo.web.dto.response.likes.DeleteLikeResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.security.core.annotation.AuthenticationPrincipal; +import org.springframework.web.bind.annotation.*; + +@RestController +@RequiredArgsConstructor +@RequestMapping("/api/v1/completes/{completeId}/likes") +public class LikesController { + + private final LikesService likesService; + + @PostMapping + public Response createLikes( + @AuthenticationPrincipal CustomUserDetails userDetails, + @PathVariable int completeId + ) { + int userId = userDetails.getUserId(); + return Response.ok(likesService.createLikes(userId, completeId)); + } + + @DeleteMapping + public Response deleteLikes( + @AuthenticationPrincipal CustomUserDetails userDetails, + @PathVariable int completeId + ) { + int userId = userDetails.getUserId(); + return Response.ok(likesService.deleteLikes(userId, completeId)); + } +} diff --git a/src/main/java/com/codeit/todo/web/dto/response/likes/CreateLikeResponse.java b/src/main/java/com/codeit/todo/web/dto/response/likes/CreateLikeResponse.java new file mode 100644 index 0000000..f12e03a --- /dev/null +++ b/src/main/java/com/codeit/todo/web/dto/response/likes/CreateLikeResponse.java @@ -0,0 +1,15 @@ +package com.codeit.todo.web.dto.response.likes; + +import com.codeit.todo.domain.Likes; +import lombok.Builder; + +@Builder +public record CreateLikeResponse(int completeId, int likesId) { + + public static CreateLikeResponse fromEntity(Likes likes) { + return CreateLikeResponse.builder() + .completeId(likes.getComplete().getCompleteId()) + .likesId(likes.getLikesId()) + .build(); + } +} diff --git a/src/main/java/com/codeit/todo/web/dto/response/likes/DeleteLikeResponse.java b/src/main/java/com/codeit/todo/web/dto/response/likes/DeleteLikeResponse.java new file mode 100644 index 0000000..af56b38 --- /dev/null +++ b/src/main/java/com/codeit/todo/web/dto/response/likes/DeleteLikeResponse.java @@ -0,0 +1,14 @@ +package com.codeit.todo.web.dto.response.likes; + +import com.codeit.todo.domain.Complete; +import lombok.Builder; + +@Builder +public record DeleteLikeResponse(int completeId) { + + public static DeleteLikeResponse fromEntity(Complete complete) { + return DeleteLikeResponse.builder() + .completeId(complete.getCompleteId()) + .build(); + } +}