Skip to content

Commit 13af9c4

Browse files
authored
Merge pull request #213 from prgrms-web-devcourse-final-project/Feat/207
Feat: 알림 설정 기능 구현 (#207)
2 parents a234851 + 6d4ea43 commit 13af9c4

21 files changed

+1409
-137
lines changed

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

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
import com.back.domain.notification.dto.NotificationResponse;
55
import com.back.domain.notification.dto.NotificationListResponse;
66
import com.back.domain.notification.entity.Notification;
7+
import com.back.domain.notification.entity.NotificationSettingType;
78
import com.back.domain.notification.service.NotificationService;
89
import com.back.domain.studyroom.entity.Room;
910
import com.back.domain.studyroom.repository.RoomRepository;
@@ -76,12 +77,14 @@ public ResponseEntity<RsData<NotificationResponse>> createNotification(
7677
User actor = userRepository.findById(request.actorId())
7778
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
7879

80+
// 개인 알림 생성
7981
yield notificationService.createPersonalNotification(
8082
receiver,
8183
actor,
8284
request.title(),
8385
request.message(),
84-
request.redirectUrl()
86+
request.redirectUrl(),
87+
NotificationSettingType.SYSTEM
8588
);
8689
}
8790
case "ROOM" -> {
@@ -93,12 +96,14 @@ public ResponseEntity<RsData<NotificationResponse>> createNotification(
9396
User actor = userRepository.findById(request.actorId())
9497
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
9598

99+
// 스터디룸 알림 생성
96100
yield notificationService.createRoomNotification(
97101
room,
98102
actor,
99103
request.title(),
100104
request.message(),
101-
request.redirectUrl()
105+
request.redirectUrl(),
106+
NotificationSettingType.ROOM_NOTICE
102107
);
103108
}
104109
case "COMMUNITY" -> {
@@ -110,12 +115,14 @@ public ResponseEntity<RsData<NotificationResponse>> createNotification(
110115
User actor = userRepository.findById(request.actorId())
111116
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
112117

118+
// 커뮤니티 알림 생성
113119
yield notificationService.createCommunityNotification(
114120
receiver,
115121
actor,
116122
request.title(),
117123
request.message(),
118-
request.redirectUrl()
124+
request.redirectUrl(),
125+
NotificationSettingType.POST_COMMENT
119126
);
120127
}
121128
case "SYSTEM" -> {
Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
package com.back.domain.notification.controller;
2+
3+
import com.back.domain.notification.dto.NotificationSettingDto.*;
4+
import com.back.domain.notification.entity.NotificationSettingType;
5+
import com.back.domain.notification.service.NotificationSettingService;
6+
import com.back.global.common.dto.RsData;
7+
import com.back.global.security.user.CurrentUser;
8+
import io.swagger.v3.oas.annotations.Operation;
9+
import io.swagger.v3.oas.annotations.Parameter;
10+
import io.swagger.v3.oas.annotations.tags.Tag;
11+
import lombok.RequiredArgsConstructor;
12+
import org.springframework.http.ResponseEntity;
13+
import org.springframework.web.bind.annotation.*;
14+
15+
@RestController
16+
@RequiredArgsConstructor
17+
@RequestMapping("/api/users/me/notification-settings")
18+
@Tag(name = "Notification Setting API", description = "알림 설정 API")
19+
public class NotificationSettingController {
20+
21+
private final NotificationSettingService settingService;
22+
private final CurrentUser currentUser;
23+
24+
@GetMapping
25+
@Operation(summary = "알림 설정 조회", description = "사용자의 모든 알림 설정을 조회합니다.")
26+
public ResponseEntity<RsData<SettingsResponse>> getSettings() {
27+
28+
SettingsResponse response = settingService.getUserSettings(currentUser.getUserId());
29+
30+
return ResponseEntity.ok(RsData.success("알림 설정 조회 성공", response));
31+
}
32+
33+
@PutMapping("/{type}")
34+
@Operation(summary = "개별 알림 설정 토글", description = "특정 알림 타입의 활성화 상태를 토글합니다.")
35+
public ResponseEntity<RsData<Void>> toggleSetting(
36+
@Parameter(description = "알림 타입", example = "ROOM_JOIN")
37+
@PathVariable NotificationSettingType type) {
38+
39+
settingService.toggleSetting(currentUser.getUserId(), type);
40+
41+
return ResponseEntity.ok(RsData.success(null));
42+
}
43+
44+
@PutMapping("/all")
45+
@Operation(summary = "전체 알림 ON/OFF", description = "모든 알림을 한 번에 활성화하거나 비활성화합니다.")
46+
public ResponseEntity<RsData<Void>> toggleAllSettings(
47+
@Parameter(description = "활성화 여부", example = "true")
48+
@RequestParam boolean enable) {
49+
50+
settingService.toggleAllSettings(currentUser.getUserId(), enable);
51+
52+
return ResponseEntity.ok(RsData.success(null));
53+
}
54+
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.back.domain.notification.dto;
2+
3+
import com.back.domain.notification.entity.NotificationSetting;
4+
import com.back.domain.notification.entity.NotificationSettingType;
5+
import io.swagger.v3.oas.annotations.media.Schema;
6+
7+
import java.util.List;
8+
import java.util.Map;
9+
10+
public class NotificationSettingDto {
11+
12+
// 응답 DTO - 단일 설정
13+
@Schema(description = "알림 설정 정보")
14+
public record SettingInfo(
15+
@Schema(description = "알림 타입", example = "ROOM_JOIN")
16+
NotificationSettingType type,
17+
18+
@Schema(description = "활성화 여부", example = "true")
19+
boolean enabled
20+
) {
21+
public static SettingInfo from(NotificationSetting setting) {
22+
return new SettingInfo(
23+
setting.getType(),
24+
setting.isEnabled()
25+
);
26+
}
27+
}
28+
29+
// 응답 DTO - 전체 설정
30+
@Schema(description = "사용자 알림 설정 전체 응답")
31+
public record SettingsResponse(
32+
@Schema(description = "전체 알림 켜기/끄기", example = "true")
33+
boolean allEnabled,
34+
35+
@Schema(description = "개별 알림 설정 목록")
36+
List<SettingInfo> settings
37+
) {
38+
public static SettingsResponse of(boolean allEnabled, List<NotificationSetting> settings) {
39+
return new SettingsResponse(
40+
allEnabled,
41+
settings.stream()
42+
.map(SettingInfo::from)
43+
.toList()
44+
);
45+
}
46+
}
47+
48+
// 요청 DTO - 일괄 변경
49+
@Schema(description = "알림 설정 일괄 변경 요청")
50+
public record UpdateSettingsRequest(
51+
@Schema(description = "알림 타입별 활성화 여부",
52+
example = "{\"ROOM_JOIN\": true, \"POST_COMMENT\": false}")
53+
Map<NotificationSettingType, Boolean> settings
54+
) {}
55+
56+
// 요청 DTO - 전체 ON/OFF
57+
@Schema(description = "전체 알림 ON/OFF 요청")
58+
public record ToggleAllRequest(
59+
@Schema(description = "전체 활성화 여부", example = "true")
60+
boolean enable
61+
) {}
62+
}

src/main/java/com/back/domain/notification/event/community/CommunityNotificationEventListener.java

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.back.domain.notification.event.community;
22

3+
import com.back.domain.notification.entity.NotificationSettingType;
34
import com.back.domain.notification.service.NotificationService;
45
import com.back.domain.user.entity.User;
56
import com.back.domain.user.repository.UserRepository;
@@ -27,7 +28,6 @@ public void handleCommentCreated(CommentCreatedEvent event) {
2728
event.getPostId(), event.getCommentId(), event.getActorId());
2829

2930
try {
30-
3131
User actor = userRepository.findById(event.getActorId())
3232
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
3333

@@ -39,7 +39,8 @@ public void handleCommentCreated(CommentCreatedEvent event) {
3939
actor,
4040
event.getTitle(),
4141
event.getContent(),
42-
"/posts/" + event.getPostId()
42+
"/posts/" + event.getPostId(),
43+
NotificationSettingType.POST_COMMENT
4344
);
4445

4546
log.info("[알림] 댓글 작성 알림 전송 완료");
@@ -57,7 +58,6 @@ public void handleReplyCreated(ReplyCreatedEvent event) {
5758
event.getParentCommentId(), event.getReplyId(), event.getActorId());
5859

5960
try {
60-
6161
User actor = userRepository.findById(event.getActorId())
6262
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
6363

@@ -69,7 +69,8 @@ public void handleReplyCreated(ReplyCreatedEvent event) {
6969
actor,
7070
event.getTitle(),
7171
event.getContent(),
72-
"/posts/" + event.getPostId() + "#comment-" + event.getParentCommentId()
72+
"/posts/" + event.getPostId() + "#comment-" + event.getParentCommentId(),
73+
NotificationSettingType.POST_COMMENT
7374
);
7475

7576
log.info("[알림] 대댓글 작성 알림 전송 완료");
@@ -87,7 +88,6 @@ public void handlePostLiked(PostLikedEvent event) {
8788
event.getPostId(), event.getActorId());
8889

8990
try {
90-
9191
User actor = userRepository.findById(event.getActorId())
9292
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
9393

@@ -99,7 +99,8 @@ public void handlePostLiked(PostLikedEvent event) {
9999
actor,
100100
event.getTitle(),
101101
event.getContent(),
102-
"/posts/" + event.getPostId()
102+
"/posts/" + event.getPostId(),
103+
NotificationSettingType.POST_LIKE
103104
);
104105

105106
log.info("[알림] 게시글 좋아요 알림 전송 완료");
@@ -117,7 +118,6 @@ public void handleCommentLiked(CommentLikedEvent event) {
117118
event.getCommentId(), event.getActorId());
118119

119120
try {
120-
121121
User actor = userRepository.findById(event.getActorId())
122122
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
123123

@@ -129,7 +129,8 @@ public void handleCommentLiked(CommentLikedEvent event) {
129129
actor,
130130
event.getTitle(),
131131
event.getContent(),
132-
"/posts/" + event.getPostId() + "#comment-" + event.getCommentId()
132+
"/posts/" + event.getPostId() + "#comment-" + event.getCommentId(),
133+
NotificationSettingType.POST_LIKE
133134
);
134135

135136
log.info("[알림] 댓글 좋아요 알림 전송 완료");

src/main/java/com/back/domain/notification/event/study/StudyNotificationEventListener.java

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.back.domain.notification.event.study;
22

3+
import com.back.domain.notification.entity.NotificationSettingType;
34
import com.back.domain.notification.service.NotificationService;
45
import com.back.domain.user.entity.User;
56
import com.back.domain.user.repository.UserRepository;
@@ -30,13 +31,12 @@ public void handleStudyRecordCreated(StudyRecordCreatedEvent event) {
3031
User user = userRepository.findById(event.getUserId())
3132
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
3233

33-
// 본인에게 개인 알림
34-
notificationService.createPersonalNotification(
35-
user, // receiver (본인)
36-
user, // actor (본인)
34+
notificationService.createSelfNotification(
35+
user,
3736
event.getTitle(),
3837
event.getContent(),
39-
"/study/records/" + event.getStudyRecordId()
38+
"/study/records/" + event.getStudyRecordId(),
39+
NotificationSettingType.SYSTEM
4040
);
4141

4242
log.info("[알림] 학습 기록 알림 전송 완료");
@@ -58,12 +58,12 @@ public void handleDailyGoalAchieved(DailyGoalAchievedEvent event) {
5858
User user = userRepository.findById(event.getUserId())
5959
.orElseThrow(() -> new CustomException(ErrorCode.USER_NOT_FOUND));
6060

61-
notificationService.createPersonalNotification(
62-
user,
61+
notificationService.createSelfNotification(
6362
user,
6463
event.getTitle(),
6564
event.getContent(),
66-
"/study/plans?date=" + event.getAchievedDate()
65+
"/study/plans?date=" + event.getAchievedDate(),
66+
NotificationSettingType.SYSTEM
6767
);
6868

6969
log.info("[알림] 일일 목표 달성 알림 전송 완료");

src/main/java/com/back/domain/notification/event/studyroom/StudyRoomNotificationEventListener.java

Lines changed: 10 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.back.domain.notification.event.studyroom;
22

3+
import com.back.domain.notification.entity.NotificationSettingType;
34
import com.back.domain.notification.service.NotificationService;
45
import com.back.domain.studyroom.entity.Room;
56
import com.back.domain.studyroom.repository.RoomMemberRepository;
@@ -45,8 +46,9 @@ public void handleNoticeCreated(StudyRoomNoticeCreatedEvent event) {
4546
room,
4647
actor,
4748
event.getTitle(),
48-
event.getNoticeTitle(), // content에 공지 제목
49-
"/rooms/" + event.getStudyRoomId() + "/notices"
49+
event.getNoticeTitle(),
50+
"/rooms/" + event.getStudyRoomId() + "/notices",
51+
NotificationSettingType.ROOM_NOTICE
5052
);
5153

5254
log.info("[알림] 스터디룸 공지사항 알림 전송 완료");
@@ -79,7 +81,8 @@ public void handleMemberRoleChanged(MemberRoleChangedEvent event) {
7981
actor,
8082
event.getTitle(),
8183
event.getContent(),
82-
"/rooms/" + event.getStudyRoomId()
84+
"/rooms/" + event.getStudyRoomId(),
85+
NotificationSettingType.ROOM_JOIN
8386
);
8487

8588
log.info("[알림] 권한 변경 알림 전송 완료");
@@ -108,7 +111,8 @@ public void handleMemberKicked(MemberKickedEvent event) {
108111
actor,
109112
event.getTitle(),
110113
event.getContent(),
111-
"/rooms"
114+
"/rooms",
115+
NotificationSettingType.ROOM_JOIN
112116
);
113117

114118
log.info("[알림] 멤버 추방 알림 전송 완료");
@@ -137,7 +141,8 @@ public void handleOwnerTransferred(OwnerTransferredEvent event) {
137141
actor,
138142
event.getTitle(),
139143
event.getContent(),
140-
"/rooms/" + event.getStudyRoomId()
144+
"/rooms/" + event.getStudyRoomId(),
145+
NotificationSettingType.ROOM_JOIN
141146
);
142147

143148
log.info("[알림] 방장 위임 알림 전송 완료");
Lines changed: 3 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,9 @@
11
package com.back.domain.notification.repository;
22

33
import com.back.domain.notification.entity.NotificationSetting;
4-
import com.back.domain.notification.entity.NotificationSettingType;
54
import org.springframework.data.jpa.repository.JpaRepository;
5+
import org.springframework.stereotype.Repository;
66

7-
import java.util.List;
8-
import java.util.Optional;
9-
10-
public interface NotificationSettingRepository extends JpaRepository<NotificationSetting, Long> {
11-
12-
// 특정 유저의 모든 알림 설정 조회
13-
List<NotificationSetting> findByUserId(Long userId);
14-
15-
// 특정 유저의 특정 타입 알림 설정 조회
16-
Optional<NotificationSetting> findByUserIdAndType(Long userId, NotificationSettingType type);
17-
18-
// 특정 유저의 특정 타입 알림 설정 존재 여부
19-
boolean existsByUserIdAndType(Long userId, NotificationSettingType type);
20-
21-
// 특정 유저의 활성화된 알림 설정만 조회
22-
List<NotificationSetting> findByUserIdAndEnabledTrue(Long userId);
7+
@Repository
8+
public interface NotificationSettingRepository extends JpaRepository<NotificationSetting, Long>, NotificationSettingRepositoryCustom {
239
}
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
package com.back.domain.notification.repository;
2+
3+
import com.back.domain.notification.entity.NotificationSetting;
4+
import com.back.domain.notification.entity.NotificationSettingType;
5+
6+
import java.util.List;
7+
import java.util.Optional;
8+
9+
public interface NotificationSettingRepositoryCustom {
10+
11+
// 특정 사용자의 모든 알림 설정 조회
12+
List<NotificationSetting> findAllByUserId(Long userId);
13+
14+
// 특정 사용자의 특정 타입 알림 설정 조회
15+
Optional<NotificationSetting> findByUserIdAndType(Long userId, NotificationSettingType type);
16+
17+
// 특정 사용자의 설정이 존재하는지 확인
18+
boolean existsByUserId(Long userId);
19+
20+
// 특정 사용자의 활성화된 알림 설정만 조회
21+
List<NotificationSetting> findEnabledSettingsByUserId(Long userId);
22+
}

0 commit comments

Comments
 (0)