diff --git a/backend/core/build.gradle b/backend/core/build.gradle index b04a3634..b7f74397 100644 --- a/backend/core/build.gradle +++ b/backend/core/build.gradle @@ -33,11 +33,11 @@ dependencies { runtimeOnly 'com.h2database:h2' runtimeOnly 'com.mysql:mysql-connector-j' implementation 'org.springframework.boot:spring-boot-starter-amqp' + implementation 'org.mapstruct:mapstruct:1.5.5.Final' + implementation 'org.jsoup:jsoup:1.15.3' annotationProcessor 'org.projectlombok:lombok' - - // jsoup 의존성 추가 - implementation 'org.jsoup:jsoup:1.15.3' + annotationProcessor 'org.mapstruct:mapstruct-processor:1.5.5.Final' testImplementation 'org.springframework.boot:spring-boot-starter-test' testImplementation 'org.springframework.security:spring-security-test' diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/controller/BookmarkController.java b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/controller/BookmarkController.java new file mode 100644 index 00000000..eaa46e34 --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/controller/BookmarkController.java @@ -0,0 +1,27 @@ +package com.rollthedice.backend.domain.bookmark.controller; + +import com.rollthedice.backend.domain.bookmark.service.BookmarkService; +import com.rollthedice.backend.domain.news.dto.response.NewsResponse; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestController; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("bookmark") +public class BookmarkController { + private final BookmarkService bookmarkService; + + @ResponseStatus(HttpStatus.OK) + @GetMapping("") + public List<NewsResponse> getBookmarked(final Pageable pageable) { + return bookmarkService.getBookmarkedNews(pageable); + } + +} diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/news/entity/Bookmark.java b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/entity/Bookmark.java similarity index 85% rename from backend/core/src/main/java/com/rollthedice/backend/domain/news/entity/Bookmark.java rename to backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/entity/Bookmark.java index 2d9e8f68..d0314627 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/news/entity/Bookmark.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/entity/Bookmark.java @@ -1,6 +1,7 @@ -package com.rollthedice.backend.domain.news.entity; +package com.rollthedice.backend.domain.bookmark.entity; import com.rollthedice.backend.domain.member.entity.Member; +import com.rollthedice.backend.domain.news.entity.News; import com.rollthedice.backend.global.config.BaseTimeEntity; import jakarta.persistence.*; import lombok.AccessLevel; diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/repository/BookmarkRepository.java b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/repository/BookmarkRepository.java new file mode 100644 index 00000000..76773a78 --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/repository/BookmarkRepository.java @@ -0,0 +1,17 @@ +package com.rollthedice.backend.domain.bookmark.repository; + +import com.rollthedice.backend.domain.member.entity.Member; +import com.rollthedice.backend.domain.bookmark.entity.Bookmark; +import com.rollthedice.backend.domain.news.entity.News; +import org.springframework.data.domain.Pageable; +import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; + +import java.util.List; + +@Repository +public interface BookmarkRepository extends JpaRepository<Bookmark, Long> { + Boolean existsBookmarkByMemberAndNews(Member member, News news); + + List<Bookmark> findAllByMemberOrderByCreatedAt(Member member, Pageable pageable); +} diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/service/BookmarkService.java b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/service/BookmarkService.java new file mode 100644 index 00000000..2a938a13 --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/bookmark/service/BookmarkService.java @@ -0,0 +1,39 @@ +package com.rollthedice.backend.domain.bookmark.service; + +import com.rollthedice.backend.domain.bookmark.repository.BookmarkRepository; +import com.rollthedice.backend.domain.member.entity.Member; +import com.rollthedice.backend.domain.member.query.AuthService; +import com.rollthedice.backend.domain.news.dto.response.NewsResponse; +import com.rollthedice.backend.domain.news.entity.News; +import com.rollthedice.backend.domain.news.mapper.NewsMapper; +import com.rollthedice.backend.domain.news.service.NewsService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.List; +import java.util.stream.Collectors; + +@RequiredArgsConstructor +@Service +public class BookmarkService { + private final AuthService authService; + private final BookmarkRepository bookmarkRepository; + private final NewsMapper newsMapper; + + + public boolean isBookmarked(Member member, News news) { + return bookmarkRepository.existsBookmarkByMemberAndNews(member, news); + } + + @Transactional(readOnly = true) + public List<NewsResponse> getBookmarkedNews(Pageable pageable) { + Member member = authService.getMember(); + return bookmarkRepository.findAllByMemberOrderByCreatedAt(member, pageable).stream() + .map(bookmark -> newsMapper.toResponse(bookmark.getNews(), true)) + .collect(Collectors.toList()); + + } +} + diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/crawling/NewsCrawlingService.java b/backend/core/src/main/java/com/rollthedice/backend/domain/crawling/NewsCrawlingService.java index a0daaa44..461267c5 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/crawling/NewsCrawlingService.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/crawling/NewsCrawlingService.java @@ -12,6 +12,7 @@ import org.jsoup.select.Elements; import org.springframework.scheduling.annotation.Scheduled; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.io.IOException; import java.util.Objects; @@ -26,6 +27,7 @@ public class NewsCrawlingService { private final NewsService newsService; + @Transactional @Scheduled(cron = CRON, zone = ZONE) public void scrap() throws IOException { for (NewsCategory category : NewsCategory.values()) { @@ -33,7 +35,7 @@ public void scrap() throws IOException { String categoryName = category.getName(); scrapNewsUrls(categoryUrl); - for (final News news : newsService.getAllNews()) { + for (final News news : newsService.getNotCrawled()) { scrapNewsContentsAndUpdate(categoryName, news); } } @@ -61,7 +63,8 @@ private String scrapThumbnailUrl(final Element news) { } } - private void scrapNewsContentsAndUpdate(String categoryName, News news) throws IOException { + @Transactional + public void scrapNewsContentsAndUpdate(String categoryName, News news) throws IOException { Document doc = Jsoup.connect(news.getUrl()).get(); String title = scrapTitle(doc); diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/news/controller/NewsController.java b/backend/core/src/main/java/com/rollthedice/backend/domain/news/controller/NewsController.java new file mode 100644 index 00000000..f62ac8bb --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/news/controller/NewsController.java @@ -0,0 +1,24 @@ +package com.rollthedice.backend.domain.news.controller; + +import com.rollthedice.backend.domain.crawling.NewsCrawlingService; +import com.rollthedice.backend.domain.news.dto.response.NewsResponse; +import com.rollthedice.backend.domain.news.service.NewsService; +import lombok.RequiredArgsConstructor; +import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; +import org.springframework.web.bind.annotation.*; + +import java.util.List; + +@RestController +@RequiredArgsConstructor +@RequestMapping("news") +public class NewsController { + private final NewsService newsService; + + @ResponseStatus(HttpStatus.OK) + @GetMapping("") + public List<NewsResponse> getNews(final Pageable pageable) { + return newsService.getNews(pageable); + } +} diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/news/dto/response/NewsResponse.java b/backend/core/src/main/java/com/rollthedice/backend/domain/news/dto/response/NewsResponse.java new file mode 100644 index 00000000..538aec9a --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/news/dto/response/NewsResponse.java @@ -0,0 +1,23 @@ +package com.rollthedice.backend.domain.news.dto.response; + +import jakarta.persistence.GeneratedValue; +import jakarta.persistence.GenerationType; +import jakarta.persistence.Id; +import jakarta.persistence.Lob; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Getter; +import lombok.NoArgsConstructor; + +@Getter +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class NewsResponse { + private Long id; + private String title; + private String content; + private String thumbnail; + private String postDate; + private Boolean isBookmarked; +} diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/news/entity/News.java b/backend/core/src/main/java/com/rollthedice/backend/domain/news/entity/News.java index 09fe35a3..5a69e036 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/news/entity/News.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/news/entity/News.java @@ -6,7 +6,10 @@ import lombok.Builder; import lombok.Getter; import lombok.NoArgsConstructor; +import lombok.extern.slf4j.Slf4j; +import org.hibernate.annotations.ColumnDefault; +@Slf4j @Getter @Entity @NoArgsConstructor(access = AccessLevel.PROTECTED) @@ -24,6 +27,9 @@ public class News extends BaseTimeEntity { private String category; private String postDate; + @ColumnDefault("0L") + private long views; + @Builder public News(String url, String thumbnailUrl) { this.url = url; @@ -31,6 +37,7 @@ public News(String url, String thumbnailUrl) { } public void addNewsBody(String title, String content, String category, String postDate) { + log.info("now category name: {}", category); this.title = title; this.content = content; this.category = category; @@ -41,4 +48,7 @@ public void updateSummarizedContent(String content) { this.content = content; } + public void increaseView() { + views++; + } } diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/news/mapper/NewsMapper.java b/backend/core/src/main/java/com/rollthedice/backend/domain/news/mapper/NewsMapper.java new file mode 100644 index 00000000..1c29d5d9 --- /dev/null +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/news/mapper/NewsMapper.java @@ -0,0 +1,12 @@ +package com.rollthedice.backend.domain.news.mapper; + +import com.rollthedice.backend.domain.news.dto.response.NewsResponse; +import com.rollthedice.backend.domain.news.entity.News; +import org.mapstruct.Mapper; +import org.mapstruct.MappingConstants.ComponentModel; + +@Mapper(componentModel = ComponentModel.SPRING) +public interface NewsMapper { + + NewsResponse toResponse(final News news, boolean isBookmarked); +} diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/news/repository/NewsRepository.java b/backend/core/src/main/java/com/rollthedice/backend/domain/news/repository/NewsRepository.java index a3fc0151..b70a295a 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/news/repository/NewsRepository.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/news/repository/NewsRepository.java @@ -1,7 +1,15 @@ package com.rollthedice.backend.domain.news.repository; import com.rollthedice.backend.domain.news.entity.News; +import org.springframework.data.domain.Pageable; import org.springframework.data.jpa.repository.JpaRepository; +import org.springframework.stereotype.Repository; +import java.util.List; + +@Repository public interface NewsRepository extends JpaRepository<News, Long> { + List<News> findAllByContentIsNull(); + List<News> findAllByOrderByViewsDescCreatedAtDesc( + final Pageable pageable); } diff --git a/backend/core/src/main/java/com/rollthedice/backend/domain/news/service/NewsService.java b/backend/core/src/main/java/com/rollthedice/backend/domain/news/service/NewsService.java index d92bc0f7..29099278 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/domain/news/service/NewsService.java +++ b/backend/core/src/main/java/com/rollthedice/backend/domain/news/service/NewsService.java @@ -1,25 +1,37 @@ package com.rollthedice.backend.domain.news.service; +import com.rollthedice.backend.domain.bookmark.entity.Bookmark; +import com.rollthedice.backend.domain.bookmark.service.BookmarkService; +import com.rollthedice.backend.domain.member.entity.Member; +import com.rollthedice.backend.domain.member.query.AuthService; import com.rollthedice.backend.domain.news.contentqueue.ContentProducer; import com.rollthedice.backend.domain.news.dto.ContentMessageDto; import com.rollthedice.backend.domain.news.dto.NewsUrlDto; +import com.rollthedice.backend.domain.news.dto.response.NewsResponse; import com.rollthedice.backend.domain.news.entity.News; +import com.rollthedice.backend.domain.news.mapper.NewsMapper; import com.rollthedice.backend.domain.news.repository.NewsRepository; import jakarta.persistence.EntityNotFoundException; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; +import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import java.util.ArrayList; import java.util.List; +import java.util.stream.Collectors; @Slf4j @Service @RequiredArgsConstructor public class NewsService { - private final NewsRepository newsRepository; + private final AuthService authService; private final ContentProducer contentProducer; + private final NewsRepository newsRepository; + private final NewsMapper newsMapper; + private final BookmarkService bookmarkService; + @Transactional public void addNews(NewsUrlDto dto) { @@ -27,8 +39,8 @@ public void addNews(NewsUrlDto dto) { } @Transactional(readOnly = true) - public List<News> getAllNews() { - return newsRepository.findAll(); + public List<News> getNotCrawled() { + return newsRepository.findAllByContentIsNull(); } @Transactional @@ -45,4 +57,13 @@ public void summarizeNewsContent() { messages.add(new ContentMessageDto(n.getId(), n.getContent()))); messages.forEach(contentProducer::sendMessage); } + + @Transactional(readOnly = true) + public List<NewsResponse> getNews(final Pageable pageable) { + Member member = authService.getMember(); + return newsRepository.findAllByOrderByViewsDescCreatedAtDesc(pageable).stream() + .map(news -> newsMapper.toResponse( + news, bookmarkService.isBookmarked(member, news))) + .collect(Collectors.toList()); + } } diff --git a/backend/core/src/main/java/com/rollthedice/backend/global/config/SecurityConfig.java b/backend/core/src/main/java/com/rollthedice/backend/global/config/SecurityConfig.java index a5f5cd65..c006cc78 100644 --- a/backend/core/src/main/java/com/rollthedice/backend/global/config/SecurityConfig.java +++ b/backend/core/src/main/java/com/rollthedice/backend/global/config/SecurityConfig.java @@ -53,6 +53,7 @@ public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { -> authorizeRequests .anyRequest().permitAll()) .oauth2Login(oauth2 -> oauth2 + .loginPage("/login") .successHandler(oAuth2LoginSuccessHandler) .failureHandler(oAuth2LoginFailureHandler) .userInfoEndpoint(userInfo -> userInfo.userService(customOAuth2UserService)) //customUserService 설정