본문 바로가기

프로젝트/멋사 개인 프로젝트 (mutsa-SNS)

[23] mutsa-SNS-2 5일차 - 알람 기능 추가

알람 기능을 추가해 보았다.

  • 로그인 한 유저에게 달린 모든 댓글과 좋아요를 page형태로 return
  • 자신이 자신의 글에 댓글과 좋아요를 달 시 알람 x
  • 좋아요 취소후 재등록시 알람 x

 

1. Domain

Alarm (Entity)

@Builder
@AllArgsConstructor
@NoArgsConstructor
@Getter
@Setter
@Table(name = "alarm")
@Entity
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
public class Alarm extends BaseEntity{

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private int id;

    @ManyToOne(fetch = LAZY)
    @JoinColumn(name = "user_id")
    private User user;

    //알람 타입 (like or comment)
    @Enumerated(EnumType.STRING)
    private AlarmType alarmType;

    //댓글이나 좋아요를 달은 userId
    private int fromUserId;

    //알람 발생한 postId
    private int targetId;

    private String text;

    public AlarmDto toResponse() {
        return AlarmDto.builder()
                .id(this.getId())
                .alarmType(this.getAlarmType())
                .fromUserId(this.getFromUserId())
                .targetId(this.getTargetId())
                .text(this.getAlarmType().getText())
                .createdAt(this.getCreatedAt())
                .build();
    }

    public static Alarm toEntity(Post post, User user, AlarmType alarmType) {
        return Alarm.builder()
                .user(post.getUser())
                .alarmType(alarmType)
                .fromUserId(user.getId())
                .targetId(post.getId())
                .build();
    }

}

Alarm Entity이다. 다른 Entity와 마찬가지로 BaseEntity를 extends해주었다.

 

AlarmType (Enum)

@AllArgsConstructor
@Getter
public enum AlarmType {

    NEW_COMMENT_ON_POST("new comment!"),
    NEW_LIKE_ON_POST("new like!");

    private String text;
}

Alarm Entity에서 사용하는 AlarmType이다.

Enum으로 구현하였다.

 

AlarmDto (DTO)

@AllArgsConstructor
@NoArgsConstructor
@Getter
@Builder
public class AlarmDto {

    private int id;
    private AlarmType alarmType;
    private int fromUserId;
    private int targetId;
    private String text;
    @JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "yyyy-MM-dd HH:mm:ss", timezone = "Asia/Seoul")
    private LocalDateTime createdAt;
}

AlarmService -> AlarmController로 return하기 위한 Dto이다.

 

2. Repository

AlarmRepository

public interface AlarmRepository extends JpaRepository<Alarm, Integer> {
    Page<Alarm> findAllByUser(User user, Pageable pageable);

}

해당 유저의 알람을 Page로 모두 불러오기 위한 findAllByUser method를 추가하였다.

 

3. Service

AlarmService

@Service
@RequiredArgsConstructor
@Slf4j
public class AlarmService {

    private final AlarmRepository alarmRepository;
    private final UserRepository userRepository;

    @Transactional
    public Page<AlarmDto> getAlarm(Pageable pageable, String userName) {

        User user = userRepository.findByUserName(userName)
                .orElseThrow(() -> new AppException(ErrorCode.NOT_FOUNDED_USER_NAME, ""));

        Page<Alarm> alarms = alarmRepository.findAllByUser(user, pageable);

        return alarms.map(Alarm::toResponse);

    }
}

알람목록을 불러오기 위한 비즈니스 로직이 담겨있다.

 

PostService

@Service
@RequiredArgsConstructor
@Slf4j
public class PostService {

    private final PostRepository postRepository;
    private final UserRepository userRepository;
    private final CommentRepository commentRepository;
    private final LikeRepository likeRepository;
    private final AlarmRepository alarmRepository;
    
    ...

    //댓글 기능
    @Transactional
    public CommentDto createComment(Integer postId, String userName, String comment) {
        Post post = postRepository.findById(postId)
                .orElseThrow(() -> new AppException(ErrorCode.POST_NOT_FOUND, ""));

        User user = userRepository.findByUserName(userName)
                .orElseThrow(() -> new AppException(ErrorCode.NOT_FOUNDED_USER_NAME, ""));

        Comment commentEntity = Comment.builder()
                .comment(comment)
                .user(user)
                .post(post)
                .build();

        commentRepository.save(commentEntity);

        //자신의 글에 다른 사람이 comment 등록 시 alarm 생성 (자기 자신 x)
        if (post.getUser().getId() != user.getId()) {
            Alarm alarm = Alarm.toEntity(post, user, AlarmType.NEW_COMMENT_ON_POST);
            alarmRepository.save(alarm);
        }

        return commentEntity.toResponse();

    }
    
    ...

    @Transactional
    public String doLike(Integer postId, String userName) {

        Post post = postRepository.findById(postId)
                .orElseThrow(() -> new AppException(ErrorCode.POST_NOT_FOUND, ""));

        User user = userRepository.findByUserName(userName)
                .orElseThrow(() -> new AppException(ErrorCode.NOT_FOUNDED_USER_NAME, ""));

        Optional<Like> like = likeRepository.findByUserAndPost(user, post);

        //좋아요가 이미 존재하고, deletedAt이 null일때 (삭제되지 않은 상태)
        if (like.isPresent() && like.get().getDeletedAt() == null) {
            likeRepository.delete(like.get());
            return "좋아요를 취소했습니다.";
        }

        //좋아요가 있지만, deletedAt이 null이 아닐 때 (삭제된 상태. 즉 취소된 상태)
        if (like.isPresent() && like.get().getDeletedAt() != null) {
            like.get().recoverLike(like.get()); //좋아요 복구 method
            likeRepository.saveAndFlush(like.get());
            return "좋아요를 눌렀습니다.";
        }

        //좋아요가 아예 없을 때 새로 생성
        likeRepository.save(Like.toEntity(user, post));

        //첫 좋아요 발생시에만 alarm 생성 + 다른 사람이 좋아요 처음 눌렀을 시 (자기 자신 x)
        if (post.getUser().getId() != user.getId()) {
            Alarm alarm = Alarm.toEntity(post, user, AlarmType.NEW_LIKE_ON_POST);
            alarmRepository.save(alarm);
        }

        return "좋아요를 눌렀습니다.";

    }

	...
    
}

새로운 댓글과 좋아요를 작성할 때 알람에 추가하는 로직을 추가하였다.

자신의 글에 다른사람이 댓글/좋아요를 누를 시에만 알람을 추가하도록 조건문을 추가하였고,

좋아요의 경우에는, 취소 후 재등록에는 알람이 생성되지 않도록 하였다.

 

4. Controller

AlarmController

@RestController
@RequestMapping("/api/v1/alarms")
@RequiredArgsConstructor
public class AlarmController {

    private final AlarmService alarmService;

    @GetMapping
    @ApiOperation(value = "유저 알람 목록 조회")
    public Response<Page<AlarmDto>> getAlarm(@PageableDefault(size=20, sort="createdAt", direction = Sort.Direction.DESC) Pageable pageable,
                                             @ApiIgnore Authentication authentication) {
        Page<AlarmDto> alarmDtos = alarmService.getAlarm(pageable, authentication.getName());

        return Response.success(alarmDtos);
    }
}

알람 목록을 조회하기 위한 Controller를 추가하였다.