From 84b9643bc1542050e85162e768d48317651dda57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=ED=9D=AC?= Date: Wed, 26 Mar 2025 17:25:07 +0900 Subject: [PATCH 01/14] =?UTF-8?q?feat:=20=EC=A2=8B=EC=95=84=EC=9A=94=20API?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=B4=20Diary=20domain=20=EA=B4=80?= =?UTF-8?q?=EB=A0=A8=20=ED=95=84=EC=9A=94=ED=95=9C=20=ED=81=B4=EB=9E=98?= =?UTF-8?q?=EC=8A=A4,=20Entity=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../log4u/domain/diary/entity/Diary.java | 53 +++++++++++++++++++ .../diary/exception/DiaryException.java | 9 ++++ .../exception/NotFoundDiaryException.java | 4 ++ .../diary/repository/DiaryRepository.java | 9 ++++ .../domain/diary/service/DiaryService.java | 26 +++++++++ 5 files changed, 101 insertions(+) create mode 100644 src/main/java/com/example/log4u/domain/diary/entity/Diary.java create mode 100644 src/main/java/com/example/log4u/domain/diary/exception/DiaryException.java create mode 100644 src/main/java/com/example/log4u/domain/diary/exception/NotFoundDiaryException.java create mode 100644 src/main/java/com/example/log4u/domain/diary/repository/DiaryRepository.java create mode 100644 src/main/java/com/example/log4u/domain/diary/service/DiaryService.java diff --git a/src/main/java/com/example/log4u/domain/diary/entity/Diary.java b/src/main/java/com/example/log4u/domain/diary/entity/Diary.java new file mode 100644 index 00000000..860dc61e --- /dev/null +++ b/src/main/java/com/example/log4u/domain/diary/entity/Diary.java @@ -0,0 +1,53 @@ +package com.example.log4u.domain.diary.entity; + +import com.example.log4u.domain.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class Diary extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long diaryId; + + //JPA 연관관계 사용 X + // 외래키 방식을 사용 O + @Column(nullable = false) + private Long userId; + + @Column(nullable = false) + private String title; + + private String thumbnailUrl; + + @Column(nullable = false) + private String content; + + @Column(nullable = false) + private Double latitude; + + @Column(nullable = false) + private Double longitude; + + @Column(nullable = false) + private Long likeCount; + + public Long updateLikeCount() { + this.likeCount++; + return this.likeCount; + } +} diff --git a/src/main/java/com/example/log4u/domain/diary/exception/DiaryException.java b/src/main/java/com/example/log4u/domain/diary/exception/DiaryException.java new file mode 100644 index 00000000..c29af581 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/diary/exception/DiaryException.java @@ -0,0 +1,9 @@ +// package com.example.log4u.domain.diary.exception; +// +// public class DiaryException extends ServiceException{ +// +// DiaryException(ErrorCode errorCode){ +// super(errorCode); +// } +// +// } diff --git a/src/main/java/com/example/log4u/domain/diary/exception/NotFoundDiaryException.java b/src/main/java/com/example/log4u/domain/diary/exception/NotFoundDiaryException.java new file mode 100644 index 00000000..65c4ee65 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/diary/exception/NotFoundDiaryException.java @@ -0,0 +1,4 @@ +// package com.example.log4u.domain.diary.exception; +// +// public class NotFoundDiaryException extends DiaryException{ +// } diff --git a/src/main/java/com/example/log4u/domain/diary/repository/DiaryRepository.java b/src/main/java/com/example/log4u/domain/diary/repository/DiaryRepository.java new file mode 100644 index 00000000..fbd9d14b --- /dev/null +++ b/src/main/java/com/example/log4u/domain/diary/repository/DiaryRepository.java @@ -0,0 +1,9 @@ +package com.example.log4u.domain.diary.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.log4u.domain.diary.entity.Diary; + +public interface DiaryRepository extends JpaRepository +{ +} diff --git a/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java b/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java new file mode 100644 index 00000000..96270c05 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java @@ -0,0 +1,26 @@ +package com.example.log4u.domain.diary.service; + +import org.springframework.stereotype.Service; + +import com.example.log4u.domain.diary.entity.Diary; +import com.example.log4u.domain.diary.repository.DiaryRepository; + +import lombok.RequiredArgsConstructor; + + +@Service +@RequiredArgsConstructor +public class DiaryService { + + private final DiaryRepository diaryRepository; + + public Diary getDiary(Long diaryId) { + return diaryRepository.findById(diaryId) + .orElseThrow(IllegalArgumentException::new); + } + + public Long incrementLikeCount(Long diaryId) { + Diary diary = getDiary(diaryId); + return diary.updateLikeCount(); + } +} From a186c06d832b653e7a8d11a257596a6fe3085f68 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=ED=9D=AC?= Date: Wed, 26 Mar 2025 17:25:52 +0900 Subject: [PATCH 02/14] =?UTF-8?q?feat:=20=EC=A2=8B=EC=95=84=EC=9A=94=20API?= =?UTF-8?q?=EC=97=90=20=EB=8C=80=ED=95=B4=20=ED=95=84=EC=9A=94=ED=95=9C=20?= =?UTF-8?q?User=20domain=20=EA=B4=80=EB=A0=A8=20Entity=20=EC=B6=94?= =?UTF-8?q?=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../log4u/domain/user/entity/SocialType.java | 7 +++ .../log4u/domain/user/entity/User.java | 46 +++++++++++++++++++ 2 files changed, 53 insertions(+) create mode 100644 src/main/java/com/example/log4u/domain/user/entity/SocialType.java create mode 100644 src/main/java/com/example/log4u/domain/user/entity/User.java diff --git a/src/main/java/com/example/log4u/domain/user/entity/SocialType.java b/src/main/java/com/example/log4u/domain/user/entity/SocialType.java new file mode 100644 index 00000000..84f2002f --- /dev/null +++ b/src/main/java/com/example/log4u/domain/user/entity/SocialType.java @@ -0,0 +1,7 @@ +package com.example.log4u.domain.user.entity; + +public enum SocialType { + KAKAO, + GOOGLE, + NAVER, +} diff --git a/src/main/java/com/example/log4u/domain/user/entity/User.java b/src/main/java/com/example/log4u/domain/user/entity/User.java new file mode 100644 index 00000000..a70ffe07 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/user/entity/User.java @@ -0,0 +1,46 @@ +package com.example.log4u.domain.user.entity; + +import com.example.log4u.domain.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.EnumType; +import jakarta.persistence.Enumerated; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +public class User extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long userId; + + @Column(nullable = false) + private String nickname; + + @Column(nullable = false) + private Long providerId; + + @Column(nullable = false) + private String email; + + @Enumerated(EnumType.STRING) + @Column(nullable = false) + private SocialType socialType; + + private String statusMessage; + + @Column(nullable = false) + private boolean isPremium; +} From daebe25045a3fe509a82030aebbfc955af0a9246 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=ED=9D=AC?= Date: Wed, 26 Mar 2025 17:26:34 +0900 Subject: [PATCH 03/14] =?UTF-8?q?feat:=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../like/controller/LikeController.java | 32 ++++++++++++++++ .../like/dto/request/LikeAddRequestDto.java | 16 ++++++++ .../like/dto/response/LikeAddResponseDto.java | 10 +++++ .../log4u/domain/like/entity/Like.java | 34 +++++++++++++++++ .../like/repository/LikeRepository.java | 9 +++++ .../domain/like/service/LikeService.java | 37 +++++++++++++++++++ 6 files changed, 138 insertions(+) create mode 100644 src/main/java/com/example/log4u/domain/like/controller/LikeController.java create mode 100644 src/main/java/com/example/log4u/domain/like/dto/request/LikeAddRequestDto.java create mode 100644 src/main/java/com/example/log4u/domain/like/dto/response/LikeAddResponseDto.java create mode 100644 src/main/java/com/example/log4u/domain/like/entity/Like.java create mode 100644 src/main/java/com/example/log4u/domain/like/repository/LikeRepository.java create mode 100644 src/main/java/com/example/log4u/domain/like/service/LikeService.java diff --git a/src/main/java/com/example/log4u/domain/like/controller/LikeController.java b/src/main/java/com/example/log4u/domain/like/controller/LikeController.java new file mode 100644 index 00000000..7b5d7e3d --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/controller/LikeController.java @@ -0,0 +1,32 @@ +package com.example.log4u.domain.like.controller; + +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.PostMapping; +import org.springframework.web.bind.annotation.RequestBody; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import com.example.log4u.domain.like.dto.request.LikeAddRequestDto; +import com.example.log4u.domain.like.dto.response.LikeAddResponseDto; +import com.example.log4u.domain.like.service.LikeService; + +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.validation.Valid; +import lombok.RequiredArgsConstructor; + +@Tag(name = "좋아요 API") +@RestController +@RequiredArgsConstructor +@RequestMapping("/likes") +public class LikeController { + + private final LikeService likeService; + + @PostMapping + public ResponseEntity addLike(@Valid @RequestBody LikeAddRequestDto requestDto) { + Long userId = 1L; // 실제 구현에서는 토큰에서 추출 + + LikeAddResponseDto response = likeService.addLike(userId, requestDto); + return ResponseEntity.ok(response); + } +} diff --git a/src/main/java/com/example/log4u/domain/like/dto/request/LikeAddRequestDto.java b/src/main/java/com/example/log4u/domain/like/dto/request/LikeAddRequestDto.java new file mode 100644 index 00000000..e38ef6f6 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/dto/request/LikeAddRequestDto.java @@ -0,0 +1,16 @@ +package com.example.log4u.domain.like.dto.request; + +import com.example.log4u.domain.like.entity.Like; + +public record LikeAddRequestDto( + Long diaryId + +) { + + public Like toEntity(Long userId) { + return Like.builder() + .userId(userId) + .diaryId(diaryId) + .build(); + } +} diff --git a/src/main/java/com/example/log4u/domain/like/dto/response/LikeAddResponseDto.java b/src/main/java/com/example/log4u/domain/like/dto/response/LikeAddResponseDto.java new file mode 100644 index 00000000..6d040813 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/dto/response/LikeAddResponseDto.java @@ -0,0 +1,10 @@ +package com.example.log4u.domain.like.dto.response; + +public record LikeAddResponseDto( + boolean liked, + Long likeCount) { + + public static LikeAddResponseDto of(boolean liked, Long likeCount) { + return new LikeAddResponseDto(liked, likeCount); + } +} diff --git a/src/main/java/com/example/log4u/domain/like/entity/Like.java b/src/main/java/com/example/log4u/domain/like/entity/Like.java new file mode 100644 index 00000000..4b7940dc --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/entity/Like.java @@ -0,0 +1,34 @@ +package com.example.log4u.domain.like.entity; + +import com.example.log4u.domain.entity.BaseEntity; + +import jakarta.persistence.Column; +import jakarta.persistence.Entity; +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Table; +import lombok.AccessLevel; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Entity +@Getter +@NoArgsConstructor(access = AccessLevel.PROTECTED) +@AllArgsConstructor +@Builder +@Table(name = "likes") +public class Like extends BaseEntity { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long likeId; + + @Column(nullable = false) + private Long userId; + + @Column(nullable = false) + private Long diaryId; +} diff --git a/src/main/java/com/example/log4u/domain/like/repository/LikeRepository.java b/src/main/java/com/example/log4u/domain/like/repository/LikeRepository.java new file mode 100644 index 00000000..8b521de8 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/repository/LikeRepository.java @@ -0,0 +1,9 @@ +package com.example.log4u.domain.like.repository; + +import org.springframework.data.jpa.repository.JpaRepository; + +import com.example.log4u.domain.like.entity.Like; + +public interface LikeRepository extends JpaRepository { + boolean existsByUserIdAndDiaryId(Long userId, Long diaryId); +} diff --git a/src/main/java/com/example/log4u/domain/like/service/LikeService.java b/src/main/java/com/example/log4u/domain/like/service/LikeService.java new file mode 100644 index 00000000..4efe6d63 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/service/LikeService.java @@ -0,0 +1,37 @@ +package com.example.log4u.domain.like.service; + +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import com.example.log4u.domain.diary.service.DiaryService; +import com.example.log4u.domain.like.dto.request.LikeAddRequestDto; +import com.example.log4u.domain.like.dto.response.LikeAddResponseDto; +import com.example.log4u.domain.like.entity.Like; +import com.example.log4u.domain.like.repository.LikeRepository; + +import lombok.RequiredArgsConstructor; + +@Service +@RequiredArgsConstructor +public class LikeService { + + private final LikeRepository likeRepository; + private final DiaryService diaryService; + + @Transactional + public LikeAddResponseDto addLike(Long userId, LikeAddRequestDto requestDto) { + validateDuplicateLike(userId, requestDto.diaryId()); + + Like like = requestDto.toEntity(userId); + likeRepository.save(like); + + Long likeCount = diaryService.incrementLikeCount(requestDto.diaryId()); + return LikeAddResponseDto.of(true, likeCount); + } + + private void validateDuplicateLike(Long userId, Long diaryId) { + if (likeRepository.existsByUserIdAndDiaryId(userId, diaryId)) { + throw new IllegalArgumentException(); + } + } +} From d4e9d39bb7e19225dc56be4e1ff1f206fc2a1ed0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=ED=9D=AC?= Date: Wed, 26 Mar 2025 17:29:38 +0900 Subject: [PATCH 04/14] =?UTF-8?q?feat:=20BaseEntity=20=EC=B6=94=EA=B0=80?= =?UTF-8?q?=20=EB=B0=8F=20=EC=82=AC=EC=9A=A9=ED=95=98=EA=B8=B0=EC=9C=84?= =?UTF-8?q?=ED=95=9C=20@EnableJpaAuditing=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../com/example/log4u/Log4UApplication.java | 2 ++ .../domain/common/entity/BaseEntity.java | 28 +++++++++++++++++++ .../log4u/domain/diary/entity/Diary.java | 2 +- .../log4u/domain/like/entity/Like.java | 2 +- .../log4u/domain/user/entity/User.java | 2 +- 5 files changed, 33 insertions(+), 3 deletions(-) create mode 100644 src/main/java/com/example/log4u/domain/common/entity/BaseEntity.java diff --git a/src/main/java/com/example/log4u/Log4UApplication.java b/src/main/java/com/example/log4u/Log4UApplication.java index 57fa72f5..d54d143b 100644 --- a/src/main/java/com/example/log4u/Log4UApplication.java +++ b/src/main/java/com/example/log4u/Log4UApplication.java @@ -2,7 +2,9 @@ import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.data.jpa.repository.config.EnableJpaAuditing; +@EnableJpaAuditing @SpringBootApplication public class Log4UApplication { diff --git a/src/main/java/com/example/log4u/domain/common/entity/BaseEntity.java b/src/main/java/com/example/log4u/domain/common/entity/BaseEntity.java new file mode 100644 index 00000000..f07280c0 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/common/entity/BaseEntity.java @@ -0,0 +1,28 @@ +package com.example.log4u.domain.common.entity; + +import java.time.LocalDateTime; + +import org.springframework.data.annotation.CreatedDate; +import org.springframework.data.annotation.LastModifiedDate; +import org.springframework.data.jpa.domain.support.AuditingEntityListener; + +import jakarta.persistence.Column; +import jakarta.persistence.EntityListeners; +import jakarta.persistence.MappedSuperclass; +import lombok.Getter; + +@Getter +@MappedSuperclass +@EntityListeners(AuditingEntityListener.class) +abstract public class BaseEntity { + + @CreatedDate + @Column(nullable = false, updatable = false) + private LocalDateTime createdAt; + + @LastModifiedDate + @Column(nullable = false) + private LocalDateTime updatedAt; + + private String deleteYn = "N"; +} diff --git a/src/main/java/com/example/log4u/domain/diary/entity/Diary.java b/src/main/java/com/example/log4u/domain/diary/entity/Diary.java index 860dc61e..8a49cac1 100644 --- a/src/main/java/com/example/log4u/domain/diary/entity/Diary.java +++ b/src/main/java/com/example/log4u/domain/diary/entity/Diary.java @@ -1,6 +1,6 @@ package com.example.log4u.domain.diary.entity; -import com.example.log4u.domain.entity.BaseEntity; +import com.example.log4u.domain.common.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/com/example/log4u/domain/like/entity/Like.java b/src/main/java/com/example/log4u/domain/like/entity/Like.java index 4b7940dc..97e0d61e 100644 --- a/src/main/java/com/example/log4u/domain/like/entity/Like.java +++ b/src/main/java/com/example/log4u/domain/like/entity/Like.java @@ -1,6 +1,6 @@ package com.example.log4u.domain.like.entity; -import com.example.log4u.domain.entity.BaseEntity; +import com.example.log4u.domain.common.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/com/example/log4u/domain/user/entity/User.java b/src/main/java/com/example/log4u/domain/user/entity/User.java index a70ffe07..1a450962 100644 --- a/src/main/java/com/example/log4u/domain/user/entity/User.java +++ b/src/main/java/com/example/log4u/domain/user/entity/User.java @@ -1,6 +1,6 @@ package com.example.log4u.domain.user.entity; -import com.example.log4u.domain.entity.BaseEntity; +import com.example.log4u.domain.common.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; From 5a362f6c4674035437d3ff3eac95e745b79a8b3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=ED=9D=AC?= Date: Wed, 26 Mar 2025 17:30:25 +0900 Subject: [PATCH 05/14] =?UTF-8?q?test:=20=EB=8B=A8=EC=9C=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9C=84=ED=95=9C=20Domain=20=EB=B3=84=20?= =?UTF-8?q?Fixture=20=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/log4u/fixture/DiaryFixture.java | 19 ++++++++ .../example/log4u/fixture/LikeFixture.java | 14 ++++++ .../example/log4u/fixture/UserFixture.java | 44 +++++++++++++++++++ 3 files changed, 77 insertions(+) create mode 100644 src/test/java/com/example/log4u/fixture/DiaryFixture.java create mode 100644 src/test/java/com/example/log4u/fixture/LikeFixture.java create mode 100644 src/test/java/com/example/log4u/fixture/UserFixture.java diff --git a/src/test/java/com/example/log4u/fixture/DiaryFixture.java b/src/test/java/com/example/log4u/fixture/DiaryFixture.java new file mode 100644 index 00000000..c53dbbe7 --- /dev/null +++ b/src/test/java/com/example/log4u/fixture/DiaryFixture.java @@ -0,0 +1,19 @@ +package com.example.log4u.fixture; + +import com.example.log4u.domain.diary.entity.Diary; + +public class DiaryFixture { + + public static Diary createDiaryFixture() { + return Diary.builder() + .diaryId(1L) + .userId(1L) + .title("테스트 다이어리") + .thumbnailUrl("thumbnail.jpg") + .content("다이어리 내용입니다.") + .latitude(37.1234) + .longitude(127.5678) + .likeCount(11L) + .build(); + } +} diff --git a/src/test/java/com/example/log4u/fixture/LikeFixture.java b/src/test/java/com/example/log4u/fixture/LikeFixture.java new file mode 100644 index 00000000..b57481e8 --- /dev/null +++ b/src/test/java/com/example/log4u/fixture/LikeFixture.java @@ -0,0 +1,14 @@ +package com.example.log4u.fixture; + +import com.example.log4u.domain.like.entity.Like; + +public class LikeFixture { + + public static Like createLikeFixture(Long likeId, Long userId, Long diaryId) { + return Like.builder() + .likeId(likeId) + .userId(userId) + .diaryId(diaryId) + .build(); + } +} diff --git a/src/test/java/com/example/log4u/fixture/UserFixture.java b/src/test/java/com/example/log4u/fixture/UserFixture.java new file mode 100644 index 00000000..db87a26f --- /dev/null +++ b/src/test/java/com/example/log4u/fixture/UserFixture.java @@ -0,0 +1,44 @@ +package com.example.log4u.fixture; + +import com.example.log4u.domain.user.entity.SocialType; +import com.example.log4u.domain.user.entity.User; + +public class UserFixture { + + public static User createUserFixture() { + return User.builder() + .userId(1L) + .nickname("testUser") + .providerId(123L) + .email("test@example.com") + .socialType(SocialType.KAKAO) + .statusMessage("상태 메시지") + .isPremium(false) + .build(); + } + + public static User createUserFixture(Long userId) { + return User.builder() + .userId(userId) + .nickname("testUser" + userId) + .providerId(100L + userId) + .email("test" + userId + "@example.com") + .socialType(SocialType.KAKAO) + .statusMessage("상태 메시지 " + userId) + .isPremium(false) + .build(); + } + + public static User createPremiumUserFixture(Long userId) { + return User.builder() + .userId(userId) + .nickname("premiumUser" + userId) + .providerId(1000L + userId) + .email("premium" + userId + "@example.com") + .socialType(SocialType.KAKAO) + .statusMessage("프리미엄 사용자") + .isPremium(true) + .build(); + } +} + From e6bd210061b4a744c45e25b59676401b282a9837 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=ED=9D=AC?= Date: Wed, 26 Mar 2025 17:30:48 +0900 Subject: [PATCH 06/14] =?UTF-8?q?test:=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20API=20=EB=8B=A8=EC=9C=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/like/service/LikeServiceTest.java | 98 +++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 src/test/java/com/example/log4u/domain/like/service/LikeServiceTest.java diff --git a/src/test/java/com/example/log4u/domain/like/service/LikeServiceTest.java b/src/test/java/com/example/log4u/domain/like/service/LikeServiceTest.java new file mode 100644 index 00000000..1152cc17 --- /dev/null +++ b/src/test/java/com/example/log4u/domain/like/service/LikeServiceTest.java @@ -0,0 +1,98 @@ +package com.example.log4u.domain.like.service; + +import static org.assertj.core.api.Assertions.*; +import static org.junit.jupiter.api.Assertions.*; +import static org.mockito.BDDMockito.*; + +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.extension.ExtendWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.mockito.junit.jupiter.MockitoExtension; + +import com.example.log4u.domain.diary.entity.Diary; +import com.example.log4u.domain.diary.service.DiaryService; +import com.example.log4u.domain.like.dto.request.LikeAddRequestDto; +import com.example.log4u.domain.like.dto.response.LikeAddResponseDto; +import com.example.log4u.domain.like.entity.Like; +import com.example.log4u.domain.like.repository.LikeRepository; +import com.example.log4u.domain.user.entity.User; +import com.example.log4u.fixture.DiaryFixture; +import com.example.log4u.fixture.LikeFixture; +import com.example.log4u.fixture.UserFixture; + +@DisplayName("좋아요 API 단위 테스트") +@ExtendWith(MockitoExtension.class) +public class LikeServiceTest { + + @InjectMocks + private LikeService likeService; + + @Mock + private LikeRepository likeRepository; + + @Mock + private DiaryService diaryService; + + @Test + @DisplayName("성공 테스트: 사용자가 게시물에 좋아요를 누르면 좋아요가 저장된다") + void likeSuccess() { + // given + User user = UserFixture.createUserFixture(); + Diary diary = DiaryFixture.createDiaryFixture(); + LikeAddRequestDto requestDto = new LikeAddRequestDto(diary.getDiaryId()); + + Like like = LikeFixture.createLikeFixture(123243L, user.getUserId(), diary.getDiaryId()); + Long updatedLikeCount = 11L; + + given(likeRepository.existsByUserIdAndDiaryId(user.getUserId(), diary.getDiaryId())).willReturn(false); + given(likeRepository.save(any(Like.class))).willReturn(like); + given(diaryService.incrementLikeCount(diary.getDiaryId())).willReturn(updatedLikeCount); + + // when + LikeAddResponseDto response = likeService.addLike(user.getUserId(), requestDto); + + // then + verify(likeRepository).save(any(Like.class)); + assertThat(response.liked()).isTrue(); + assertThat(response.likeCount()).isEqualTo(updatedLikeCount); + } + + @Test + @DisplayName("예외 테스트: 존재하지 않는 다이어리에 좋아요 요청 시 예외가 발생한다") + void likeFail_whenDiaryNotFound() { + // given + Long userId = 1L; + Long diaryId = 100L; + LikeAddRequestDto requestDto = new LikeAddRequestDto(diaryId); + + given(likeRepository.existsByUserIdAndDiaryId(userId, diaryId)).willReturn(false); + given(diaryService.incrementLikeCount(diaryId)).willThrow(new IllegalArgumentException()); + + // when & then + assertThrows(IllegalArgumentException.class, () -> { + likeService.addLike(userId, requestDto); + }); + + verify(likeRepository).save(any(Like.class)); + } + + @Test + @DisplayName("예외 테스트: 사용자가 이미 좋아요를 누른 다이어리에 또 요청하면 예외가 발생한다") + void likeFail_whenAlreadyLiked() { + // given + Long userId = 1L; + Long diaryId = 100L; + LikeAddRequestDto requestDto = new LikeAddRequestDto(diaryId); + + given(likeRepository.existsByUserIdAndDiaryId(userId, diaryId)).willReturn(true); + + // when & then + assertThrows(IllegalArgumentException.class, () -> { + likeService.addLike(userId, requestDto); + }); + + verify(likeRepository, never()).save(any(Like.class)); + } +} From 5e68a39b3b6bb287b25e824a7b78aeae3a1d85f0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=ED=9D=AC?= Date: Wed, 26 Mar 2025 17:31:47 +0900 Subject: [PATCH 07/14] =?UTF-8?q?build:=20Security=20=EA=B4=80=EB=A0=A8=20?= =?UTF-8?q?=EC=84=A4=EC=A0=95=20=EC=9E=84=EC=8B=9C=20=EC=A3=BC=EC=84=9D=20?= =?UTF-8?q?=EC=B2=98=EB=A6=AC=20=EB=B0=8F=20mysql=20=EC=84=A4=EC=A0=95=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 구현 후 다시 변경 예정 --- build.gradle.kts | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/build.gradle.kts b/build.gradle.kts index 0ad7d420..db887474 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -28,13 +28,16 @@ repositories { dependencies { implementation("org.springframework.boot:spring-boot-starter-data-jpa") - implementation("org.springframework.boot:spring-boot-starter-oauth2-client") - implementation("org.springframework.boot:spring-boot-starter-security") +// implementation("org.springframework.boot:spring-boot-starter-oauth2-client") +// implementation("org.springframework.boot:spring-boot-starter-security") implementation("org.springframework.boot:spring-boot-starter-validation") implementation("org.springframework.boot:spring-boot-starter-web") implementation("org.springframework.boot:spring-boot-starter-data-redis") + implementation("mysql:mysql-connector-java:8.0.33") compileOnly("org.projectlombok:lombok") + testCompileOnly("org.projectlombok:lombok") + testAnnotationProcessor("org.projectlombok:lombok") runtimeOnly("com.h2database:h2") annotationProcessor("org.projectlombok:lombok") testImplementation("org.springframework.boot:spring-boot-starter-test") From 7ea9ed1730097fc0c0d8b7455e331b2645ea8e34 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=ED=9D=AC?= Date: Wed, 26 Mar 2025 17:43:40 +0900 Subject: [PATCH 08/14] =?UTF-8?q?rename:=20BaseEntity=20=ED=8F=B4=EB=8D=94?= =?UTF-8?q?=20=EC=9C=84=EC=B9=98=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../example/log4u/{domain => }/common/entity/BaseEntity.java | 2 +- src/main/java/com/example/log4u/domain/diary/entity/Diary.java | 2 +- src/main/java/com/example/log4u/domain/like/entity/Like.java | 2 +- src/main/java/com/example/log4u/domain/user/entity/User.java | 2 +- 4 files changed, 4 insertions(+), 4 deletions(-) rename src/main/java/com/example/log4u/{domain => }/common/entity/BaseEntity.java (93%) diff --git a/src/main/java/com/example/log4u/domain/common/entity/BaseEntity.java b/src/main/java/com/example/log4u/common/entity/BaseEntity.java similarity index 93% rename from src/main/java/com/example/log4u/domain/common/entity/BaseEntity.java rename to src/main/java/com/example/log4u/common/entity/BaseEntity.java index f07280c0..6f5adb62 100644 --- a/src/main/java/com/example/log4u/domain/common/entity/BaseEntity.java +++ b/src/main/java/com/example/log4u/common/entity/BaseEntity.java @@ -1,4 +1,4 @@ -package com.example.log4u.domain.common.entity; +package com.example.log4u.common.entity; import java.time.LocalDateTime; diff --git a/src/main/java/com/example/log4u/domain/diary/entity/Diary.java b/src/main/java/com/example/log4u/domain/diary/entity/Diary.java index 8a49cac1..a3cbadcb 100644 --- a/src/main/java/com/example/log4u/domain/diary/entity/Diary.java +++ b/src/main/java/com/example/log4u/domain/diary/entity/Diary.java @@ -1,6 +1,6 @@ package com.example.log4u.domain.diary.entity; -import com.example.log4u.domain.common.entity.BaseEntity; +import com.example.log4u.common.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/com/example/log4u/domain/like/entity/Like.java b/src/main/java/com/example/log4u/domain/like/entity/Like.java index 97e0d61e..6b449d4e 100644 --- a/src/main/java/com/example/log4u/domain/like/entity/Like.java +++ b/src/main/java/com/example/log4u/domain/like/entity/Like.java @@ -1,6 +1,6 @@ package com.example.log4u.domain.like.entity; -import com.example.log4u.domain.common.entity.BaseEntity; +import com.example.log4u.common.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; diff --git a/src/main/java/com/example/log4u/domain/user/entity/User.java b/src/main/java/com/example/log4u/domain/user/entity/User.java index 1a450962..b20e0d9d 100644 --- a/src/main/java/com/example/log4u/domain/user/entity/User.java +++ b/src/main/java/com/example/log4u/domain/user/entity/User.java @@ -1,6 +1,6 @@ package com.example.log4u.domain.user.entity; -import com.example.log4u.domain.common.entity.BaseEntity; +import com.example.log4u.common.entity.BaseEntity; import jakarta.persistence.Column; import jakarta.persistence.Entity; From 490b3770acaea404c8756128771e99bdc94f66bf Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=ED=9D=AC?= Date: Wed, 26 Mar 2025 17:44:26 +0900 Subject: [PATCH 09/14] =?UTF-8?q?refactor:=20=EC=98=88=EC=99=B8=EC=B2=98?= =?UTF-8?q?=EB=A6=AC=20=EB=B0=A9=EC=8B=9D=20=EA=B0=81=20Domain=EC=97=90=20?= =?UTF-8?q?=EC=95=8C=EB=A7=9E=EA=B2=8C=20=EC=88=98=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../diary/exception/DiaryErrorCode.java | 29 +++++++++++++++++ .../diary/exception/DiaryException.java | 20 ++++++------ .../exception/NotFoundDiaryException.java | 11 ++++--- .../domain/diary/service/DiaryService.java | 3 +- .../exception/DuplicateLikeException.java | 7 +++++ .../domain/like/exception/LikeErrorCode.java | 31 +++++++++++++++++++ .../domain/like/exception/LikeException.java | 10 ++++++ .../domain/like/service/LikeService.java | 3 +- 8 files changed, 99 insertions(+), 15 deletions(-) create mode 100644 src/main/java/com/example/log4u/domain/diary/exception/DiaryErrorCode.java create mode 100644 src/main/java/com/example/log4u/domain/like/exception/DuplicateLikeException.java create mode 100644 src/main/java/com/example/log4u/domain/like/exception/LikeErrorCode.java create mode 100644 src/main/java/com/example/log4u/domain/like/exception/LikeException.java diff --git a/src/main/java/com/example/log4u/domain/diary/exception/DiaryErrorCode.java b/src/main/java/com/example/log4u/domain/diary/exception/DiaryErrorCode.java new file mode 100644 index 00000000..def651be --- /dev/null +++ b/src/main/java/com/example/log4u/domain/diary/exception/DiaryErrorCode.java @@ -0,0 +1,29 @@ +package com.example.log4u.domain.diary.exception; + +import org.springframework.http.HttpStatus; + +import com.example.log4u.common.exception.base.ErrorCode; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum DiaryErrorCode implements ErrorCode { + + NOT_FOUND_DIARY(HttpStatus.NOT_FOUND, "다이어리를 찾을 수 없습니다."); + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String getErrorMessage() { + return message; + } +} + diff --git a/src/main/java/com/example/log4u/domain/diary/exception/DiaryException.java b/src/main/java/com/example/log4u/domain/diary/exception/DiaryException.java index c29af581..b8f325f8 100644 --- a/src/main/java/com/example/log4u/domain/diary/exception/DiaryException.java +++ b/src/main/java/com/example/log4u/domain/diary/exception/DiaryException.java @@ -1,9 +1,11 @@ -// package com.example.log4u.domain.diary.exception; -// -// public class DiaryException extends ServiceException{ -// -// DiaryException(ErrorCode errorCode){ -// super(errorCode); -// } -// -// } +package com.example.log4u.domain.diary.exception; + +import com.example.log4u.common.exception.base.ErrorCode; +import com.example.log4u.common.exception.base.ServiceException; + +public class DiaryException extends ServiceException { + public DiaryException(ErrorCode errorCode) { + super(errorCode); + } +} + diff --git a/src/main/java/com/example/log4u/domain/diary/exception/NotFoundDiaryException.java b/src/main/java/com/example/log4u/domain/diary/exception/NotFoundDiaryException.java index 65c4ee65..452f1141 100644 --- a/src/main/java/com/example/log4u/domain/diary/exception/NotFoundDiaryException.java +++ b/src/main/java/com/example/log4u/domain/diary/exception/NotFoundDiaryException.java @@ -1,4 +1,7 @@ -// package com.example.log4u.domain.diary.exception; -// -// public class NotFoundDiaryException extends DiaryException{ -// } +package com.example.log4u.domain.diary.exception; + +public class NotFoundDiaryException extends DiaryException { + public NotFoundDiaryException() { + super(DiaryErrorCode.NOT_FOUND_DIARY); + } +} diff --git a/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java b/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java index 96270c05..f3755e51 100644 --- a/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java +++ b/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java @@ -3,6 +3,7 @@ import org.springframework.stereotype.Service; import com.example.log4u.domain.diary.entity.Diary; +import com.example.log4u.domain.diary.exception.NotFoundDiaryException; import com.example.log4u.domain.diary.repository.DiaryRepository; import lombok.RequiredArgsConstructor; @@ -16,7 +17,7 @@ public class DiaryService { public Diary getDiary(Long diaryId) { return diaryRepository.findById(diaryId) - .orElseThrow(IllegalArgumentException::new); + .orElseThrow(NotFoundDiaryException::new); } public Long incrementLikeCount(Long diaryId) { diff --git a/src/main/java/com/example/log4u/domain/like/exception/DuplicateLikeException.java b/src/main/java/com/example/log4u/domain/like/exception/DuplicateLikeException.java new file mode 100644 index 00000000..46096185 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/exception/DuplicateLikeException.java @@ -0,0 +1,7 @@ +package com.example.log4u.domain.like.exception; + +public class DuplicateLikeException extends LikeException { + public DuplicateLikeException() { + super(LikeErrorCode.DUPLICATE_LIKE); + } +} diff --git a/src/main/java/com/example/log4u/domain/like/exception/LikeErrorCode.java b/src/main/java/com/example/log4u/domain/like/exception/LikeErrorCode.java new file mode 100644 index 00000000..bd11f6d6 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/exception/LikeErrorCode.java @@ -0,0 +1,31 @@ +package com.example.log4u.domain.like.exception; + +import org.springframework.http.HttpStatus; + +import com.example.log4u.common.exception.base.ErrorCode; + +import lombok.Getter; +import lombok.RequiredArgsConstructor; + +@Getter +@RequiredArgsConstructor +public enum LikeErrorCode implements ErrorCode { + + NOT_FOUND_LIKE(HttpStatus.NOT_FOUND, "좋아요 정보를 찾을 수 없습니다."), + DUPLICATE_LIKE(HttpStatus.BAD_REQUEST, "이미 좋아요를 눌렀습니다."); + + + private final HttpStatus httpStatus; + private final String message; + + @Override + public HttpStatus getHttpStatus() { + return httpStatus; + } + + @Override + public String getErrorMessage() { + return message; + } +} + diff --git a/src/main/java/com/example/log4u/domain/like/exception/LikeException.java b/src/main/java/com/example/log4u/domain/like/exception/LikeException.java new file mode 100644 index 00000000..ffca40df --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/exception/LikeException.java @@ -0,0 +1,10 @@ +package com.example.log4u.domain.like.exception; + +import com.example.log4u.common.exception.base.ErrorCode; +import com.example.log4u.common.exception.base.ServiceException; + +public class LikeException extends ServiceException { + public LikeException(ErrorCode errorCode) { + super(errorCode); + } +} diff --git a/src/main/java/com/example/log4u/domain/like/service/LikeService.java b/src/main/java/com/example/log4u/domain/like/service/LikeService.java index 4efe6d63..a48d7d2f 100644 --- a/src/main/java/com/example/log4u/domain/like/service/LikeService.java +++ b/src/main/java/com/example/log4u/domain/like/service/LikeService.java @@ -7,6 +7,7 @@ import com.example.log4u.domain.like.dto.request.LikeAddRequestDto; import com.example.log4u.domain.like.dto.response.LikeAddResponseDto; import com.example.log4u.domain.like.entity.Like; +import com.example.log4u.domain.like.exception.DuplicateLikeException; import com.example.log4u.domain.like.repository.LikeRepository; import lombok.RequiredArgsConstructor; @@ -31,7 +32,7 @@ public LikeAddResponseDto addLike(Long userId, LikeAddRequestDto requestDto) { private void validateDuplicateLike(Long userId, Long diaryId) { if (likeRepository.existsByUserIdAndDiaryId(userId, diaryId)) { - throw new IllegalArgumentException(); + throw new DuplicateLikeException(); } } } From 6163d8072e3e5d127fef0e95ebbd5d16263c05ca Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=ED=9D=AC?= Date: Wed, 26 Mar 2025 17:53:21 +0900 Subject: [PATCH 10/14] =?UTF-8?q?refactor:=20=ED=85=8C=EC=8A=A4=ED=8A=B8?= =?UTF-8?q?=20API=20=EC=9D=BC=EB=B6=80=20=EC=A0=9C=EA=B1=B0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../log4u/domain/comment/testController/TestController.java | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/main/java/com/example/log4u/domain/comment/testController/TestController.java b/src/main/java/com/example/log4u/domain/comment/testController/TestController.java index 932ff616..8967f3f3 100644 --- a/src/main/java/com/example/log4u/domain/comment/testController/TestController.java +++ b/src/main/java/com/example/log4u/domain/comment/testController/TestController.java @@ -31,10 +31,4 @@ public String testLog4uException() { throw new NotFoundCommentException(); // 또는 임의의 ServiceException } - @GetMapping("/unknown") - public String testUnexpectedException() { - String str = null; - str.length(); // NPE - return "절대 도달하지 않음"; - } } From fe0f0f8b6c726de273ea31bd94657c63060ebea6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=ED=9D=AC?= Date: Thu, 27 Mar 2025 00:53:13 +0900 Subject: [PATCH 11/14] =?UTF-8?q?test:=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=B6=94=EA=B0=80=20API=20=ED=85=8C=EC=8A=A4=ED=8A=B8=20?= =?UTF-8?q?=EC=BD=94=EB=93=9C=20=EC=84=A4=EB=AA=85,=20=EC=98=88=EC=99=B8?= =?UTF-8?q?=20=EC=B2=98=EB=A6=AC=20=EC=95=8C=EB=A7=9E=EA=B2=8C=20=EC=88=98?= =?UTF-8?q?=EC=A0=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/like/service/LikeServiceTest.java | 31 +++++++++++-------- 1 file changed, 18 insertions(+), 13 deletions(-) diff --git a/src/test/java/com/example/log4u/domain/like/service/LikeServiceTest.java b/src/test/java/com/example/log4u/domain/like/service/LikeServiceTest.java index 1152cc17..a79f6235 100644 --- a/src/test/java/com/example/log4u/domain/like/service/LikeServiceTest.java +++ b/src/test/java/com/example/log4u/domain/like/service/LikeServiceTest.java @@ -4,6 +4,8 @@ import static org.junit.jupiter.api.Assertions.*; import static org.mockito.BDDMockito.*; +import java.util.Optional; + import org.junit.jupiter.api.DisplayName; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; @@ -12,10 +14,13 @@ import org.mockito.junit.jupiter.MockitoExtension; import com.example.log4u.domain.diary.entity.Diary; +import com.example.log4u.domain.diary.exception.NotFoundDiaryException; import com.example.log4u.domain.diary.service.DiaryService; import com.example.log4u.domain.like.dto.request.LikeAddRequestDto; import com.example.log4u.domain.like.dto.response.LikeAddResponseDto; +import com.example.log4u.domain.like.dto.response.LikeCancelResponseDto; import com.example.log4u.domain.like.entity.Like; +import com.example.log4u.domain.like.exception.DuplicateLikeException; import com.example.log4u.domain.like.repository.LikeRepository; import com.example.log4u.domain.user.entity.User; import com.example.log4u.fixture.DiaryFixture; @@ -36,22 +41,22 @@ public class LikeServiceTest { private DiaryService diaryService; @Test - @DisplayName("성공 테스트: 사용자가 게시물에 좋아요를 누르면 좋아요가 저장된다") + @DisplayName("성공 테스트: 좋아요 추가 ") void likeSuccess() { // given - User user = UserFixture.createUserFixture(); - Diary diary = DiaryFixture.createDiaryFixture(); - LikeAddRequestDto requestDto = new LikeAddRequestDto(diary.getDiaryId()); + Long userId = 1L; + Long diaryId = 123L; + LikeAddRequestDto requestDto = new LikeAddRequestDto(diaryId); - Like like = LikeFixture.createLikeFixture(123243L, user.getUserId(), diary.getDiaryId()); + Like like = LikeFixture.createLikeFixture(123243L, userId, diaryId); Long updatedLikeCount = 11L; - given(likeRepository.existsByUserIdAndDiaryId(user.getUserId(), diary.getDiaryId())).willReturn(false); + given(likeRepository.existsByUserIdAndDiaryId(userId, diaryId)).willReturn(false); given(likeRepository.save(any(Like.class))).willReturn(like); - given(diaryService.incrementLikeCount(diary.getDiaryId())).willReturn(updatedLikeCount); + given(diaryService.incrementLikeCount(diaryId)).willReturn(updatedLikeCount); // when - LikeAddResponseDto response = likeService.addLike(user.getUserId(), requestDto); + LikeAddResponseDto response = likeService.addLike(userId, requestDto); // then verify(likeRepository).save(any(Like.class)); @@ -60,7 +65,7 @@ void likeSuccess() { } @Test - @DisplayName("예외 테스트: 존재하지 않는 다이어리에 좋아요 요청 시 예외가 발생한다") + @DisplayName("예외 테스트: 좋아요 추가 - 존재하지 않는 다이어리에 좋아요 요청") void likeFail_whenDiaryNotFound() { // given Long userId = 1L; @@ -68,10 +73,10 @@ void likeFail_whenDiaryNotFound() { LikeAddRequestDto requestDto = new LikeAddRequestDto(diaryId); given(likeRepository.existsByUserIdAndDiaryId(userId, diaryId)).willReturn(false); - given(diaryService.incrementLikeCount(diaryId)).willThrow(new IllegalArgumentException()); + given(diaryService.incrementLikeCount(diaryId)).willThrow(new NotFoundDiaryException()); // when & then - assertThrows(IllegalArgumentException.class, () -> { + assertThrows(NotFoundDiaryException.class, () -> { likeService.addLike(userId, requestDto); }); @@ -79,7 +84,7 @@ void likeFail_whenDiaryNotFound() { } @Test - @DisplayName("예외 테스트: 사용자가 이미 좋아요를 누른 다이어리에 또 요청하면 예외가 발생한다") + @DisplayName("예외 테스트: 좋아요 추가 - 이미 누른 좋아요 또 요청") void likeFail_whenAlreadyLiked() { // given Long userId = 1L; @@ -89,7 +94,7 @@ void likeFail_whenAlreadyLiked() { given(likeRepository.existsByUserIdAndDiaryId(userId, diaryId)).willReturn(true); // when & then - assertThrows(IllegalArgumentException.class, () -> { + assertThrows(DuplicateLikeException.class, () -> { likeService.addLike(userId, requestDto); }); From a97d94f772a90956a75c3d9cf9e080d14fdff995 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=ED=9D=AC?= Date: Thu, 27 Mar 2025 00:54:09 +0900 Subject: [PATCH 12/14] =?UTF-8?q?feat:=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=20API=20=EA=B5=AC=ED=98=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../log4u/domain/diary/entity/Diary.java | 7 ++++++- .../domain/diary/service/DiaryService.java | 12 +++++++++++- .../domain/like/controller/LikeController.java | 11 +++++++++++ .../dto/response/LikeCancelResponseDto.java | 10 ++++++++++ .../domain/like/repository/LikeRepository.java | 5 +++++ .../log4u/domain/like/service/LikeService.java | 17 +++++++++++++++++ 6 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 src/main/java/com/example/log4u/domain/like/dto/response/LikeCancelResponseDto.java diff --git a/src/main/java/com/example/log4u/domain/diary/entity/Diary.java b/src/main/java/com/example/log4u/domain/diary/entity/Diary.java index a3cbadcb..9f558d7f 100644 --- a/src/main/java/com/example/log4u/domain/diary/entity/Diary.java +++ b/src/main/java/com/example/log4u/domain/diary/entity/Diary.java @@ -46,8 +46,13 @@ public class Diary extends BaseEntity { @Column(nullable = false) private Long likeCount; - public Long updateLikeCount() { + public Long incrementLikeCount() { this.likeCount++; return this.likeCount; } + + public Long decreaseLikeCount() { + this.likeCount--; + return this.likeCount; + } } diff --git a/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java b/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java index f3755e51..48b95d34 100644 --- a/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java +++ b/src/main/java/com/example/log4u/domain/diary/service/DiaryService.java @@ -22,6 +22,16 @@ public Diary getDiary(Long diaryId) { public Long incrementLikeCount(Long diaryId) { Diary diary = getDiary(diaryId); - return diary.updateLikeCount(); + return diary.incrementLikeCount(); + } + + public Long decreaseLikeCount(Long diaryId) { + Diary diary = getDiary(diaryId); + return diary.decreaseLikeCount(); + } + + public Long getLikeCount(Long diaryId) { + Diary diary = getDiary(diaryId); + return diary.getLikeCount(); } } diff --git a/src/main/java/com/example/log4u/domain/like/controller/LikeController.java b/src/main/java/com/example/log4u/domain/like/controller/LikeController.java index 7b5d7e3d..e0922c73 100644 --- a/src/main/java/com/example/log4u/domain/like/controller/LikeController.java +++ b/src/main/java/com/example/log4u/domain/like/controller/LikeController.java @@ -1,6 +1,8 @@ package com.example.log4u.domain.like.controller; import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.DeleteMapping; +import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; @@ -8,6 +10,7 @@ import com.example.log4u.domain.like.dto.request.LikeAddRequestDto; import com.example.log4u.domain.like.dto.response.LikeAddResponseDto; +import com.example.log4u.domain.like.dto.response.LikeCancelResponseDto; import com.example.log4u.domain.like.service.LikeService; import io.swagger.v3.oas.annotations.tags.Tag; @@ -29,4 +32,12 @@ public ResponseEntity addLike(@Valid @RequestBody LikeAddReq LikeAddResponseDto response = likeService.addLike(userId, requestDto); return ResponseEntity.ok(response); } + + @DeleteMapping("/{diaryId}") + public ResponseEntity cancelLike(@PathVariable Long diaryId){ + Long userId = 1L;// 실제 구현에서는 토큰에서 추출 + + LikeCancelResponseDto response = likeService.cancelLike(userId, diaryId); + return ResponseEntity.ok(response); + } } diff --git a/src/main/java/com/example/log4u/domain/like/dto/response/LikeCancelResponseDto.java b/src/main/java/com/example/log4u/domain/like/dto/response/LikeCancelResponseDto.java new file mode 100644 index 00000000..5c778678 --- /dev/null +++ b/src/main/java/com/example/log4u/domain/like/dto/response/LikeCancelResponseDto.java @@ -0,0 +1,10 @@ +package com.example.log4u.domain.like.dto.response; + +public record LikeCancelResponseDto( + boolean liked, + Long likeCount) { + + public static LikeCancelResponseDto of(boolean liked, Long likeCount) { + return new LikeCancelResponseDto(liked, likeCount); + } +} diff --git a/src/main/java/com/example/log4u/domain/like/repository/LikeRepository.java b/src/main/java/com/example/log4u/domain/like/repository/LikeRepository.java index 8b521de8..01997377 100644 --- a/src/main/java/com/example/log4u/domain/like/repository/LikeRepository.java +++ b/src/main/java/com/example/log4u/domain/like/repository/LikeRepository.java @@ -1,9 +1,14 @@ package com.example.log4u.domain.like.repository; +import java.util.List; +import java.util.Optional; + import org.springframework.data.jpa.repository.JpaRepository; import com.example.log4u.domain.like.entity.Like; public interface LikeRepository extends JpaRepository { boolean existsByUserIdAndDiaryId(Long userId, Long diaryId); + + Optional findByUserIdAndDiaryId(Long userId, Long diaryId); } diff --git a/src/main/java/com/example/log4u/domain/like/service/LikeService.java b/src/main/java/com/example/log4u/domain/like/service/LikeService.java index a48d7d2f..4f368cb7 100644 --- a/src/main/java/com/example/log4u/domain/like/service/LikeService.java +++ b/src/main/java/com/example/log4u/domain/like/service/LikeService.java @@ -1,11 +1,14 @@ package com.example.log4u.domain.like.service; +import java.util.Optional; + import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.example.log4u.domain.diary.service.DiaryService; import com.example.log4u.domain.like.dto.request.LikeAddRequestDto; import com.example.log4u.domain.like.dto.response.LikeAddResponseDto; +import com.example.log4u.domain.like.dto.response.LikeCancelResponseDto; import com.example.log4u.domain.like.entity.Like; import com.example.log4u.domain.like.exception.DuplicateLikeException; import com.example.log4u.domain.like.repository.LikeRepository; @@ -30,6 +33,20 @@ public LikeAddResponseDto addLike(Long userId, LikeAddRequestDto requestDto) { return LikeAddResponseDto.of(true, likeCount); } + @Transactional + public LikeCancelResponseDto cancelLike(Long userId, Long diaryId) { + return likeRepository.findByUserIdAndDiaryId(userId, diaryId) + .map(like -> { + likeRepository.delete(like); + Long likeCount = diaryService.decreaseLikeCount(diaryId); + return LikeCancelResponseDto.of(false, likeCount); + }) + .orElseGet(() -> { + Long currentCount = diaryService.getLikeCount(diaryId); + return LikeCancelResponseDto.of(false, currentCount); + }); + } + private void validateDuplicateLike(Long userId, Long diaryId) { if (likeRepository.existsByUserIdAndDiaryId(userId, diaryId)) { throw new DuplicateLikeException(); From e237e94cc783c75621024dd851f84ba39238c9ae Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=ED=9D=AC?= Date: Thu, 27 Mar 2025 00:54:34 +0900 Subject: [PATCH 13/14] =?UTF-8?q?test:=20=EC=A2=8B=EC=95=84=EC=9A=94=20?= =?UTF-8?q?=EC=B7=A8=EC=86=8C=20API=20=EB=8B=A8=EC=9C=84=20=ED=85=8C?= =?UTF-8?q?=EC=8A=A4=ED=8A=B8=20=EC=9E=91=EC=84=B1?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../domain/like/service/LikeServiceTest.java | 46 +++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/test/java/com/example/log4u/domain/like/service/LikeServiceTest.java b/src/test/java/com/example/log4u/domain/like/service/LikeServiceTest.java index a79f6235..36c14b2c 100644 --- a/src/test/java/com/example/log4u/domain/like/service/LikeServiceTest.java +++ b/src/test/java/com/example/log4u/domain/like/service/LikeServiceTest.java @@ -100,4 +100,50 @@ void likeFail_whenAlreadyLiked() { verify(likeRepository, never()).save(any(Like.class)); } + + @DisplayName("성공 테스트: 좋아요 취소") + @Test + void cancelLike_Success() { + Long userId = 1L; + Long diaryId = 123L; + + Like like = LikeFixture.createLikeFixture(123243L, userId, diaryId); + Long updatedLikeCount = 11L; + + given(likeRepository.findByUserIdAndDiaryId(userId, diaryId)).willReturn(Optional.of(like)); + doNothing().when(likeRepository).delete(like); + given(diaryService.decreaseLikeCount(diaryId)).willReturn(updatedLikeCount); + + // when + LikeCancelResponseDto response = likeService.cancelLike(userId, diaryId); + + // then + verify(likeRepository).delete(like); + verify(diaryService).decreaseLikeCount(diaryId); + + assertThat(response.liked()).isFalse(); + assertThat(response.likeCount()).isEqualTo(updatedLikeCount); + } + + @DisplayName("성공 테스트: 좋아요 취소 - 존재하지 않는 좋아요에 대해 동일한 결과 반환)") + @Test + void cancelLike_NoLikeExists() { + // given + Long userId = 1L; + Long diaryId = 100L; + Long currentCount = 5L; + + given(likeRepository.findByUserIdAndDiaryId(userId, diaryId)).willReturn(Optional.empty()); + given(diaryService.getLikeCount(diaryId)).willReturn(currentCount); + + // when + LikeCancelResponseDto response = likeService.cancelLike(userId, diaryId); + + // then + verify(likeRepository, never()).delete(any()); + verify(diaryService).getLikeCount(diaryId); + + assertThat(response.liked()).isFalse(); + assertThat(response.likeCount()).isEqualTo(currentCount); + } } From e8620b279abb94b22fceac2b1bf3aa218926d481 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=EC=9D=B4=EA=B1=B4=ED=9D=AC?= Date: Thu, 27 Mar 2025 09:47:13 +0900 Subject: [PATCH 14/14] test: PR GPT TST --- .../log4u/domain/comment/testController/TestController.java | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main/java/com/example/log4u/domain/comment/testController/TestController.java b/src/main/java/com/example/log4u/domain/comment/testController/TestController.java index 8967f3f3..dfb57508 100644 --- a/src/main/java/com/example/log4u/domain/comment/testController/TestController.java +++ b/src/main/java/com/example/log4u/domain/comment/testController/TestController.java @@ -31,4 +31,5 @@ public String testLog4uException() { throw new NotFoundCommentException(); // 또는 임의의 ServiceException } + //test }