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 ba575d8..dc7b06e 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,16 @@ 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 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/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..f19dc67 --- /dev/null +++ b/src/main/java/com/haruhan/content/dto/ContentResDto.java @@ -0,0 +1,14 @@ +package com.haruhan.content.dto; + +import java.util.List; + +//사용자가 받은 컨텐츠 정보를 응답하는 DTO +public record ContentResDto( + Long id, + String title, + String summary, + List background, + List importance, + List tip, + List 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..eb732b9 --- /dev/null +++ b/src/main/java/com/haruhan/content/service/ContentService.java @@ -0,0 +1,11 @@ +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..c67d387 --- /dev/null +++ b/src/main/java/com/haruhan/content/service/ContentServiceImpl.java @@ -0,0 +1,76 @@ +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.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; + +@Service +@Transactional +@RequiredArgsConstructor +public class ContentServiceImpl implements ContentService { + + 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)); + + 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(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}