Skip to content

Commit 2a777d9

Browse files
authored
Feat: 알림 추가 기능 구현
Feat: 알림 추가 기능 구현
2 parents cb6c82d + e2cff73 commit 2a777d9

17 files changed

+400
-156
lines changed

src/main/java/UMC/news/newsIntelligent/domain/member/repository/MemberTopicRepository.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
11
package UMC.news.newsIntelligent.domain.member.repository;
22

3+
import java.util.Collection;
4+
import java.util.List;
35
import java.util.Optional;
46

57
import UMC.news.newsIntelligent.domain.member.entity.MemberTopic;
@@ -59,4 +61,20 @@ Slice<Topic> searchReadTopicsByKeyword(
5961
order by t.summaryTime desc
6062
""")
6163
Page<Topic> findSubscribedTopicsOrderByUpdatedDesc(@Param("memberId") Long memberId, Pageable pageable);
64+
65+
// 해당 토픽을 구독한 멤버 조회
66+
@Query("""
67+
SELECT mt.member.id
68+
FROM MemberTopic mt
69+
WHERE mt.topic.id = :topicId AND mt.isSubscribe = true
70+
""")
71+
List<Long> findSubscribedMemberIdsByTopicId(Long topicId);
72+
73+
// 해당 토픽을 읽은 멤버 조회
74+
@Query("""
75+
SELECT mt.member.id
76+
FROM MemberTopic mt
77+
WHERE mt.topic.id IN :topicId AND mt.isRead = true
78+
""")
79+
List<Long> findReadMemberIdsByTopicId(Long topicId);
6280
}

src/main/java/UMC/news/newsIntelligent/domain/news/entity/LatestCorrection.java

Lines changed: 0 additions & 30 deletions
This file was deleted.
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
1+
package UMC.news.newsIntelligent.domain.news.entity.latestCorrection;
2+
3+
import UMC.news.newsIntelligent.domain.topic.entity.Topic;
4+
import UMC.news.newsIntelligent.global.entity.BaseEntity;
5+
import jakarta.persistence.*;
6+
import lombok.AccessLevel;
7+
import lombok.AllArgsConstructor;
8+
import lombok.Getter;
9+
import lombok.NoArgsConstructor;
10+
11+
import java.util.ArrayList;
12+
import java.util.List;
13+
14+
@Entity
15+
@Getter
16+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
17+
@AllArgsConstructor
18+
@Table(name = "latest_correction")
19+
public class LatestCorrection extends BaseEntity {
20+
21+
@Id @GeneratedValue(strategy = GenerationType.IDENTITY)
22+
private Long id;
23+
24+
@Column(name = "run_key", nullable = false, length = 36)
25+
private String runKey;
26+
27+
@ManyToOne(fetch = FetchType.LAZY, optional = false)
28+
@JoinColumn(name = "topic_id", nullable = false)
29+
private Topic topic;
30+
31+
@OneToMany(mappedBy = "latestCorrection", cascade = CascadeType.ALL, orphanRemoval = true)
32+
private List<LatestCorrectionItem> items = new ArrayList<>();
33+
34+
public static LatestCorrection of(String runKey, Topic topic) {
35+
LatestCorrection lc = new LatestCorrection();
36+
lc.runKey = runKey;
37+
lc.topic = topic;
38+
return lc;
39+
}
40+
41+
public void addItem(LatestCorrectionItem item) {
42+
this.items.add(item);
43+
}
44+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package UMC.news.newsIntelligent.domain.news.entity.latestCorrection;
2+
3+
import UMC.news.newsIntelligent.domain.news.entity.News;
4+
import jakarta.persistence.*;
5+
import lombok.AccessLevel;
6+
import lombok.Getter;
7+
import lombok.NoArgsConstructor;
8+
9+
import static jakarta.persistence.FetchType.LAZY;
10+
11+
@Entity
12+
@Getter
13+
@NoArgsConstructor(access = AccessLevel.PROTECTED)
14+
@Table(name = "latest_correction_item")
15+
public class LatestCorrectionItem {
16+
@Id
17+
@GeneratedValue(strategy = GenerationType.IDENTITY)
18+
private Long id;
19+
20+
@ManyToOne(fetch = LAZY, optional = false)
21+
@JoinColumn(name="latest_correction_id", nullable=false)
22+
private LatestCorrection latestCorrection;
23+
24+
@ManyToOne(fetch = LAZY, optional = false)
25+
@JoinColumn(name="news_id", nullable=false)
26+
private News news;
27+
28+
public static LatestCorrectionItem of(LatestCorrection lc, News news) {
29+
LatestCorrectionItem item = new LatestCorrectionItem();
30+
item.latestCorrection = lc;
31+
item.news = news;
32+
return item;
33+
}
34+
}

src/main/java/UMC/news/newsIntelligent/domain/news/repository/LatestCorrectionRepository.java

Lines changed: 0 additions & 16 deletions
This file was deleted.

src/main/java/UMC/news/newsIntelligent/domain/news/repository/NewsRepository.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,6 +73,19 @@ SELECT MAX(n3.id)
7373
""")
7474
List<OldestPerTopicProjection> findOldestPerTopic(java.util.Collection<Long> topicIds);
7575

76+
// is_new = true AND is_third = false 인 기사 중
77+
// 아직 어떤 run에서도 LatestCorrectionItem 에 기록되지 않은 것만
78+
@Query("""
79+
SELECT n
80+
FROM News n
81+
WHERE n.isNew = true AND n.isThird = false
82+
AND NOT EXISTS (
83+
SELECT 1 FROM LatestCorrectionItem i
84+
WHERE i.news.id = n.id
85+
)
86+
""")
87+
List<News> findAllQualifiedNotRecorded();
88+
7689
// 특정 토픽 내에서 가장 오래된 publishDate, id가 가장 큰 기사
7790
Optional<News> findFirstByTopicIdOrderByPublishDateAscIdDesc(Long topicId);
7891
}
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
package UMC.news.newsIntelligent.domain.news.repository.latestCorrection;
2+
3+
import UMC.news.newsIntelligent.domain.news.entity.latestCorrection.LatestCorrectionItem;
4+
import org.springframework.data.jpa.repository.JpaRepository;
5+
6+
public interface LatestCorrectionItemRepository extends JpaRepository<LatestCorrectionItem, Long> {
7+
boolean existsByNews_Id(Long newsId);
8+
9+
boolean existsByLatestCorrection_IdAndNews_Id(Long latestCorrectionId, Long newsId);
10+
11+
}
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
package UMC.news.newsIntelligent.domain.news.repository.latestCorrection;
2+
3+
import UMC.news.newsIntelligent.domain.news.entity.latestCorrection.LatestCorrection;
4+
import org.springframework.data.jpa.repository.EntityGraph;
5+
import org.springframework.data.jpa.repository.JpaRepository;
6+
7+
import java.util.List;
8+
import java.util.Optional;
9+
10+
public interface LatestCorrectionRepository extends JpaRepository<LatestCorrection, Long> {
11+
Optional<LatestCorrection> findByRunKeyAndTopic_Id(String runKey, Long topicId);
12+
13+
@EntityGraph(attributePaths = {"topic", "items", "items.news"})
14+
List<LatestCorrection> findByRunKey(String runKey);
15+
}

src/main/java/UMC/news/newsIntelligent/domain/news/service/LatestCorrectionQueryService.java

Lines changed: 0 additions & 4 deletions
This file was deleted.
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package UMC.news.newsIntelligent.domain.news.service;
2+
3+
import UMC.news.newsIntelligent.domain.news.entity.News;
4+
import UMC.news.newsIntelligent.domain.news.entity.latestCorrection.LatestCorrection;
5+
import UMC.news.newsIntelligent.domain.news.entity.latestCorrection.LatestCorrectionItem;
6+
import UMC.news.newsIntelligent.domain.news.repository.NewsRepository;
7+
import UMC.news.newsIntelligent.domain.news.repository.latestCorrection.LatestCorrectionItemRepository;
8+
import UMC.news.newsIntelligent.domain.news.repository.latestCorrection.LatestCorrectionRepository;
9+
import UMC.news.newsIntelligent.domain.topic.entity.Topic;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.stereotype.Service;
12+
import org.springframework.transaction.annotation.Transactional;
13+
14+
import java.util.ArrayList;
15+
import java.util.LinkedHashMap;
16+
import java.util.List;
17+
import java.util.Map;
18+
import java.util.stream.Collectors;
19+
20+
@Service
21+
@RequiredArgsConstructor
22+
public class LatestCorrectionService {
23+
24+
private final NewsRepository newsRepository;
25+
private final LatestCorrectionRepository lcRepository;
26+
private final LatestCorrectionItemRepository itemRepository;
27+
28+
@Transactional
29+
public List<LatestCorrection> record(String runKey) {
30+
// 이번 사이클에서 조건을 만족하지만 아직 기록되지 않은 뉴스 수집
31+
List<News> candidates = newsRepository.findAllQualifiedNotRecorded();
32+
if (candidates.isEmpty()) return List.of();
33+
34+
// 토픽 기준으로 그룹핑
35+
Map<Topic, List<News>> byTopic = candidates.stream()
36+
.collect(Collectors.groupingBy(News::getTopic, LinkedHashMap::new, Collectors.toList()));
37+
38+
List<LatestCorrection> out = new ArrayList<>();
39+
40+
for (var entry : byTopic.entrySet()) {
41+
Topic topic = entry.getKey();
42+
List<News> newsList = entry.getValue();
43+
44+
LatestCorrection lc = lcRepository.findByRunKeyAndTopic_Id(runKey, topic.getId())
45+
.orElseGet(() -> lcRepository.save(LatestCorrection.of(runKey, topic)));
46+
47+
// 각 뉴스에 대해 이미 기록되었으면 스킵, 아니면 추가
48+
for (News n : newsList) {
49+
if (itemRepository.existsByLatestCorrection_IdAndNews_Id(lc.getId(), n.getId())
50+
|| itemRepository.existsByNews_Id(n.getId())) {
51+
continue;
52+
}
53+
LatestCorrectionItem item = LatestCorrectionItem.of(lc, n);
54+
itemRepository.save(item);
55+
lc.addItem(item);
56+
}
57+
out.add(lc);
58+
}
59+
return out;
60+
}
61+
}

0 commit comments

Comments
 (0)