Skip to content
Original file line number Diff line number Diff line change
Expand Up @@ -11,8 +11,10 @@
import net.causw.app.main.domain.community.post.service.implementation.PostReader;
import net.causw.app.main.domain.community.post.service.util.LikePostValidator;
import net.causw.app.main.domain.community.post.service.util.PostValidator;
import net.causw.app.main.domain.community.reaction.service.implementation.LikePostReader;
import net.causw.app.main.domain.community.reaction.service.implementation.LikePostWriter;
import net.causw.app.main.domain.notification.notification.event.PostLikedEvent;
import net.causw.app.main.domain.notification.notification.event.PostLikeMilestoneReachedEvent;
import net.causw.app.main.domain.notification.notification.service.policy.LikePostNotificationPolicy;
import net.causw.app.main.domain.user.account.entity.user.User;
import net.causw.app.main.domain.user.account.service.implementation.UserReader;
import net.causw.app.main.domain.user.relation.service.implementation.BlockReader;
Expand All @@ -26,6 +28,7 @@
public class LikePostService {

private final PostReader postReader;
private final LikePostReader likePostReader;
private final LikePostWriter likePostWriter;
private final LikePostValidator likePostValidator;
private final ApplicationEventPublisher eventPublisher;
Expand All @@ -52,8 +55,10 @@ public void likePost(String userId, String postId) {

likePostWriter.saveLikePost(userId, post);

// 좋아요 알림 이벤트
eventPublisher.publishEvent(new PostLikedEvent(postId, userId));
long likeCount = likePostReader.countByPostId(postId);
if (LikePostNotificationPolicy.isMilestone(likeCount)) {
eventPublisher.publishEvent(new PostLikeMilestoneReachedEvent(postId, userId, likeCount));
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
package net.causw.app.main.domain.notification.notification.entity;

import net.causw.app.main.domain.community.post.entity.Post;
import net.causw.app.main.domain.notification.notification.enums.PostLikeMilestoneAchievementStatus;
import net.causw.app.main.domain.notification.notification.enums.PostLikeMilestoneSuppressionReason;
import net.causw.app.main.domain.user.account.entity.user.User;
import net.causw.app.main.shared.entity.BaseEntity;

import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.FetchType;
import jakarta.persistence.Index;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToOne;
import jakarta.persistence.Table;
import jakarta.persistence.UniqueConstraint;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Getter
@Entity
@Builder(access = AccessLevel.PROTECTED)
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PRIVATE)
@Table(name = "tb_post_like_milestone_achievement", uniqueConstraints = {
@UniqueConstraint(name = "uk_post_like_milestone_achievement_post_milestone", columnNames = {"post_id",
"milestone_count"})
}, indexes = {
@Index(name = "idx_post_like_milestone_achievement_trigger_user", columnList = "trigger_user_id")
})
public class PostLikeMilestoneAchievement extends BaseEntity {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "post_id", nullable = false, updatable = false)
private Post post;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "trigger_user_id", updatable = false)
private User triggerUser;

@Column(name = "milestone_count", nullable = false, updatable = false)
private long milestoneCount;

@Enumerated(EnumType.STRING)
@Column(name = "status", nullable = false, length = 32)
private PostLikeMilestoneAchievementStatus status;

@Enumerated(EnumType.STRING)
@Column(name = "suppression_reason", length = 32)
private PostLikeMilestoneSuppressionReason suppressionReason;

@OneToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "notification_id", unique = true)
private Notification notification;

public static PostLikeMilestoneAchievement pending(Post post, User triggerUser, long milestoneCount) {
return PostLikeMilestoneAchievement.builder()
.post(post)
.triggerUser(triggerUser)
.milestoneCount(milestoneCount)
.status(PostLikeMilestoneAchievementStatus.PENDING)
.build();
}

public static PostLikeMilestoneAchievement baselined(Post post, long milestoneCount) {
return PostLikeMilestoneAchievement.builder()
.post(post)
.milestoneCount(milestoneCount)
.status(PostLikeMilestoneAchievementStatus.BASELINED)
.build();
}

public void markNotificationCreated(Notification notification) {
this.notification = notification;
this.suppressionReason = null;
this.status = PostLikeMilestoneAchievementStatus.NOTIFICATION_CREATED;
}

public void suppress(PostLikeMilestoneSuppressionReason suppressionReason) {
this.notification = null;
this.suppressionReason = suppressionReason;
this.status = PostLikeMilestoneAchievementStatus.SUPPRESSED;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package net.causw.app.main.domain.notification.notification.enums;

public enum PostLikeMilestoneAchievementStatus {
PENDING,
BASELINED,
NOTIFICATION_CREATED,
SUPPRESSED
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package net.causw.app.main.domain.notification.notification.enums;

public enum PostLikeMilestoneSuppressionReason {
SELF_LIKE,
SETTING_DISABLED,
BLOCKED,
TARGET_UNAVAILABLE
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package net.causw.app.main.domain.notification.notification.event;

import net.causw.app.main.domain.notification.notification.service.dto.PushNotificationData;

public record PostLikeMilestonePushEvent(
String recipientUserId,
String pushTitle,
String pushBody,
PushNotificationData pushData) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
package net.causw.app.main.domain.notification.notification.event;

public record PostLikeMilestoneReachedEvent(String postId, String likerId, long milestoneCount) {
}

This file was deleted.

Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package net.causw.app.main.domain.notification.notification.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import net.causw.app.main.domain.notification.notification.entity.PostLikeMilestoneAchievement;

@Repository
public interface PostLikeMilestoneAchievementRepository
extends JpaRepository<PostLikeMilestoneAchievement, String> {

boolean existsByPostIdAndMilestoneCount(String postId, long milestoneCount);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package net.causw.app.main.domain.notification.notification.service.implementation;

import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import net.causw.app.main.domain.notification.notification.entity.PostLikeMilestoneAchievement;
import net.causw.app.main.domain.notification.notification.repository.PostLikeMilestoneAchievementRepository;

import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
@Transactional(readOnly = true)
public class PostLikeMilestoneAchievementReader {

private final PostLikeMilestoneAchievementRepository achievementRepository;

public PostLikeMilestoneAchievement findById(String achievementId) {
return achievementRepository.findById(achievementId)
.orElseThrow(() -> new IllegalStateException(
"게시글 좋아요 마일스톤 이력을 찾을 수 없습니다: " + achievementId));
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package net.causw.app.main.domain.notification.notification.service.implementation;

import java.util.Optional;

import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import net.causw.app.main.domain.community.post.entity.Post;
import net.causw.app.main.domain.community.post.service.implementation.PostReader;
import net.causw.app.main.domain.notification.notification.event.PostLikeMilestoneReachedEvent;
import net.causw.app.main.domain.user.account.entity.user.User;
import net.causw.app.main.domain.user.account.service.implementation.UserReader;

import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class PostLikeMilestoneAchievementRecorder {

private final PostReader postReader;
private final UserReader userReader;
private final PostLikeMilestoneAchievementWriter achievementWriter;

@Transactional(propagation = Propagation.REQUIRES_NEW)
public Optional<String> record(PostLikeMilestoneReachedEvent event) {
Post post = postReader.findById(event.postId());
User liker = userReader.findUserById(event.likerId());

return achievementWriter.savePendingIfAbsent(post, liker, event.milestoneCount())
.map(achievement -> achievement.getId());
}
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
package net.causw.app.main.domain.notification.notification.service.implementation;

import java.util.Optional;

import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;

import net.causw.app.main.domain.community.post.entity.Post;
import net.causw.app.main.domain.notification.notification.entity.Notification;
import net.causw.app.main.domain.notification.notification.entity.PostLikeMilestoneAchievement;
import net.causw.app.main.domain.notification.notification.enums.PostLikeMilestoneSuppressionReason;
import net.causw.app.main.domain.notification.notification.repository.PostLikeMilestoneAchievementRepository;
import net.causw.app.main.domain.user.account.entity.user.User;

import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
@Transactional
public class PostLikeMilestoneAchievementWriter {

private final PostLikeMilestoneAchievementRepository achievementRepository;

public Optional<PostLikeMilestoneAchievement> savePendingIfAbsent(
Post post,
User triggerUser,
long milestoneCount) {
if (achievementRepository.existsByPostIdAndMilestoneCount(post.getId(), milestoneCount)) {
return Optional.empty();
}

PostLikeMilestoneAchievement achievement = PostLikeMilestoneAchievement.pending(
post,
triggerUser,
milestoneCount);
return Optional.of(achievementRepository.saveAndFlush(achievement));
}
Comment thread
KEEKE132 marked this conversation as resolved.

public PostLikeMilestoneAchievement suppress(
PostLikeMilestoneAchievement achievement,
PostLikeMilestoneSuppressionReason suppressionReason) {
achievement.suppress(suppressionReason);
return achievementRepository.save(achievement);
}

public PostLikeMilestoneAchievement markNotificationCreated(
PostLikeMilestoneAchievement achievement,
Notification notification) {
achievement.markNotificationCreated(notification);
return achievementRepository.save(achievement);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package net.causw.app.main.domain.notification.notification.service.implementation;

import org.springframework.context.ApplicationEventPublisher;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import net.causw.app.main.domain.community.common.service.CommunityPermissionPolicy;
import net.causw.app.main.domain.community.post.entity.Post;
import net.causw.app.main.domain.notification.notification.entity.Notification;
import net.causw.app.main.domain.notification.notification.entity.PostLikeMilestoneAchievement;
import net.causw.app.main.domain.notification.notification.enums.NoticeType;
import net.causw.app.main.domain.notification.notification.enums.PostLikeMilestoneAchievementStatus;
import net.causw.app.main.domain.notification.notification.enums.PostLikeMilestoneSuppressionReason;
import net.causw.app.main.domain.notification.notification.enums.UserNotificationSettingKey;
import net.causw.app.main.domain.notification.notification.event.PostLikeMilestonePushEvent;
import net.causw.app.main.domain.notification.notification.service.dto.PushNotificationData;
import net.causw.app.main.domain.notification.notification.service.dto.UserNotificationSettingMap;
import net.causw.app.main.domain.user.account.entity.user.User;
import net.causw.app.main.domain.user.relation.service.implementation.BlockReader;

import lombok.RequiredArgsConstructor;

@Component
@RequiredArgsConstructor
public class PostLikeMilestoneNotificationProcessor {

private final PostLikeMilestoneAchievementReader achievementReader;
private final PostLikeMilestoneAchievementWriter achievementWriter;
private final NotificationWriter notificationWriter;
private final NotificationSettingReader notificationSettingReader;
private final BlockReader blockReader;
private final ApplicationEventPublisher eventPublisher;

@Transactional(propagation = Propagation.REQUIRES_NEW)
public void process(String achievementId) {
PostLikeMilestoneAchievement achievement = achievementReader.findById(achievementId);
if (achievement.getStatus() != PostLikeMilestoneAchievementStatus.PENDING) {
return;
}

Post post = achievement.getPost();
User liker = achievement.getTriggerUser();
User postWriter = post == null ? null : post.getWriter();

if (liker == null || isTargetUnavailable(post, postWriter)) {
achievementWriter.suppress(achievement, PostLikeMilestoneSuppressionReason.TARGET_UNAVAILABLE);
return;
}

if (liker.getId().equals(postWriter.getId())) {
achievementWriter.suppress(achievement, PostLikeMilestoneSuppressionReason.SELF_LIKE);
return;
}

UserNotificationSettingMap settingMap = notificationSettingReader.findSettingMap(postWriter.getId());
if (!settingMap.get(UserNotificationSettingKey.COMMUNITY_LIKE_ON_MY_POST)) {
achievementWriter.suppress(achievement, PostLikeMilestoneSuppressionReason.SETTING_DISABLED);
return;
}

if (blockReader.existsByBlockerAndBlocked(postWriter, liker)) {
achievementWriter.suppress(achievement, PostLikeMilestoneSuppressionReason.BLOCKED);
return;
}

long likeCount = achievement.getMilestoneCount();
String serviceTitle = String.format("게시물이 좋아요 %d개를 달성했습니다!", likeCount);
String serviceBody = String.format("내 게시글에 좋아요가 %d개 달렸어요.", likeCount);
String pushTitle = String.format("게시물 좋아요 %d개 달성", likeCount);
PushNotificationData pushData = new PushNotificationData(NoticeType.COMMUNITY, post.getId(),
post.getBoard().getId());

Notification notification = notificationWriter.save(
Notification.of(postWriter, serviceTitle, serviceBody, NoticeType.COMMUNITY, post.getId(),
post.getBoard().getId()));
notificationWriter.saveLog(postWriter, notification);
achievementWriter.markNotificationCreated(achievement, notification);

eventPublisher.publishEvent(new PostLikeMilestonePushEvent(
postWriter.getId(),
pushTitle,
serviceBody,
pushData));
}

private boolean isTargetUnavailable(Post post, User postWriter) {
return !CommunityPermissionPolicy.isAlive(post)
|| postWriter == null
|| postWriter.isInactive()
|| postWriter.isDropped();
}
}
Loading
Loading