Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,8 @@ ARG JAR_FILE=./build/libs/*-SNAPSHOT.jar

COPY ${JAR_FILE} haruhan.jar

ENTRYPOINT ["java", "-jar", "/haruhan.jar"]
# 시스템 시간대 설정
ENV TZ=Asia/Seoul

# JVM 시간대 설정
ENTRYPOINT ["java", "-Duser.timezone=Asia/Seoul", "-jar", "/haruhan.jar"]
Original file line number Diff line number Diff line change
Expand Up @@ -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<Content, Long> {
}

@Query("SELECT c FROM Content c ORDER BY c.bookmark_count DESC LIMIT 3")
List<Content> findTop5ByBookmarkCount();

@Query("SELECT c FROM Content c WHERE c.content_id <= :end ORDER BY c.content_id ASC")
List<Content> findUpToLastContent(@Param("end") Long lastContentId);
}
Original file line number Diff line number Diff line change
@@ -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<Message> getUserReceivedContent(@PathVariable String email) {
List<ContentResDto> contentList = contentService.getUserReceivedContent(email);
return ResponseEntity.ok(new Message(StatusCode.OK, contentList));
}

@GetMapping("/top5")
public ResponseEntity<Message> getTop5BookmarkedContent() {
List<ContentResDto> top5Content = contentService.getTop5BookmarkedContent();
return ResponseEntity.ok(new Message(StatusCode.OK, top5Content));
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/haruhan/content/dto/ContentResDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.haruhan.content.dto;

import java.util.List;

//사용자가 받은 컨텐츠 정보를 응답하는 DTO
public record ContentResDto(
Long id,
String title,
String summary,
List<String> background,
List<String> importance,
List<String> tip,
List<String> additionalResources
) {}
39 changes: 39 additions & 0 deletions src/main/java/com/haruhan/content/entity/UserReceivedContent.java
Original file line number Diff line number Diff line change
@@ -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로 할지, 복합키 쓸지 고민중
Original file line number Diff line number Diff line change
@@ -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<UserReceivedContent, Long> {

List<UserReceivedContent> findByUser(User user);
}
11 changes: 11 additions & 0 deletions src/main/java/com/haruhan/content/service/ContentService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package com.haruhan.content.service;

import com.haruhan.content.dto.ContentResDto;

import java.util.List;

public interface ContentService {

List<ContentResDto> getUserReceivedContent(String email);
List<ContentResDto> getTop5BookmarkedContent();
}
76 changes: 76 additions & 0 deletions src/main/java/com/haruhan/content/service/ContentServiceImpl.java
Original file line number Diff line number Diff line change
@@ -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<ContentResDto> 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<Content> contentList = contentRepository.findUpToLastContent(lastContentId);

return contentList.stream()
.map(this::convertToDto)
.collect(Collectors.toList());
}


@Override
public List<ContentResDto> getTop5BookmarkedContent() {
List<Content> top5Content = contentRepository.findTop5ByBookmarkCount();

return top5Content.stream()
.map(this::convertToDto)
.collect(Collectors.toList());
}


//문자열을 줄바꿈 기준으로 분리하여 리스트로 변환
private List<String> 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())
);
}
}
3 changes: 3 additions & 0 deletions src/main/java/com/haruhan/user/entity/User.java
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,9 @@ public class User {
//orphanRemoval : 연결이 끊어진(null이 된) 북마크 엔티티를 자동 삭제
private List<Bookmark> bookmarks;

@Column(name = "last_received_content_id")
private Long lastReceivedContentId = 0L; // 마지막으로 받은 콘텐츠 ID


public User(String email, PreferedTime preferedTime, Boolean isDaily) {
this.email = email;
Expand Down
3 changes: 3 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand Down