Skip to content

Commit

Permalink
fix(article): slack message for safe
Browse files Browse the repository at this point in the history
  • Loading branch information
gracefulBrown committed Jun 5, 2024
1 parent ceef3a9 commit e44c3c7
Show file tree
Hide file tree
Showing 11 changed files with 284 additions and 130 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
package wooteco.prolog.article.application;

import com.google.common.collect.Lists;
import lombok.RequiredArgsConstructor;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
Expand All @@ -10,7 +11,8 @@
import wooteco.prolog.article.application.dto.ArticleResponse;
import wooteco.prolog.article.domain.Article;
import wooteco.prolog.article.domain.ArticleFilterType;
import wooteco.prolog.article.domain.Articles;
import wooteco.prolog.article.domain.RssFeed;
import wooteco.prolog.article.domain.RssFeeds;
import wooteco.prolog.article.domain.repository.ArticleRepository;
import wooteco.prolog.common.exception.BadRequestException;
import wooteco.prolog.login.ui.LoginMember;
Expand All @@ -19,6 +21,7 @@
import wooteco.prolog.member.domain.MemberUpdatedEvent;
import wooteco.prolog.member.domain.Role;

import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
Expand All @@ -37,6 +40,7 @@ public class ArticleService {
private final ArticleRepository articleRepository;
private final MemberService memberService;
private final SlackService slackService;
private final RssClient rssClient;

@Transactional
public Long create(final ArticleRequest articleRequest, final LoginMember loginMember) {
Expand Down Expand Up @@ -113,7 +117,7 @@ public void updateViewCount(final Long id) {
@EventListener
public void handleMemberUpdatedEvent(MemberUpdatedEvent event) {
Member member = event.getMember();
fetchArticleWhenMemberUpdated(member);
fetchArticlesOf(member);
}

@Transactional
Expand All @@ -136,54 +140,52 @@ private List<ArticleResponse> fetchArticleWithRssFeeds() {
}

private List<ArticleResponse> fetchArticleWithRssFeedOf(Member member) {
try {
List<Article> newArticles = findNewArticles(member);
if (newArticles.isEmpty()) {
return new ArrayList<>();
}

List<Article> persistNewArticles = articleRepository.saveAll(newArticles);
List<Article> newArticles = fetchArticlesOf(member);
if (newArticles.isEmpty()) {
return new ArrayList<>();
}

// 가장 최신의 글만 슬랙 메시지로 알림
persistNewArticles.stream()
.max(Comparator.comparing(Article::getPublishedAt))
.ifPresent(slackService::sendSlackMessage);
// 가장 최신의 글만 슬랙 메시지로 알림
newArticles.stream()
.max(Comparator.comparing(Article::getPublishedAt))
.ifPresent(slackService::sendSlackMessage);

return persistNewArticles.stream()
.map(article -> ArticleResponse.of(article, member.getId()))
.collect(toList());
} catch (Exception e) {
logger.error("Failed to fetch RSS feed for member: " + member.getId(), e);
throw new RssFeedException("Failed to fetch RSS feed for member: " + member.getId(), e);
}
// 저장된 모든 아티클 출력
return newArticles.stream()
.map(article -> ArticleResponse.of(article, member.getId()))
.collect(toList());
}

private List<ArticleResponse> fetchArticleWhenMemberUpdated(Member member) {
private List<Article> fetchArticlesOf(Member member) {
try {
List<Article> newArticles = findNewArticles(member).stream()
.sorted(Comparator.comparing(Article::getPublishedAt).reversed())
.limit(3)
.collect(toList());
RssFeeds rssFeeds = rssClient.fromRssFeedBy(member.getRssFeedUrl());
RssFeed latestRssFeed = rssFeeds.findLatestRssFeed();

// 만약에 가장 최신의 피드가 없으면? 끝
if (rssFeeds.isEmpty()) {
new ArrayList<>();
}

if (newArticles.isEmpty()) {
return new ArrayList<>();
// 기존에 하나도 저장된 아티클이 없으면? 가장 최신의 피드를 저장
List<Article> existedArticles = articleRepository.findAllByMemberId(member.getId());
if (existedArticles.isEmpty()) {
return Lists.newArrayList(articleRepository.save(latestRssFeed.toArticle(member)));
}

List<Article> persistNewArticles = articleRepository.saveAll(newArticles);
// 가장 최근 아티클의 발행시간 조회
LocalDateTime latestArticlePublishedAt = existedArticles.stream()
.max(Comparator.comparing(Article::getPublishedAt)).get().getPublishedAt();

return persistNewArticles.stream()
.map(article -> ArticleResponse.of(article, member.getId()))
.collect(toList());
// 최근 아티클 발행 시간 이후로 작성된 피드 추출
List<RssFeed> articlesAfter = rssFeeds.findArticlesAfter(latestArticlePublishedAt);

// 최근 아티클 발행 시간 이후로 작성된 피드를 모두 저장
return articleRepository.saveAll(articlesAfter.stream()
.map(it -> it.toArticle(member))
.collect(toList()));
} catch (Exception e) {
logger.error("Failed to fetch RSS feed for member: " + member.getId(), e);
throw new RssFeedException("Failed to fetch RSS feed for member: " + member.getId(), e);
}
}

private List<Article> findNewArticles(Member member) {
Articles rssArticles = Articles.fromRssFeedBy(member);
List<Article> existedArticles = articleRepository.findAllByMemberId(member.getId());

return rssArticles.findNewArticles(existedArticles);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package wooteco.prolog.article.application;

import com.rometools.rome.feed.synd.SyndFeed;
import com.rometools.rome.io.SyndFeedInput;
import com.rometools.rome.io.XmlReader;
import org.springframework.stereotype.Component;
import wooteco.prolog.article.domain.RssFeed;
import wooteco.prolog.article.domain.RssFeeds;

import java.net.URL;
import java.util.ArrayList;

import static java.util.stream.Collectors.toList;

@Component
public class RssClient {
public RssFeeds fromRssFeedBy(String feedUrl) {
try {
URL feedSource = new URL(feedUrl);
SyndFeedInput input = new SyndFeedInput();
SyndFeed syndFeed = input.build(new XmlReader(feedSource));
String defaultImageUrl = RssFeed.extractDefaultImageUrl(syndFeed);

return new RssFeeds(syndFeed.getEntries().stream()
.map(it -> RssFeed.of(it, defaultImageUrl))
.collect(toList()));

} catch (Exception e) {
e.printStackTrace();
return new RssFeeds(new ArrayList<>());
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,6 @@ public ArticleRequest(String title, String url, String imageUrl) {
}

public Article toArticle(final Member member) {
return new Article(member, new Title(title), new Description(description), new Url(url), new ImageUrl(imageUrl));
return new Article(member, new Title(title), new Description(description), new Url(url), new ImageUrl(imageUrl), null);
}
}
13 changes: 0 additions & 13 deletions backend/src/main/java/wooteco/prolog/article/domain/Article.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,10 +67,6 @@ public class Article {
@Embedded
private ViewCount views;

public Article(Member member, Title title, Description description, Url url, ImageUrl imageUrl) {
this(member, title, description, url, imageUrl, null);
}

public Article(Member member, Title title, Url url, ImageUrl imageUrl) {
this(member, title, new Description(), url, imageUrl, null);
}
Expand All @@ -87,15 +83,6 @@ public Article(Member member, Title title, Description description, Url url, Ima
this.views = new ViewCount();
}

public static Article of(Member member, SyndFeed syndFeed, SyndEntry entry) {
Title title = new Title(entry.getTitle());
ImageUrl imageUrl = ImageUrl.of(entry.getDescription().getValue(), syndFeed.getImage().getUrl());
Description description = new Description(entry.getDescription().getValue());
LocalDateTime publishedAt = entry.getPublishedDate().toInstant().atZone(ZoneId.systemDefault()).toLocalDateTime();

return new Article(member, title, description, new Url(entry.getLink()), imageUrl, publishedAt);
}

public void validateOwner(final Member member) {
if (!member.equals(this.member)) {
throw new BadRequestException(BadRequestCode.INVALID_ARTICLE_AUTHOR_EXCEPTION);
Expand Down
48 changes: 0 additions & 48 deletions backend/src/main/java/wooteco/prolog/article/domain/Articles.java

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,8 @@ public Description(String description) {
if (description == null || description.isEmpty() || description.trim().isEmpty()) {
description = "내용없음";
}
if (description.length() > 100) {
description = description.substring(0, 100);
if (description.length() > 200) {
description = description.substring(0, 200);
}

this.description = StringEscapeUtils.unescapeHtml4(Jsoup.clean(description, Safelist.none()));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,4 @@ public ImageUrl(String url) {
}
this.url = url.trim();
}

public static ImageUrl of(String description, String defaultUrl) {
Document doc = Jsoup.parse(description);
Element img = doc.select("img").first();
String url = img != null ? img.attr("src") : defaultUrl;

return new ImageUrl(url);
}
}
Loading

0 comments on commit e44c3c7

Please sign in to comment.