Merge branch 'fix/notifications-controller-defensive' into 'main'
fix(notifications): defensive /me/notifications — wrap DB ops in try/catch See merge request 2-6/2-6-4/terravault/ordinis!195
This commit is contained in:
+44
-15
@@ -72,21 +72,48 @@ public class MeNotificationsController {
|
||||
String emailKey = currentUserEmail();
|
||||
int safeLimit = Math.min(limit, MAX_LIMIT);
|
||||
|
||||
Page<NotificationLog> page;
|
||||
if (emailKey != null && !emailKey.isBlank()) {
|
||||
page = notificationLogRepository.findByRecipientOrderBySentAtDesc(
|
||||
emailKey, PageRequest.of(0, safeLimit));
|
||||
} else {
|
||||
// No email in JWT — return empty, not an error
|
||||
// Hardened against runtime exceptions — endpoint hit от bell on каждой
|
||||
// странице; 500 здесь сломает global UX. Каждая под-операция wraped
|
||||
// в try/catch с structured log + sensible fallback (empty list / 0 count).
|
||||
// Backend can still see issues via logs; user sees empty drawer instead
|
||||
// of "Внутренняя ошибка сервера".
|
||||
|
||||
if (emailKey == null || emailKey.isBlank()) {
|
||||
// No email claim in JWT — can't look up notifications. Return empty.
|
||||
log.debug("No email claim in JWT for user sub={}, returning empty notifications", currentUserSub);
|
||||
return Map.of("items", List.of(), "totalUnread", 0);
|
||||
}
|
||||
|
||||
List<NotificationItemDto> items = page.getContent().stream()
|
||||
.filter(n -> status == null || status.equalsIgnoreCase(n.getStatus().name()))
|
||||
.map(n -> toDto(n, currentUserSub))
|
||||
.toList();
|
||||
List<NotificationItemDto> items;
|
||||
try {
|
||||
Page<NotificationLog> page = notificationLogRepository.findByRecipientOrderBySentAtDesc(
|
||||
emailKey, PageRequest.of(0, safeLimit));
|
||||
items = page.getContent().stream()
|
||||
.filter(n -> status == null || status.equalsIgnoreCase(n.getStatus().name()))
|
||||
.map(n -> {
|
||||
try {
|
||||
return toDto(n, currentUserSub);
|
||||
} catch (RuntimeException e) {
|
||||
log.warn("Failed to map NotificationLog id={} to DTO: {}", n.getId(), e.getMessage());
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(java.util.Objects::nonNull)
|
||||
.toList();
|
||||
} catch (RuntimeException e) {
|
||||
log.error("Failed to load notifications for user sub={}, email={}: {}",
|
||||
currentUserSub, emailKey, e.getMessage(), e);
|
||||
items = List.of();
|
||||
}
|
||||
|
||||
long totalUnread = countUnread(currentUserSub, emailKey);
|
||||
long totalUnread = 0L;
|
||||
try {
|
||||
totalUnread = countUnread(currentUserSub, emailKey);
|
||||
} catch (RuntimeException e) {
|
||||
log.error("Failed to count unread notifications for user sub={}: {}",
|
||||
currentUserSub, e.getMessage(), e);
|
||||
// Fallback to 0 — better to under-count than show user a 500.
|
||||
}
|
||||
|
||||
return Map.of(
|
||||
"items", items,
|
||||
@@ -118,7 +145,6 @@ public class MeNotificationsController {
|
||||
Page<NotificationLog> page = notificationLogRepository.findByRecipientOrderBySentAtDesc(
|
||||
emailKey, PageRequest.of(0, MAX_LIMIT));
|
||||
|
||||
OffsetDateTime now = OffsetDateTime.now();
|
||||
page.getContent().forEach(n -> markReadState(sub, n.getId()));
|
||||
log.debug("Marked {} notifications read for user {}", page.getNumberOfElements(), sub);
|
||||
}
|
||||
@@ -160,10 +186,13 @@ public class MeNotificationsController {
|
||||
}
|
||||
|
||||
private boolean isRead(String sub, Long notificationId) {
|
||||
Integer count = jdbcTemplate.queryForObject(
|
||||
// PostgreSQL COUNT(*) returns BIGINT — request Long.class to avoid
|
||||
// narrowing-conversion edge cases в Spring's RowMapper. Was Integer.class
|
||||
// which Spring auto-converts via rs.getInt() (works but type-imprecise).
|
||||
Long count = jdbcTemplate.queryForObject(
|
||||
"SELECT COUNT(*) FROM notification_read_state WHERE user_id = ? AND notification_id = ?",
|
||||
Integer.class, sub, notificationId);
|
||||
return count != null && count > 0;
|
||||
Long.class, sub, notificationId);
|
||||
return count != null && count > 0L;
|
||||
}
|
||||
|
||||
private static String buildPreview(NotificationLog n) {
|
||||
|
||||
Reference in New Issue
Block a user