diff --git a/src/main/java/com/back/domain/mybar/service/MyBarService.java b/src/main/java/com/back/domain/mybar/service/MyBarService.java index 2eacf9b5..c93d22b9 100644 --- a/src/main/java/com/back/domain/mybar/service/MyBarService.java +++ b/src/main/java/com/back/domain/mybar/service/MyBarService.java @@ -7,6 +7,7 @@ import com.back.domain.mybar.enums.KeepStatus; import com.back.domain.mybar.repository.MyBarRepository; import com.back.domain.user.repository.UserRepository; +import com.back.domain.user.service.AbvScoreService; import lombok.RequiredArgsConstructor; import org.springframework.data.domain.Page; import org.springframework.data.domain.PageRequest; @@ -26,6 +27,7 @@ public class MyBarService { private final MyBarRepository myBarRepository; private final UserRepository userRepository; private final CocktailRepository cocktailRepository; + private final AbvScoreService abvScoreService; // 내 바 목록 조회 (무한스크롤) // - 커서: lastKeptAt + lastId 조합으로 안정적인 정렬/페이지네이션 @@ -83,6 +85,8 @@ public void keep(Long userId, Long cocktailId) { // 해제돼 있던 건 복원 myBar.setStatus(KeepStatus.ACTIVE); myBar.setDeletedAt(null); + // 활동 점수: 복원 시 +0.1 + abvScoreService.awardForKeep(userId); } return; // 이미 ACTIVE여도 keptAt 갱신으로 충분 } @@ -95,12 +99,18 @@ public void keep(Long userId, Long cocktailId) { myBar.setKeptAt(now); myBarRepository.save(myBar); + // 활동 점수: 신규 keep 시 +0.1 + abvScoreService.awardForKeep(userId); } /** 킵 해제(소프트 삭제) — 멱등 처리 */ @Transactional public void unkeep(Long userId, Long cocktailId) { - myBarRepository.softDeleteByUserAndCocktail(userId, cocktailId); + int changed = myBarRepository.softDeleteByUserAndCocktail(userId, cocktailId); + // 실제로 ACTIVE -> DELETED로 변경된 경우만 -0.1 + if (changed > 0) { + abvScoreService.revokeForKeep(userId); + } } } diff --git a/src/main/java/com/back/domain/post/comment/service/CommentService.java b/src/main/java/com/back/domain/post/comment/service/CommentService.java index 6e9b11c2..e589cc50 100644 --- a/src/main/java/com/back/domain/post/comment/service/CommentService.java +++ b/src/main/java/com/back/domain/post/comment/service/CommentService.java @@ -12,6 +12,7 @@ import com.back.domain.post.post.repository.PostRepository; import com.back.domain.user.entity.User; import com.back.global.rq.Rq; +import com.back.domain.user.service.AbvScoreService; import java.util.List; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -25,6 +26,7 @@ public class CommentService { private final PostRepository postRepository; private final NotificationService notificationService; private final Rq rq; + private final AbvScoreService abvScoreService; // 댓글 작성 로직 @Transactional @@ -48,7 +50,10 @@ public CommentResponseDto createComment(Long postId, CommentCreateRequestDto req user.getNickname() + " 님이 댓글을 남겼습니다." ); - return new CommentResponseDto(commentRepository.save(comment)); + Comment saved = commentRepository.save(comment); + // 활동 점수: 댓글 작성 +0.2 + abvScoreService.awardForComment(user.getId()); + return new CommentResponseDto(saved); } // 댓글 다건 조회 로직 (무한스크롤) @@ -102,6 +107,8 @@ public void deleteComment(Long postId, Long commentId) { } comment.updateStatus(CommentStatus.DELETED); + // 활동 점수: 댓글 삭제 시 -0.2 (작성자 기준) + abvScoreService.revokeForComment(user.getId()); // soft delete를 사용하기 위해 레포지토리 삭제 작업은 진행하지 않음. // commentRepository.delete(comment); diff --git a/src/main/java/com/back/domain/post/post/service/PostService.java b/src/main/java/com/back/domain/post/post/service/PostService.java index c54d2818..03552e7f 100644 --- a/src/main/java/com/back/domain/post/post/service/PostService.java +++ b/src/main/java/com/back/domain/post/post/service/PostService.java @@ -17,6 +17,7 @@ import com.back.domain.post.post.repository.TagRepository; import com.back.domain.user.entity.User; import com.back.global.rq.Rq; +import com.back.domain.user.service.AbvScoreService; import java.util.List; import java.util.NoSuchElementException; import java.util.Optional; @@ -35,6 +36,7 @@ public class PostService { private final PostLikeRepository postLikeRepository; private final NotificationService notificationService; private final Rq rq; + private final AbvScoreService abvScoreService; // 게시글 작성 로직 @Transactional @@ -58,7 +60,10 @@ public PostResponseDto createPost(PostCreateRequestDto reqBody) { addTag(tagNames, post); } - return new PostResponseDto(postRepository.save(post)); + Post saved = postRepository.save(post); + // 활동 점수: 게시글 작성 +0.5 + abvScoreService.awardForPost(user.getId()); + return new PostResponseDto(saved); } // 게시글 다건 조회 로직 @@ -136,6 +141,8 @@ public void deletePost(Long postId) { .orElseThrow(() -> new NoSuchElementException("해당 게시글을 찾을 수 없습니다. ID: " + postId)); post.updateStatus(PostStatus.DELETED); + // 활동 점수: 게시글 삭제 시 -0.5 (작성자 기준) + abvScoreService.revokeForPost(post.getUser().getId()); // soft delete를 사용하기 위해 레포지토리 삭제 작업은 진행하지 않음. // postRepository.delete(post); @@ -156,6 +163,8 @@ public void toggleLike(Long postId) { existingLike.get().updateStatus(PostLikeStatus.NONE); postLikeRepository.delete(existingLike.get()); post.decreaseLikeCount(); + // 활동 점수: 추천 취소 시 -0.1 + abvScoreService.revokeForLike(user.getId()); } else { // 추천 추가 PostLike postLike = PostLike.builder() @@ -165,6 +174,8 @@ public void toggleLike(Long postId) { .build(); postLikeRepository.save(postLike); post.increaseLikeCount(); + // 활동 점수: 추천 추가 시 +0.1 + abvScoreService.awardForLike(user.getId()); } // 게시글 작성자에게 알림 전송 diff --git a/src/main/java/com/back/domain/user/service/AbvScoreService.java b/src/main/java/com/back/domain/user/service/AbvScoreService.java new file mode 100644 index 00000000..ec66e02b --- /dev/null +++ b/src/main/java/com/back/domain/user/service/AbvScoreService.java @@ -0,0 +1,77 @@ +package com.back.domain.user.service; + +import com.back.domain.user.entity.User; +import com.back.domain.user.repository.UserRepository; +import com.back.global.exception.ServiceException; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +@Service +@RequiredArgsConstructor +public class AbvScoreService { + + private final UserRepository userRepository; + + private static final double POST_SCORE = 0.5; + private static final double COMMENT_SCORE = 0.2; + private static final double LIKE_SCORE = 0.1; + private static final double KEEP_SCORE = 0.1; + + @Transactional + public void awardForPost(Long userId) { + addScore(userId, POST_SCORE); + } + + @Transactional + public void revokeForPost(Long userId) { + addScore(userId, -POST_SCORE); + } + + @Transactional + public void awardForComment(Long userId) { + addScore(userId, COMMENT_SCORE); + } + + @Transactional + public void revokeForComment(Long userId) { + addScore(userId, -COMMENT_SCORE); + } + + @Transactional + public void awardForLike(Long userId) { + addScore(userId, LIKE_SCORE); + } + + @Transactional + public void revokeForLike(Long userId) { + addScore(userId, -LIKE_SCORE); + } + + @Transactional + public void awardForKeep(Long userId) { + addScore(userId, KEEP_SCORE); + } + + @Transactional + public void revokeForKeep(Long userId) { + addScore(userId, -KEEP_SCORE); + } + + private void addScore(Long userId, double delta) { + User user = userRepository.findById(userId) + .orElseThrow(() -> new ServiceException(404, "사용자를 찾을 수 없습니다.")); + + Double current = user.getAbvDegree(); + if (current == null) current = 5.0; // 신규 사용자는 5%부터 시작 + + double next = clamp(current + delta, 0.0, 100.0); + user.setAbvDegree(next); + + userRepository.save(user); + } + + private static double clamp(double v, double min, double max) { + return Math.max(min, Math.min(max, v)); + } +} diff --git a/src/main/java/com/back/domain/user/service/UserAuthService.java b/src/main/java/com/back/domain/user/service/UserAuthService.java index 72b5bc72..246e5851 100644 --- a/src/main/java/com/back/domain/user/service/UserAuthService.java +++ b/src/main/java/com/back/domain/user/service/UserAuthService.java @@ -95,7 +95,7 @@ public User joinSocial(String oauthId, String email, String nickname){ User user = User.builder() .email(email) .nickname(uniqueNickname) - .abvDegree(0.0) + .abvDegree(5.0) .createdAt(LocalDateTime.now()) .updatedAt(LocalDateTime.now()) .role("USER")