From 850cbc0235492dc445569857b51fc5639381bd89 Mon Sep 17 00:00:00 2001 From: HyeonSeong Date: Thu, 19 Feb 2026 16:22:29 +0900 Subject: [PATCH] =?UTF-8?q?feat=20:=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=83=9D=EC=84=B1=20=EB=B0=8F=20=EC=B7=A8=EC=86=8C=20API?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../prompt/controller/PromptController.java | 18 +++++++-- .../progbe/domain/prompt/dto/LikeStatus.java | 5 +++ .../domain/prompt/dto/PromptLikeResponse.java | 9 +++++ .../prompt/entity/PromptLikeEntity.java | 40 +++++++------------ .../repository/PromptLikeRepository.java | 14 +++++++ .../domain/prompt/service/PromptService.java | 26 ++++++++++++ 6 files changed, 82 insertions(+), 30 deletions(-) create mode 100644 src/main/java/com/progbe/domain/prompt/dto/LikeStatus.java create mode 100644 src/main/java/com/progbe/domain/prompt/dto/PromptLikeResponse.java create mode 100644 src/main/java/com/progbe/domain/prompt/repository/PromptLikeRepository.java diff --git a/src/main/java/com/progbe/domain/prompt/controller/PromptController.java b/src/main/java/com/progbe/domain/prompt/controller/PromptController.java index 55b54a1..7b72c59 100644 --- a/src/main/java/com/progbe/domain/prompt/controller/PromptController.java +++ b/src/main/java/com/progbe/domain/prompt/controller/PromptController.java @@ -1,9 +1,6 @@ package com.progbe.domain.prompt.controller; -import com.progbe.domain.prompt.dto.PromptCreateRequest; -import com.progbe.domain.prompt.dto.PromptListResponse; -import com.progbe.domain.prompt.dto.PromptResponse; -import com.progbe.domain.prompt.dto.PromptUpdateRequest; +import com.progbe.domain.prompt.dto.*; import com.progbe.domain.prompt.service.PromptService; import com.progbe.global.common.ApiResponse; import jakarta.validation.Valid; @@ -108,5 +105,18 @@ public ResponseEntity> deletePrompt( promptService.deletePrompt(promptId, userId); return ResponseEntity.ok(ApiResponse.success(null)); } + + /** + * 좋아요 생성 및 취소 기능 + * @param promptId + * @param userDetails + * @return + */ + @PostMapping("/{promptId}/like") + public ApiResponse likePrompt(@PathVariable Long promptId, @AuthenticationPrincipal UserDetails userDetails) { + Long userId = Long.parseLong(userDetails.getUsername()); + PromptLikeResponse likePrompt = promptService.likePrompt(promptId, userId); + return ApiResponse.success(likePrompt); + } } diff --git a/src/main/java/com/progbe/domain/prompt/dto/LikeStatus.java b/src/main/java/com/progbe/domain/prompt/dto/LikeStatus.java new file mode 100644 index 0000000..5aa9b97 --- /dev/null +++ b/src/main/java/com/progbe/domain/prompt/dto/LikeStatus.java @@ -0,0 +1,5 @@ +package com.progbe.domain.prompt.dto; + +public enum LikeStatus { + LIKE, UNLIKE +} diff --git a/src/main/java/com/progbe/domain/prompt/dto/PromptLikeResponse.java b/src/main/java/com/progbe/domain/prompt/dto/PromptLikeResponse.java new file mode 100644 index 0000000..3c5af5a --- /dev/null +++ b/src/main/java/com/progbe/domain/prompt/dto/PromptLikeResponse.java @@ -0,0 +1,9 @@ +package com.progbe.domain.prompt.dto; + +public record PromptLikeResponse( + String likeStatus +) { + public static PromptLikeResponse of(LikeStatus likeStatus) { + return new PromptLikeResponse(likeStatus.name()); + } +} diff --git a/src/main/java/com/progbe/domain/prompt/entity/PromptLikeEntity.java b/src/main/java/com/progbe/domain/prompt/entity/PromptLikeEntity.java index c8272cf..1e7bf57 100644 --- a/src/main/java/com/progbe/domain/prompt/entity/PromptLikeEntity.java +++ b/src/main/java/com/progbe/domain/prompt/entity/PromptLikeEntity.java @@ -1,33 +1,16 @@ package com.progbe.domain.prompt.entity; import com.progbe.domain.user.entity.UserEntity; +import com.progbe.global.common.BaseEntity; import jakarta.persistence.*; -import lombok.AccessLevel; -import lombok.Builder; -import lombok.Getter; -import lombok.NoArgsConstructor; -import org.springframework.data.annotation.CreatedDate; +import lombok.*; import org.springframework.data.jpa.domain.support.AuditingEntityListener; -import java.time.LocalDateTime; - @Entity -@Table( - name = "prompt_likes", - indexes = { - @Index(name = "idx_prompt_likes_prompt_id", columnList = "prompt_id") - }, - uniqueConstraints = { - @UniqueConstraint( - name = "pk_prompt_likes_user_prompt", - columnNames = {"user_id", "prompt_id"} - ) - } -) @Getter -@NoArgsConstructor(access = AccessLevel.PROTECTED) @EntityListeners(AuditingEntityListener.class) -public class PromptLikeEntity { +@Builder +public class PromptLikeEntity extends BaseEntity { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @@ -42,13 +25,18 @@ public class PromptLikeEntity { @JoinColumn(name = "prompt_id", nullable = false) private PromptEntity prompt; - @CreatedDate - @Column(name = "created_at", updatable = false) - private LocalDateTime createdAt; + public PromptLikeEntity() { } - @Builder - public PromptLikeEntity(UserEntity user, PromptEntity prompt) { + public PromptLikeEntity(Long id, UserEntity user, PromptEntity prompt) { + this.id = id; this.user = user; this.prompt = prompt; } + + public static PromptLikeEntity from(UserEntity user, PromptEntity prompt) { + return PromptLikeEntity.builder() + .user(user) + .prompt(prompt) + .build(); + } } \ No newline at end of file diff --git a/src/main/java/com/progbe/domain/prompt/repository/PromptLikeRepository.java b/src/main/java/com/progbe/domain/prompt/repository/PromptLikeRepository.java new file mode 100644 index 0000000..dc4fc3b --- /dev/null +++ b/src/main/java/com/progbe/domain/prompt/repository/PromptLikeRepository.java @@ -0,0 +1,14 @@ +package com.progbe.domain.prompt.repository; + +import com.progbe.domain.prompt.entity.PromptEntity; +import com.progbe.domain.prompt.entity.PromptLikeEntity; +import com.progbe.domain.user.entity.UserEntity; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.Optional; + +@Repository +public interface PromptLikeRepository extends JpaRepository { + Optional findByUserAndPrompt(UserEntity user, PromptEntity prompt); +} diff --git a/src/main/java/com/progbe/domain/prompt/service/PromptService.java b/src/main/java/com/progbe/domain/prompt/service/PromptService.java index f5bdfa0..9ab4c54 100644 --- a/src/main/java/com/progbe/domain/prompt/service/PromptService.java +++ b/src/main/java/com/progbe/domain/prompt/service/PromptService.java @@ -4,7 +4,9 @@ import com.progbe.domain.category.repository.CategoryRepository; import com.progbe.domain.prompt.dto.*; import com.progbe.domain.prompt.entity.PromptEntity; +import com.progbe.domain.prompt.entity.PromptLikeEntity; import com.progbe.domain.prompt.mapper.PromptMapper; +import com.progbe.domain.prompt.repository.PromptLikeRepository; import com.progbe.domain.prompt.repository.PromptRepository; import com.progbe.domain.user.entity.UserEntity; import com.progbe.domain.user.repository.UserRepository; @@ -17,6 +19,7 @@ import org.springframework.transaction.annotation.Transactional; import java.util.List; +import java.util.Optional; @Service @RequiredArgsConstructor @@ -25,6 +28,7 @@ public class PromptService { private final PromptRepository promptRepository; private final CategoryRepository categoryRepository; private final UserRepository userRepository; + private final PromptLikeRepository promptLikeRepository; private final PromptMapper promptMapper; /** @@ -107,4 +111,26 @@ public void deletePrompt(Long promptId, Long userId) { prompt.delete(); promptRepository.save(prompt); } + + // 좋아요 생성 (#32) + // 좋아요 눌렀는지를 확인하기 위해 null 값이 허용되는 Optional 전략 사용 + public PromptLikeResponse likePrompt(Long promptId, Long userId) { + UserEntity user = userRepository.findById(userId) + .orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND)); + + PromptEntity prompt = promptRepository.findById(promptId) + .orElseThrow(() -> new CustomException(ErrorCode.PROMPT_NOT_FOUND)); + + Optional existingLike = promptLikeRepository.findByUserAndPrompt(user, prompt); + + if (existingLike.isPresent()) { + promptLikeRepository.delete(existingLike.get()); + return PromptLikeResponse.of(LikeStatus.UNLIKE); + } + + PromptLikeEntity newLike = PromptLikeEntity.from(user, prompt); + + promptLikeRepository.save(newLike); + return PromptLikeResponse.of(LikeStatus.LIKE); + } }