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")
})
Comment on lines +31 to +36

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/usr/bin/env bash
set -euo pipefail

echo "== Flyway SQL files =="
fd -t f -e sql . | sort -V

echo "== Achievement schema references =="
fd -t f -e sql . -x rg -n -i -C 3 \
  'post_like_milestone_achievement|uk_post_like_milestone_achievement_post_milestone|idx_post_like_milestone_achievement_trigger_user' {}

echo "== Current PR labels, if available =="
if gh pr view --json labels --jq '.labels[].name' 2>/dev/null; then
  :
else
  echo "PR labels could not be read in this environment."
fi

Repository: CAUCSE/CAUSW_backend

Length of output: 8299


Flyway 스키마 변경을 함께 추가해 주세요.

tb_post_like_milestone_achievement 테이블, 외래 키(post_id, trigger_user_id, notification_id), uk_post_like_milestone_achievement_post_milestone, idx_post_like_milestone_achievement_trigger_user 변경이 엔티티에 추가되어 있으므로, 이 PR에 VYYYYMMDDHHMMSS__create_post_like_milestone_achievement.sql Flyway 마이그레이션과 db-change 라벨을 포함해야 합니다.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.java`
around lines 31 - 36, PostLikeMilestoneAchievement 엔티티 변경에 대응하는 Flyway 마이그레이션을
추가하세요. tb_post_like_milestone_achievement 테이블과 post_id, trigger_user_id,
notification_id 외래 키, uk_post_like_milestone_achievement_post_milestone 유니크 제약,
idx_post_like_milestone_achievement_trigger_user 인덱스를 생성하고, PR에 db-change 라벨을
지정하세요.

Source: Path instructions

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,24 @@
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 net.causw.app.main.shared.exception.errorcode.PostLikeMilestoneAchievementErrorCode;

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(
PostLikeMilestoneAchievementErrorCode.POST_LIKE_MILESTONE_ACHIEVEMENT_NOT_FOUND::toBaseException);
}
}
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());
}
}
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 {
Comment on lines +17 to +20

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟠 Major | 🏗️ Heavy lift

트랜잭션 경계를 Service 메서드로 이동하세요.

PostLikeMilestoneAchievementWriter는 Implementation 계층입니다. Line 19의 클래스 수준 @Transactional은 Writer의 모든 public 메서드를 트랜잭션 진입점으로 만듭니다.

유스케이스를 조합하는 Service 메서드 또는 명시적인 유스케이스 진입점에 트랜잭션 경계를 두세요. Writer는 영속 상태 변경만 수행하게 유지하세요.

As per path instructions, "트랜잭션 경계(@Transactional)가 Service 메서드 단위로 적절하게 설정되어 있는지 확인합니다."

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java`
around lines 17 - 20, Remove the class-level `@Transactional` from
PostLikeMilestoneAchievementWriter and keep this writer focused on persistence
state changes. Move the transaction boundary to the appropriate Service method
or explicit use-case entry point that coordinates the milestone achievement
flow, using method-level `@Transactional` there.

Source: Path instructions


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 on lines +24 to +36

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

ast-grep outline \
  app-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.java \
  --items all

fd -t f -0 'V.*__.*\.sql$' . |
  xargs -0 -r rg -n -C 4 \
    'PostLikeMilestoneAchievement|post_id|milestone_count|UNIQUE|unique'

rg -n -C 5 \
  'uniqueConstraints|post_id|milestoneCount|milestone_count' \
  app-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.java

Repository: CAUCSE/CAUSW_backend

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## Candidate files"
fd -t f \
  'PostLikeMilestoneAchievementWriter.java|PostLikeMilestoneAchievementTest.java|PostLikeMilestoneAchievement.java|PostLikeMilestoneAchievementRepository.java|.*Notification.*Service.*\.java|.*Application.*Listener.*\.java|.*Event.*\.java' \
  app-main/src/main/java app-main/src/test/java

echo
echo "## Writer"
cat -n app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java

echo
echo "## Entity"
cat -n app-main/src/main/java/net/causw/app/main/domain/notification/notification/entity/PostLikeMilestoneAchievement.java

echo
echo "## Repository"
fd -t f 'PostLikeMilestoneAchievementRepository.java' app-main/src/main/java -x cat -n {}

echo
echo "## Test"
cat -n app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriterTest.java

echo
echo "## Migrations mentioning entity/table names"
rg -n -C 4 'PostLikeMilestoneAchievement|post_like_milestone|milestone_count|milestone_count|trigger_user_id|status' app-main/src/main/resources/db/migration app-main/src/main/java/net/causw/app/main/domain/notification || true

echo
echo "## Call sites"
rg -n -C 4 'savePendingIfAbsent|PostLikeMilestoneAchievementWriter|PostLikeMilestoneAchievement' app-main/src/main/java app-main/src/test/java

Repository: CAUCSE/CAUSW_backend

Length of output: 50377


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## PostLikeMilestoneAchievement call sites"
rg -n -C 5 'savePendingIfAbsent|PostLikeMilestoneAchievementWriter|PostLikeMilestoneReachedEvent|PostLikeMilestoneReachedListener|PostLikeMilestoneNotificationProcessor|transactionalEventListener' \
  app-main/src/main/java/net/causw/app/main/domain/notification app-main/src/test/java

echo
echo "## Writer usages in repositories/services via direct grep across app-main"
rg -n -C 4 'PostLikeMilestoneAchievementWriter|savePendingIfAbsent' app-main/src/main/java app-main/src/test/java || true

Repository: CAUCSE/CAUSW_backend

Length of output: 50376


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## Conflict handling symbols"
rg -n -C 3 'DataIntegrityViolationException|isPostMilestoneUniqueConstraintViolation|PostLikeMilestoneAchievementErrorCode|PostLikeMilestoneAchievement' \
  app-main/src/main/java/net/causw/app/main/domain/notification | head -n 220

echo
echo "## Focused constraint check"
python3 - <<'PY'
from pathlib import Path
target = "tb_post_like_milestone_achievement"
for path in Path("app-main/src/main/resources/db/migration").glob("*.sql"):
    txt = path.read_text(errors="ignore").lower()
    if target in txt:
        lines = txt.splitlines()
        for i, line in enumerate(lines, 1):
            if target in line or "uniqu" in line or "constraint" in line:
                print(f"{path}:{i}:{line.strip()}")
PY

Repository: CAUCSE/CAUSW_backend

Length of output: 32447


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## ErrorCodes related to milestone achievement"
fd -t f 'PostLikeMilestoneAchievementErrorCode.java|.*ErrorCode.*java' app-main/src/main/java/net/causw/app/main/domain -x sh -c 'echo "--- $1"; rg -n -C 3 "POST_LIKE_MILESTONE_ACHIEVEMENT|milestone|Unique|UNIQUE|unique" "$1" || true' sh {}

echo
echo "## Base exception / runtime exception handling"
rg -n -C 3 'BaseException|BaseRunTimeV2Exception|RuntimeException|`@ExceptionHandler`|DataIntegrityViolationException|Postlike' app-main/src/main/java/net/causw/app/main/shared app-main/src/main/java/net/causw/app/main/domain/notification | head -n 260

Repository: CAUCSE/CAUSW_backend

Length of output: 27673


마일스톤 중복 방지 로직을 원자적으로 처리하세요.

savePendingIfAbsent()existsByPostIdAndMilestoneCount()saveAndFlush()가 별도 작업이라, 같은 게시글과 마일스톤 이벤트가 동시에 처리되면 두 요청 모두 저장합니다. savePendingIfAbsent()의 충돌 상황을 Optional.empty()로 처리하고, 충돌이 없으면 notificationProcessor::process가 실행될 수 있습니다.

현재 PostLikeMilestoneAchievement 엔티티는 (post_id, milestone_count)에 JPA UniqueConstraint가 있지만, 이 제약에 대한 Flyway 생성迁移와 충돌 오류를 처리하는 예외 처리는 없습니다. DataIntegrityViolationException을 필터해서 해당 제약 only Optional.empty() 처리하고, 다른 무결성 오류는 그대로 전파하세요.

또한 PostLikeMilestoneAchievementWriterTest는 Mockito 단위 케이스만覆蓋하므로, post_id + milestone_count 동시 저장 시 DB가 중복을 막고 한 호출만 처리되는지 확인하는 @DataJpaTest 병렬 케이스를 추가하세요.

📍 Affects 2 files
  • app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java#L24-L36 (this comment)
  • app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriterTest.java#L36-L65
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In
`@app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java`
around lines 24 - 36, Make savePendingIfAbsent atomically handle duplicate
post_id and milestone_count inserts by catching DataIntegrityViolationException,
returning Optional.empty() only when it originates from the
PostLikeMilestoneAchievement unique constraint, and rethrowing all other
integrity violations; preserve successful saves so
notificationProcessor::process can run. Add the required Flyway migration for
this unique constraint. In
app-main/src/main/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriter.java:24-36
update PostLikeMilestoneAchievementWriter; in
app-main/src/test/java/net/causw/app/main/domain/notification/notification/service/implementation/PostLikeMilestoneAchievementWriterTest.java:36-65
add a `@DataJpaTest` parallel-save case proving only one call succeeds for the
same post and milestone.

Source: Path instructions

}

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