본문 바로가기
스프링과 JPA 기반 웹 어플리케이션 개발/7부 알림

75. 알림 목록 조회 및 삭게

by Backchus 2020. 5. 12.

GET "/notifications"

  • 읽지 않은 알림 메시지만 보여준다.
  • 알림 메시지를 카테고리 별로 나눠서 Model에 담아주고 뷰에서 보여준다.
  • 모든 읽지 않은 알림 메시지를 읽은 메시지로 수정한다.

GET "/notifications/old"

  • 읽은 알림 메시지를 보여준다.

DELETE "/notifications"

  • 읽은 알림 메시지를 삭제한다.

 

GET "/notifications", "/notifications/old", DELETE "/notifications" 핸들러 작성

package me.weekbelt.studyolle.modules.notification;

@RequiredArgsConstructor
@Controller
public class NotificationsController {

    private final NotificationRepository notificationRepository;
    private final NotificationsService notificationsService;

    @GetMapping("/notifications")
    public String getNotifications(@CurrentAccount Account account, Model model) {
        List<Notification> notifications = notificationRepository
                .findByAccountAndCheckedOrderByCreatedLocalDateTimeDesc(account, false);
        long numberOfChecked = notificationRepository.countByAccountAndChecked(account, true);
        putCategorizedNotifications(model, notifications, numberOfChecked, notifications.size());
        model.addAttribute("isNew", true);
        notificationsService.markAsRead(notifications);
        return "notification/list";
    }


    @GetMapping("/notifications/old")
    public String getOldNotifications(@CurrentAccount Account account, Model model) {
        List<Notification> notifications = notificationRepository
                .findByAccountAndCheckedOrderByCreatedLocalDateTimeDesc(account, true);
        long numberOfNotChecked = notificationRepository.countByAccountAndChecked(account, false);
        putCategorizedNotifications(model, notifications, notifications.size(), numberOfNotChecked);
        model.addAttribute("isNew", false);
        return "notification/list";
    }

    @DeleteMapping("/notifications")
    public String deleteNotifications(@CurrentAccount Account account) {
        notificationRepository.deleteByAccountAndChecked(account, true);
        return "redirect:/notifications";
    }

    // 알림 종류를 분류하는 메소드
    private void putCategorizedNotifications(Model model, List<Notification> notifications,
                                             long numberOfChecked, long numberOfNotChecked) {
        List<Notification> newStudyNotifications = new ArrayList<>();           // 스터디 생성 관련
        List<Notification> eventEnrollmentNotifications = new ArrayList<>();    // 스터디 참가/신청 관련
        List<Notification> watchingStudyNotifications = new ArrayList<>();      // 스터디 수정사항 관련
        for (var notification : notifications) {
            switch (notification.getNotificationType()) {
                case STUDY_CREATED: newStudyNotifications.add(notification); break;
                case EVENT_ENROLLMENT: eventEnrollmentNotifications.add(notification); break;
                case STUDY_UPDATED: watchingStudyNotifications.add(notification); break;
            }
        }

        model.addAttribute("numberOfNotChecked", numberOfNotChecked);
        model.addAttribute("numberOfChecked", numberOfChecked);
        model.addAttribute("notifications", notifications);
        model.addAttribute("newStudyNotifications", newStudyNotifications);
        model.addAttribute("eventEnrollmentNotifications", eventEnrollmentNotifications);
        model.addAttribute("watchingStudyNotifications", watchingStudyNotifications);

    }
}

 

NotificationsRepository에 메소드 추가

package me.weekbelt.studyolle.modules.notification;

@Transactional(readOnly = true)
public interface NotificationRepository extends JpaRepository<Notification, Long> {

    // ........

    @Transactional
    List<Notification> findByAccountAndCheckedOrderByCreatedLocalDateTimeDesc(Account account, boolean checked);

    @Transactional
    void deleteByAccountAndChecked(Account account, boolean checked);
}

 

GET "/notifications" 요청으로 알림을 확인한 알림들을 확인처리하기 위해 NotificationService를 생성하여 markAsRead메소드를 추가한다.

package me.weekbelt.studyolle.modules.notification;

@RequiredArgsConstructor
@Transactional
@Service
public class NotificationsService {

    private final NotificationRepository notificationRepository;

    public void markAsRead(List<Notification> notifications) {
        notifications.forEach(n -> n.setChecked(true));
        notificationRepository.saveAll(notifications);
    }
}

 

알림 목록 조회 뷰인 notificiation/list.html를 생성

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head th:replace="fragments.html :: head"></head>
<body class="bg-light">
<nav th:replace="fragments.html :: main-nav"></nav>
<div class="container">
    <div class="row py-5 text-center">
        <div class="col-3">
            <ul class="list-group">
                <a href="#" th:href="@{/notifications}" th:classappend="${isNew}? active"
                   class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
                    읽지 않은 알림
                    <span th:text="${numberOfNotChecked}">3</span>
                </a>
                <a href="#" th:href="@{/notifications/old}" th:classappend="${!isNew}? active"
                   class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
                    읽은 알림
                    <span th:text="${numberOfChecked}">0</span>
                </a>
            </ul>

            <ul class="list-group mt-4">
                <a href="#" th:if="${newStudyNotifications.size() > 0}"
                   class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
                    새 스터디 알림
                    <span th:text="${newStudyNotifications.size()}">3</span>
                </a>
                <a href="#" th:if="${eventEnrollmentNotifications.size() > 0}"
                   class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
                    모임 참가 신청 알림
                    <span th:text="${eventEnrollmentNotifications.size()}">0</span>
                </a>
                <a href="#" th:if="${watchingStudyNotifications.size() > 0}"
                   class="list-group-item list-group-item-action d-flex justify-content-between align-items-center">
                    관심있는 스터디 알림
                    <span th:text="${watchingStudyNotifications.size()}">0</span>
                </a>
            </ul>

            <ul class="list-group mt-4" th:if="${numberOfChecked > 0}">
                <form th:action="@{/notifications}" th:method="delete">
                    <button type="submit" class="btn btn-block btn-outline-warning" aria-describedby="deleteHelp">
                        읽은 알림 삭제
                    </button>
                    <small id="deleteHelp">삭제하지 않아도 한달이 지난 알림은 사라집니다.</small>
                </form>
            </ul>
        </div>
        <div class="col-9">
            <div class="card" th:if="${notifications.size() == 0}">
                <div class="card-header">
                    알림 메시지가 없습니다.
                </div>
            </div>

            <div class="card" th:if="${newStudyNotifications.size() > 0}">
                <div class="card-header">
                    주요 활동 지역에 관심있는 주제의 스터디가 생겼습니다.
                </div>
                <div th:replace="fragments.html :: notification-list (notifications=${newStudyNotifications})"></div>
            </div>

            <div class="card mt-4" th:if="${eventEnrollmentNotifications.size() > 0}">
                <div class="card-header">
                    모임 참가 신청 관련 소식이 있습니다.
                </div>
                <div th:replace="fragments.html :: notification-list (notifications=${eventEnrollmentNotifications})"></div>
            </div>

            <div class="card mt-4" th:if="${watchingStudyNotifications.size() > 0}">
                <div class="card-header">
                    참여중인 스터디 관련 소식이 있습니다.
                </div>
                <div th:replace="fragments.html :: notification-list (notifications=${watchingStudyNotifications})"></div>
            </div>
        </div>
    </div>
    <div th:replace="fragments.html :: footer"></div>
</div>
<script th:replace="fragments.html :: date-time"></script>
</body>
</html>

 

fragments.html에 알림 메시지의 리스트 뷰를 담당하는 notifications-list fragment를 추가

<ul th:fragment="notification-list (notifications)" class="list-group list-group-flush">
    <a href="#" th:href="@{${noti.link}}" th:each="noti: ${notifications}"
       class="list-group-item list-group-item-action">
        <div class="d-flex w-100 justify-content-between">
            <small class="text-muted" th:text="${noti.title}">Noti title</small>
            <small class="fromNow text-muted" th:text="${noti.createdDateTime}">3 days ago</small>
        </div>
        <p th:text="${noti.message}" class="text-left mb-0 mt-1">message</p>
    </a>
</ul>

 

참고: https://www.inflearn.com/course/%EC%8A%A4%ED%94%84%EB%A7%81-JPA-%EC%9B%B9%EC%95%B1#

 

스프링과 JPA 기반 웹 애플리케이션 개발 - 인프런

이 강좌에서 여러분은 실제로 운영 중인 서비스를 스프링, JPA 그리고 타임리프를 비롯한 여러 자바 기반의 여러 오픈 소스 기술을 사용하여 웹 애플리케이션을 개발하는 과정을 학습할 수 있습�

www.inflearn.com