Skip to content

Commit 09923c6

Browse files
jueunk617namgigun
authored andcommitted
Feat: 에러 코드 추가 및 controller, service 코드 추가
1 parent d89057f commit 09923c6

File tree

3 files changed

+88
-18
lines changed

3 files changed

+88
-18
lines changed

src/main/java/com/back/domain/notification/controller/NotificationController.java

Lines changed: 55 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,36 @@ public class NotificationController {
3636
private final UserRepository userRepository;
3737
private final RoomRepository roomRepository;
3838

39-
@Operation(summary = "알림 전송", description = "USER/ROOM/COMMUNITY/SYSTEM 타입별 알림 생성 및 전송")
39+
@Operation(
40+
summary = "알림 전송",
41+
description = "USER/ROOM/COMMUNITY/SYSTEM 타입별 알림 생성 및 전송\n\n" +
42+
"- USER: 개인 알림 (actorId, targetId 필수)\n" +
43+
"- ROOM: 스터디룸 알림 (actorId, targetId(roomId) 필수)\n" +
44+
"- COMMUNITY: 커뮤니티 알림 (actorId, targetId 필수)\n" +
45+
"- SYSTEM: 시스템 전체 알림 (actorId, targetId 불필요)"
46+
)
4047
@PostMapping
4148
public ResponseEntity<RsData<NotificationResponse>> createNotification(
4249
@Parameter(hidden = true) @AuthenticationPrincipal CustomUserDetails userDetails,
4350
@RequestBody NotificationCreateRequest request) {
4451

4552
log.info("알림 전송 요청 - 타입: {}, 제목: {}", request.targetType(), request.title());
4653

54+
// targetType 검증
55+
if (!isValidTargetType(request.targetType())) {
56+
throw new CustomException(ErrorCode.NOTIFICATION_INVALID_TARGET_TYPE);
57+
}
58+
59+
// SYSTEM이 아닌 경우 필수 필드 검증
60+
if (!"SYSTEM".equals(request.targetType())) {
61+
if (request.actorId() == null) {
62+
throw new CustomException(ErrorCode.NOTIFICATION_MISSING_ACTOR);
63+
}
64+
if (request.targetId() == null) {
65+
throw new CustomException(ErrorCode.NOTIFICATION_MISSING_TARGET);
66+
}
67+
}
68+
4769
Notification notification = switch (request.targetType()) {
4870
case "USER" -> {
4971
// 수신자 조회
@@ -97,22 +119,27 @@ public ResponseEntity<RsData<NotificationResponse>> createNotification(
97119
);
98120
}
99121
case "SYSTEM" -> {
100-
// 시스템 알림은 발신자 없음
122+
// 시스템 알림은 발신자/수신자 없음
101123
yield notificationService.createSystemNotification(
102124
request.title(),
103125
request.message(),
104126
request.redirectUrl()
105127
);
106128
}
107-
default -> throw new IllegalArgumentException("유효하지 않은 알림 타입입니다: " + request.targetType());
129+
default -> throw new CustomException(ErrorCode.NOTIFICATION_INVALID_TARGET_TYPE);
108130
};
109131

110132
NotificationResponse response = NotificationResponse.from(notification);
111133

112134
return ResponseEntity.ok(RsData.success("알림 전송 성공", response));
113135
}
114136

115-
@Operation(summary = "알림 목록 조회", description = "사용자의 알림 목록 조회 (페이징)")
137+
@Operation(
138+
summary = "알림 목록 조회",
139+
description = "사용자의 알림 목록 조회 (페이징)\n\n" +
140+
"- unreadOnly=true: 읽지 않은 알림만\n" +
141+
"- unreadOnly=false: 모든 알림"
142+
)
116143
@GetMapping
117144
public ResponseEntity<RsData<NotificationListResponse>> getNotifications(
118145
@Parameter(hidden = true) @AuthenticationPrincipal CustomUserDetails userDetails,
@@ -143,7 +170,11 @@ public ResponseEntity<RsData<NotificationListResponse>> getNotifications(
143170
return ResponseEntity.ok(RsData.success("알림 목록 조회 성공", response));
144171
}
145172

146-
@Operation(summary = "알림 읽음 처리", description = "특정 알림을 읽음 상태로 변경")
173+
@Operation(
174+
summary = "알림 읽음 처리",
175+
description = "특정 알림을 읽음 상태로 변경\n\n" +
176+
"이미 읽은 알림일 경우 NOTIFICATION_ALREADY_READ 에러 반환"
177+
)
147178
@PutMapping("/{notificationId}/read")
148179
public ResponseEntity<RsData<NotificationResponse>> markAsRead(
149180
@Parameter(hidden = true) @AuthenticationPrincipal CustomUserDetails userDetails,
@@ -157,12 +188,19 @@ public ResponseEntity<RsData<NotificationResponse>> markAsRead(
157188
notificationService.markAsRead(notificationId, user);
158189

159190
Notification notification = notificationService.getNotification(notificationId);
160-
NotificationResponse response = NotificationResponse.from(notification);
191+
boolean isRead = notificationService.isNotificationRead(notificationId, user.getId());
192+
193+
// readAt 조회 (NotificationRead에서)
194+
NotificationResponse response = NotificationResponse.from(notification, isRead,
195+
isRead ? java.time.LocalDateTime.now() : null);
161196

162197
return ResponseEntity.ok(RsData.success("알림 읽음 처리 성공", response));
163198
}
164199

165-
@Operation(summary = "모든 알림 읽음 처리", description = "사용자의 읽지 않은 모든 알림을 읽음 상태로 변경")
200+
@Operation(
201+
summary = "모든 알림 읽음 처리",
202+
description = "사용자의 읽지 않은 모든 알림을 읽음 상태로 변경"
203+
)
166204
@PutMapping("/read-all")
167205
public ResponseEntity<RsData<Void>> markAllAsRead(
168206
@Parameter(hidden = true) @AuthenticationPrincipal CustomUserDetails userDetails) {
@@ -176,4 +214,14 @@ public ResponseEntity<RsData<Void>> markAllAsRead(
176214

177215
return ResponseEntity.ok(RsData.success("전체 알림 읽음 처리 성공"));
178216
}
217+
218+
// ==================== 헬퍼 메서드 ====================
219+
220+
private boolean isValidTargetType(String targetType) {
221+
return targetType != null &&
222+
(targetType.equals("USER") ||
223+
targetType.equals("ROOM") ||
224+
targetType.equals("COMMUNITY") ||
225+
targetType.equals("SYSTEM"));
226+
}
179227
}

src/main/java/com/back/domain/notification/service/NotificationService.java

Lines changed: 30 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,17 @@ public class NotificationService {
2626
private final NotificationReadRepository notificationReadRepository;
2727
private final NotificationWebSocketService webSocketService;
2828

29+
// ==================== 검증 메서드 ====================
30+
31+
// 발신자와 수신자 검증
32+
private void validateActorAndReceiver(User receiver, User actor) {
33+
if (receiver.getId().equals(actor.getId())) {
34+
log.debug("자기 자신에게 알림 전송 시도 - receiver: {}, actor: {}",
35+
receiver.getId(), actor.getId());
36+
throw new CustomException(ErrorCode.NOTIFICATION_FORBIDDEN);
37+
}
38+
}
39+
2940
// ==================== 알림 생성 및 전송 ====================
3041

3142
// 개인 알림 생성 및 전송
@@ -37,6 +48,9 @@ public Notification createPersonalNotification(
3748
String content,
3849
String targetUrl) {
3950

51+
// 자기 자신에게 알림 방지
52+
validateActorAndReceiver(receiver, actor);
53+
4054
// DB에 알림 저장
4155
Notification notification = Notification.createPersonalNotification(
4256
receiver, actor, title, content, targetUrl);
@@ -72,7 +86,7 @@ public Notification createRoomNotification(
7286
return notification;
7387
}
7488

75-
// 시스템 전체 알림 생성 및 브로드캐스트 (발신자 없음)
89+
// 시스템 전체 알림 생성 및 브로드캐스트
7690
@Transactional
7791
public Notification createSystemNotification(String title, String content, String targetUrl) {
7892

@@ -95,6 +109,9 @@ public Notification createCommunityNotification(
95109
String content,
96110
String targetUrl) {
97111

112+
// 검증: 자기 자신에게 알림 방지
113+
validateActorAndReceiver(receiver, actor);
114+
98115
Notification notification = Notification.createCommunityNotification(
99116
receiver, actor, title, content, targetUrl);
100117
notificationRepository.save(notification);
@@ -109,28 +126,23 @@ public Notification createCommunityNotification(
109126

110127
// ==================== 알림 조회 ====================
111128

112-
// 특정 유저의 알림 목록 조회 (개인 알림 + 시스템 알림)
113129
public Page<Notification> getUserNotifications(Long userId, Pageable pageable) {
114130
return notificationRepository.findByUserIdOrSystemType(userId, pageable);
115131
}
116132

117-
// 특정 유저의 읽지 않은 알림 목록 조회
118133
public Page<Notification> getUnreadNotifications(Long userId, Pageable pageable) {
119134
return notificationRepository.findUnreadByUserId(userId, pageable);
120135
}
121136

122-
// 특정 유저의 읽지 않은 알림 개수 조회
123137
public long getUnreadCount(Long userId) {
124138
return notificationRepository.countUnreadByUserId(userId);
125139
}
126140

127-
// 알림 단건 조회
128141
public Notification getNotification(Long notificationId) {
129142
return notificationRepository.findById(notificationId)
130143
.orElseThrow(() -> new CustomException(ErrorCode.NOTIFICATION_NOT_FOUND));
131144
}
132145

133-
// 특정 유저가 특정 알림을 읽었는지 확인
134146
public boolean isNotificationRead(Long notificationId, Long userId) {
135147
return notificationReadRepository.existsByNotificationIdAndUserId(notificationId, userId);
136148
}
@@ -140,16 +152,21 @@ public boolean isNotificationRead(Long notificationId, Long userId) {
140152
// 알림 읽음 처리
141153
@Transactional
142154
public void markAsRead(Long notificationId, User user) {
143-
// 1. 알림 존재 확인
155+
// 알림 존재 확인
144156
Notification notification = getNotification(notificationId);
145157

146-
// 2. 이미 읽은 알림인지 확인
158+
// 알림 접근 권한 확인
159+
if (!notification.isVisibleToUser(user.getId())) {
160+
throw new CustomException(ErrorCode.NOTIFICATION_FORBIDDEN);
161+
}
162+
163+
// 이미 읽은 알림인지 확인
147164
if (notificationReadRepository.existsByNotificationIdAndUserId(notificationId, user.getId())) {
148165
log.debug("이미 읽은 알림 - 알림 ID: {}, 유저 ID: {}", notificationId, user.getId());
149-
return;
166+
throw new CustomException(ErrorCode.NOTIFICATION_ALREADY_READ);
150167
}
151168

152-
// 3. 읽음 기록 생성
169+
// 읽음 기록 생성
153170
NotificationRead notificationRead = NotificationRead.create(notification, user);
154171
notificationReadRepository.save(notificationRead);
155172

@@ -161,13 +178,15 @@ public void markAsRead(Long notificationId, User user) {
161178
public void markMultipleAsRead(Long userId, User user) {
162179
Page<Notification> unreadNotifications = getUnreadNotifications(userId, Pageable.unpaged());
163180

181+
int count = 0;
164182
for (Notification notification : unreadNotifications) {
165183
if (!notificationReadRepository.existsByNotificationIdAndUserId(notification.getId(), user.getId())) {
166184
NotificationRead notificationRead = NotificationRead.create(notification, user);
167185
notificationReadRepository.save(notificationRead);
186+
count++;
168187
}
169188
}
170189

171-
log.info("일괄 읽음 처리 - 유저 ID: {}, 처리 개수: {}", userId, unreadNotifications.getTotalElements());
190+
log.info("일괄 읽음 처리 - 유저 ID: {}, 처리 개수: {}", userId, count);
172191
}
173192
}

src/main/java/com/back/global/exception/ErrorCode.java

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,9 @@ public enum ErrorCode {
6363
NOTIFICATION_NOT_FOUND(HttpStatus.NOT_FOUND, "NOTIFICATION_001", "존재하지 않는 알림입니다."),
6464
NOTIFICATION_FORBIDDEN(HttpStatus.FORBIDDEN, "NOTIFICATION_002", "알림에 대한 접근 권한이 없습니다."),
6565
NOTIFICATION_ALREADY_READ(HttpStatus.BAD_REQUEST, "NOTIFICATION_003", "이미 읽은 알림입니다."),
66+
NOTIFICATION_INVALID_TARGET_TYPE(HttpStatus.BAD_REQUEST, "NOTIFICATION_004", "유효하지 않은 알림 타입입니다."),
67+
NOTIFICATION_MISSING_ACTOR(HttpStatus.BAD_REQUEST, "NOTIFICATION_005", "발신자 정보가 필요합니다."),
68+
NOTIFICATION_MISSING_TARGET(HttpStatus.BAD_REQUEST, "NOTIFICATION_006", "수신자 또는 대상 정보가 필요합니다."),
6669

6770
// ======================== 메시지 관련 ========================
6871
MESSAGE_NOT_FOUND(HttpStatus.NOT_FOUND, "MESSAGE_001", "존재하지 않는 메시지입니다."),

0 commit comments

Comments
 (0)