From f799568e7c646f9233bdc1f8559c984b92061f3c Mon Sep 17 00:00:00 2001 From: jjindol Date: Thu, 13 Mar 2025 21:08:43 +0900 Subject: [PATCH 1/2] =?UTF-8?q?feat:=20=EC=A0=84=EC=B2=B4=20=EB=8D=B0?= =?UTF-8?q?=EC=9D=B4=ED=84=B0=20&=20top5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../error/repository/ContentRepository.java | 6 ++ .../content/controller/ContentController.java | 32 +++++++++ .../haruhan/content/dto/ContentResDto.java | 12 ++++ .../content/entity/UserReceivedContent.java | 39 +++++++++++ .../UserReceivedContentRepository.java | 14 ++++ .../content/service/ContentService.java | 12 ++++ .../content/service/ContentServiceImpl.java | 68 +++++++++++++++++++ 7 files changed, 183 insertions(+) create mode 100644 src/main/java/com/haruhan/content/controller/ContentController.java create mode 100644 src/main/java/com/haruhan/content/dto/ContentResDto.java create mode 100644 src/main/java/com/haruhan/content/entity/UserReceivedContent.java create mode 100644 src/main/java/com/haruhan/content/repository/UserReceivedContentRepository.java create mode 100644 src/main/java/com/haruhan/content/service/ContentService.java create mode 100644 src/main/java/com/haruhan/content/service/ContentServiceImpl.java diff --git a/src/main/java/com/haruhan/common/error/repository/ContentRepository.java b/src/main/java/com/haruhan/common/error/repository/ContentRepository.java index ba575d8..c75905f 100644 --- a/src/main/java/com/haruhan/common/error/repository/ContentRepository.java +++ b/src/main/java/com/haruhan/common/error/repository/ContentRepository.java @@ -2,6 +2,12 @@ import com.haruhan.common.error.entity.Content; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.data.jpa.repository.Query; + +import java.util.List; public interface ContentRepository extends JpaRepository { + + @Query("SELECT c FROM Content c ORDER BY c.bookmark_count DESC LIMIT 1") + List findTop5ByBookmarkCount(); } diff --git a/src/main/java/com/haruhan/content/controller/ContentController.java b/src/main/java/com/haruhan/content/controller/ContentController.java new file mode 100644 index 0000000..661cf75 --- /dev/null +++ b/src/main/java/com/haruhan/content/controller/ContentController.java @@ -0,0 +1,32 @@ +package com.haruhan.content.controller; + +import com.haruhan.common.error.StatusCode; +import com.haruhan.common.error.dto.Message; +import com.haruhan.content.dto.ContentResDto; +import com.haruhan.content.service.ContentService; +import lombok.RequiredArgsConstructor; +import org.springframework.http.ResponseEntity; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequestMapping("/api/content") +@RequiredArgsConstructor +@CrossOrigin +public class ContentController { + + private final ContentService contentService; + + @GetMapping("/mine/{email}") + public ResponseEntity getUserReceivedContent(@PathVariable String email) { + List contentList = contentService.getUserReceivedContent(email); + return ResponseEntity.ok(new Message(StatusCode.OK, contentList)); + } + + @GetMapping("/top5") + public ResponseEntity getTop5BookmarkedContent() { + List top5Content = contentService.getTop5BookmarkedContent(); + return ResponseEntity.ok(new Message(StatusCode.OK, top5Content)); + } +} diff --git a/src/main/java/com/haruhan/content/dto/ContentResDto.java b/src/main/java/com/haruhan/content/dto/ContentResDto.java new file mode 100644 index 0000000..ff69d7a --- /dev/null +++ b/src/main/java/com/haruhan/content/dto/ContentResDto.java @@ -0,0 +1,12 @@ +package com.haruhan.content.dto; + +//사용자가 받은 컨텐츠 정보를 응답하는 DTO +public record ContentResDto( + Long id, + String title, + String summary, + String background, + String importance, + String tip, + String additionalResources +) {} \ No newline at end of file diff --git a/src/main/java/com/haruhan/content/entity/UserReceivedContent.java b/src/main/java/com/haruhan/content/entity/UserReceivedContent.java new file mode 100644 index 0000000..441e2e4 --- /dev/null +++ b/src/main/java/com/haruhan/content/entity/UserReceivedContent.java @@ -0,0 +1,39 @@ +package com.haruhan.content.entity; + +import com.haruhan.common.error.entity.Content; +import com.haruhan.user.entity.User; +import jakarta.persistence.*; +import lombok.Getter; +import lombok.NoArgsConstructor; + +import java.time.LocalDateTime; + +@Entity +@Getter +@NoArgsConstructor +@Table(name = "user_received_content") +public class UserReceivedContent { + + @Id + @GeneratedValue(strategy = GenerationType.IDENTITY) + private Long id; + + @ManyToOne + @JoinColumn(name = "user_id", nullable = false) + private User user; // 사용자를 참조 + + @ManyToOne + @JoinColumn(name = "content_id", nullable = false) + private Content content; // 받은 컨텐츠를 참조 + + @Column(name = "received_at", nullable = false) + private LocalDateTime receivedAt; // 받은 날짜 + + public UserReceivedContent(User user, Content content) { + this.user = user; + this.content = content; + this.receivedAt = LocalDateTime.now(); + } +} + +//id를 pk로 할지, 복합키 쓸지 고민중 \ No newline at end of file diff --git a/src/main/java/com/haruhan/content/repository/UserReceivedContentRepository.java b/src/main/java/com/haruhan/content/repository/UserReceivedContentRepository.java new file mode 100644 index 0000000..3f829ed --- /dev/null +++ b/src/main/java/com/haruhan/content/repository/UserReceivedContentRepository.java @@ -0,0 +1,14 @@ +package com.haruhan.content.repository; + +import com.haruhan.content.entity.UserReceivedContent; +import com.haruhan.user.entity.User; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface UserReceivedContentRepository extends JpaRepository { + + List findByUser(User user); +} \ No newline at end of file diff --git a/src/main/java/com/haruhan/content/service/ContentService.java b/src/main/java/com/haruhan/content/service/ContentService.java new file mode 100644 index 0000000..a890133 --- /dev/null +++ b/src/main/java/com/haruhan/content/service/ContentService.java @@ -0,0 +1,12 @@ +package com.haruhan.content.service; + +import com.haruhan.content.dto.ContentResDto; + +import java.util.List; + +public interface ContentService { + + List getUserReceivedContent(String email); + + List getTop5BookmarkedContent(); +} diff --git a/src/main/java/com/haruhan/content/service/ContentServiceImpl.java b/src/main/java/com/haruhan/content/service/ContentServiceImpl.java new file mode 100644 index 0000000..78683e6 --- /dev/null +++ b/src/main/java/com/haruhan/content/service/ContentServiceImpl.java @@ -0,0 +1,68 @@ +package com.haruhan.content.service; + +import com.haruhan.common.error.CustomException; +import com.haruhan.common.error.StatusCode; +import com.haruhan.common.error.entity.Content; +import com.haruhan.common.error.repository.ContentRepository; +import com.haruhan.content.dto.ContentResDto; +import com.haruhan.content.entity.UserReceivedContent; +import com.haruhan.content.repository.UserReceivedContentRepository; +import com.haruhan.user.entity.User; +import com.haruhan.user.repository.UserRepository; +import jakarta.transaction.Transactional; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; + +import java.util.List; +import java.util.stream.Collectors; + +@Service +@Transactional +@RequiredArgsConstructor +public class ContentServiceImpl implements ContentService { + + private final UserReceivedContentRepository userReceivedContentRepository; + private final UserRepository userRepository; + private final ContentRepository contentRepository; + + @Override + public List getUserReceivedContent(String email) { + + // 사용자가 존재하는지 확인 + User user = userRepository.findByEmail(email) + .orElseThrow(() -> new CustomException(StatusCode.NOT_FOUND_USER)); + + // 사용자가 받은 컨텐츠 조회 + List receivedContentList = userReceivedContentRepository.findByUser(user); + + // DTO 변환 후 반환 + return receivedContentList.stream() + .map(receivedContent -> new ContentResDto( + receivedContent.getContent().getContent_id(), + receivedContent.getContent().getTitle(), + receivedContent.getContent().getSummary(), + receivedContent.getContent().getBackground(), + receivedContent.getContent().getImportance(), + receivedContent.getContent().getTip(), + receivedContent.getContent().getAdditional_resources() + )) + .collect(Collectors.toList()); + } + + @Override + public List getTop5BookmarkedContent() { + List top5Content = contentRepository.findTop5ByBookmarkCount(); + + return top5Content.stream() + .map(content -> new ContentResDto( + content.getContent_id(), + content.getTitle(), + content.getSummary(), + content.getBackground(), + content.getImportance(), + content.getTip(), + content.getAdditional_resources() + )) + .collect(Collectors.toList()); + } +} \ No newline at end of file From 56881448ba83da119cb76b23eedaa0371b2661ac Mon Sep 17 00:00:00 2001 From: jjindol Date: Mon, 24 Mar 2025 14:27:38 +0900 Subject: [PATCH 2/2] =?UTF-8?q?fix:=20=EC=A7=88=EB=AC=B8=20=EB=AA=A8?= =?UTF-8?q?=EC=95=84=EB=B3=B4=EA=B8=B0=20=EB=A6=AC=ED=8C=A9=ED=86=A0?= =?UTF-8?q?=EB=A7=81,=20content=20=EC=A0=80=EC=9E=A5=20=ED=83=80=EC=9E=85?= =?UTF-8?q?=20=EB=B3=80=EA=B2=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Dockerfile | 6 +- .../error/repository/ContentRepository.java | 8 ++- .../haruhan/content/dto/ContentResDto.java | 10 +-- .../content/service/ContentService.java | 1 - .../content/service/ContentServiceImpl.java | 64 +++++++++++-------- .../java/com/haruhan/user/entity/User.java | 3 + src/main/resources/application.yml | 3 + 7 files changed, 59 insertions(+), 36 deletions(-) diff --git a/Dockerfile b/Dockerfile index dccde4c..2bfcc7b 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,4 +4,8 @@ ARG JAR_FILE=./build/libs/*-SNAPSHOT.jar COPY ${JAR_FILE} haruhan.jar -ENTRYPOINT ["java", "-jar", "/haruhan.jar"] \ No newline at end of file +# 시스템 시간대 설정 +ENV TZ=Asia/Seoul + +# JVM 시간대 설정 +ENTRYPOINT ["java", "-Duser.timezone=Asia/Seoul", "-jar", "/haruhan.jar"] \ No newline at end of file diff --git a/src/main/java/com/haruhan/common/error/repository/ContentRepository.java b/src/main/java/com/haruhan/common/error/repository/ContentRepository.java index c75905f..dc7b06e 100644 --- a/src/main/java/com/haruhan/common/error/repository/ContentRepository.java +++ b/src/main/java/com/haruhan/common/error/repository/ContentRepository.java @@ -3,11 +3,15 @@ import com.haruhan.common.error.entity.Content; import org.springframework.data.jpa.repository.JpaRepository; import org.springframework.data.jpa.repository.Query; +import org.springframework.data.repository.query.Param; import java.util.List; public interface ContentRepository extends JpaRepository { - @Query("SELECT c FROM Content c ORDER BY c.bookmark_count DESC LIMIT 1") + @Query("SELECT c FROM Content c ORDER BY c.bookmark_count DESC LIMIT 3") List findTop5ByBookmarkCount(); -} + + @Query("SELECT c FROM Content c WHERE c.content_id <= :end ORDER BY c.content_id ASC") + List findUpToLastContent(@Param("end") Long lastContentId); +} \ No newline at end of file diff --git a/src/main/java/com/haruhan/content/dto/ContentResDto.java b/src/main/java/com/haruhan/content/dto/ContentResDto.java index ff69d7a..f19dc67 100644 --- a/src/main/java/com/haruhan/content/dto/ContentResDto.java +++ b/src/main/java/com/haruhan/content/dto/ContentResDto.java @@ -1,12 +1,14 @@ package com.haruhan.content.dto; +import java.util.List; + //사용자가 받은 컨텐츠 정보를 응답하는 DTO public record ContentResDto( Long id, String title, String summary, - String background, - String importance, - String tip, - String additionalResources + List background, + List importance, + List tip, + List additionalResources ) {} \ No newline at end of file diff --git a/src/main/java/com/haruhan/content/service/ContentService.java b/src/main/java/com/haruhan/content/service/ContentService.java index a890133..eb732b9 100644 --- a/src/main/java/com/haruhan/content/service/ContentService.java +++ b/src/main/java/com/haruhan/content/service/ContentService.java @@ -7,6 +7,5 @@ public interface ContentService { List getUserReceivedContent(String email); - List getTop5BookmarkedContent(); } diff --git a/src/main/java/com/haruhan/content/service/ContentServiceImpl.java b/src/main/java/com/haruhan/content/service/ContentServiceImpl.java index 78683e6..c67d387 100644 --- a/src/main/java/com/haruhan/content/service/ContentServiceImpl.java +++ b/src/main/java/com/haruhan/content/service/ContentServiceImpl.java @@ -5,14 +5,13 @@ import com.haruhan.common.error.entity.Content; import com.haruhan.common.error.repository.ContentRepository; import com.haruhan.content.dto.ContentResDto; -import com.haruhan.content.entity.UserReceivedContent; -import com.haruhan.content.repository.UserReceivedContentRepository; import com.haruhan.user.entity.User; import com.haruhan.user.repository.UserRepository; import jakarta.transaction.Transactional; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import java.util.Arrays; import java.util.List; import java.util.stream.Collectors; @@ -21,48 +20,57 @@ @RequiredArgsConstructor public class ContentServiceImpl implements ContentService { - private final UserReceivedContentRepository userReceivedContentRepository; private final UserRepository userRepository; private final ContentRepository contentRepository; @Override public List getUserReceivedContent(String email) { - - // 사용자가 존재하는지 확인 User user = userRepository.findByEmail(email) .orElseThrow(() -> new CustomException(StatusCode.NOT_FOUND_USER)); - // 사용자가 받은 컨텐츠 조회 - List receivedContentList = userReceivedContentRepository.findByUser(user); - - // DTO 변환 후 반환 - return receivedContentList.stream() - .map(receivedContent -> new ContentResDto( - receivedContent.getContent().getContent_id(), - receivedContent.getContent().getTitle(), - receivedContent.getContent().getSummary(), - receivedContent.getContent().getBackground(), - receivedContent.getContent().getImportance(), - receivedContent.getContent().getTip(), - receivedContent.getContent().getAdditional_resources() - )) + Long lastContentId = user.getLastReceivedContentId(); + + if (lastContentId == null || lastContentId < 1) { + throw new CustomException(StatusCode.NOT_FOUND); + } + + List contentList = contentRepository.findUpToLastContent(lastContentId); + + return contentList.stream() + .map(this::convertToDto) .collect(Collectors.toList()); } + @Override public List getTop5BookmarkedContent() { List top5Content = contentRepository.findTop5ByBookmarkCount(); return top5Content.stream() - .map(content -> new ContentResDto( - content.getContent_id(), - content.getTitle(), - content.getSummary(), - content.getBackground(), - content.getImportance(), - content.getTip(), - content.getAdditional_resources() - )) + .map(this::convertToDto) + .collect(Collectors.toList()); + } + + + //문자열을 줄바꿈 기준으로 분리하여 리스트로 변환 + private List splitByNewLine(String text) { + return Arrays.stream(text.split("\n")) + .map(String::trim) + .filter(s -> !s.isEmpty()) .collect(Collectors.toList()); } + + + //Content -> ContentResDto 변환 + private ContentResDto convertToDto(Content content) { + return new ContentResDto( + content.getContent_id(), + content.getTitle(), + content.getSummary(), + splitByNewLine(content.getBackground()), + splitByNewLine(content.getImportance()), + splitByNewLine(content.getTip()), + splitByNewLine(content.getAdditional_resources()) + ); + } } \ No newline at end of file diff --git a/src/main/java/com/haruhan/user/entity/User.java b/src/main/java/com/haruhan/user/entity/User.java index 21b2075..50c7cfd 100644 --- a/src/main/java/com/haruhan/user/entity/User.java +++ b/src/main/java/com/haruhan/user/entity/User.java @@ -43,6 +43,9 @@ public class User { //orphanRemoval : 연결이 끊어진(null이 된) 북마크 엔티티를 자동 삭제 private List bookmarks; + @Column(name = "last_received_content_id") + private Long lastReceivedContentId = 0L; // 마지막으로 받은 콘텐츠 ID + public User(String email, PreferedTime preferedTime, Boolean isDaily) { this.email = email; diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index 01a2037..40c0985 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -9,6 +9,9 @@ spring: application: name: haruhan-mail + jackson: + time-zone: Asia/Seoul + datasource: url: jdbc:mysql://${DB_HOST}:${DB_PORT}/${DB_NAME} username: ${DB_USERNAME}