Skip to content

Commit 68ef5da

Browse files
authored
Merge pull request #176 from GoToBILL/feature/alert
feat: 읽지 않은 활동 알림과 키워드 알림의 개수를 반환하는 API를 만들었습니다.
2 parents c9a16d8 + 9db0c91 commit 68ef5da

File tree

13 files changed

+415
-60
lines changed

13 files changed

+415
-60
lines changed

src/main/java/com/example/cherrydan/activity/controller/ActivityController.java

Lines changed: 12 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
import org.springframework.data.domain.Pageable;
1515
import org.springframework.data.domain.Sort;
1616
import org.springframework.data.web.PageableDefault;
17+
import org.springframework.http.ResponseEntity;
1718
import org.springframework.security.core.annotation.AuthenticationPrincipal;
1819
import org.springframework.web.bind.annotation.*;
1920

@@ -36,62 +37,62 @@ public class ActivityController {
3637
description = """
3738
사용자의 북마크 기반 활동 알림 목록을 조회합니다.
3839
북마크한 캠페인의 신청 마감이 3일 남았을 때 생성되는 알림들입니다.
39-
40+
4041
**쿼리 파라미터 예시:**
4142
- ?page=0&size=20&sort=alertDate,desc
4243
- ?page=1&size=10&sort=alertDate,asc
43-
44+
4445
**정렬 가능한 필드:**
4546
- alertDate: 알림 생성 날짜 (기본값, DESC)
46-
47+
4748
**주의:** 이는 Request Body가 아닌 **Query Parameter**입니다.
4849
"""
4950
)
5051
@GetMapping("/bookmark-alerts")
51-
public ApiResponse<PageListResponseDTO<ActivityAlertResponseDTO>> getBookmarkActivityAlerts(
52+
public ResponseEntity<ApiResponse<PageListResponseDTO<ActivityAlertResponseDTO>>> getBookmarkActivityAlerts(
5253
@Parameter(hidden = true) @AuthenticationPrincipal UserDetailsImpl currentUser,
5354
@PageableDefault(size = 20, sort = "alertDate", direction = Sort.Direction.DESC) Pageable pageable
5455
) {
5556
Page<ActivityAlertResponseDTO> alerts = activityAlertService.getUserActivityAlerts(currentUser.getId(), pageable);
5657
PageListResponseDTO<ActivityAlertResponseDTO> response = PageListResponseDTO.from(alerts);
57-
return ApiResponse.success("북마크 활동 알림 목록 조회 성공", response);
58+
return ResponseEntity.ok(ApiResponse.success("북마크 활동 알림 목록 조회 성공", response));
5859
}
5960

6061
@Operation(
6162
summary = "북마크 기반 활동 알림 개수 조회",
6263
description = "사용자의 북마크 기반 활동 알림 개수를 조회합니다."
6364
)
6465
@GetMapping("/bookmark-alerts/count")
65-
public ApiResponse<Long> getBookmarkActivityAlertsCount(
66+
public ResponseEntity<ApiResponse<Long>> getBookmarkActivityAlertsCount(
6667
@Parameter(hidden = true) @AuthenticationPrincipal UserDetailsImpl currentUser
6768
) {
6869
Long alertsCount = activityAlertService.getUserActivityAlertsCount(currentUser.getId());
69-
return ApiResponse.success("북마크 활동 알림 개수 조회 성공", alertsCount);
70+
return ResponseEntity.ok(ApiResponse.success("북마크 활동 알림 개수 조회 성공", alertsCount));
7071
}
7172

7273
@Operation(
7374
summary = "북마크 기반 활동 알림 삭제",
7475
description = "선택한 북마크 기반 활동 알림들을 삭제합니다. 본인의 알림이 아닌 경우 403 에러를 반환합니다."
7576
)
7677
@DeleteMapping("/bookmark-alerts")
77-
public ApiResponse<Void> deleteBookmarkActivityAlerts(
78+
public ResponseEntity<ApiResponse<Void>> deleteBookmarkActivityAlerts(
7879
@Parameter(hidden = true) @AuthenticationPrincipal UserDetailsImpl currentUser,
7980
@RequestBody AlertIdsRequestDTO request
8081
) {
8182
activityAlertService.deleteActivityAlert(currentUser.getId(), request.getAlertIds());
82-
return ApiResponse.success("북마크 활동 알림 삭제 성공", null);
83+
return ResponseEntity.ok(ApiResponse.success("북마크 활동 알림 삭제 성공", null));
8384
}
8485

8586
@Operation(
8687
summary = "북마크 기반 활동 알림 읽음 처리",
8788
description = "선택한 북마크 기반 활동 알림들을 읽음 상태로 변경합니다. 본인의 알림이 아닌 경우 403 에러를 반환합니다."
8889
)
8990
@PatchMapping("/bookmark-alerts/read")
90-
public ApiResponse<Void> markBookmarkActivityAlertsAsRead(
91+
public ResponseEntity<ApiResponse<Void>> markBookmarkActivityAlertsAsRead(
9192
@Parameter(hidden = true) @AuthenticationPrincipal UserDetailsImpl currentUser,
9293
@RequestBody AlertIdsRequestDTO request
9394
) {
9495
activityAlertService.markActivityAlertsAsRead(currentUser.getId(), request.getAlertIds());
95-
return ApiResponse.success("북마크 활동 알림 읽음 처리 성공", null);
96+
return ResponseEntity.ok(ApiResponse.success("북마크 활동 알림 읽음 처리 성공", null));
9697
}
9798
}
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
package com.example.cherrydan.activity.controller;
2+
3+
import com.example.cherrydan.activity.dto.UnreadAlertCountResponseDTO;
4+
import com.example.cherrydan.activity.service.AlertService;
5+
import com.example.cherrydan.common.response.ApiResponse;
6+
import com.example.cherrydan.oauth.security.jwt.UserDetailsImpl;
7+
import io.swagger.v3.oas.annotations.Operation;
8+
import io.swagger.v3.oas.annotations.Parameter;
9+
import io.swagger.v3.oas.annotations.tags.Tag;
10+
import lombok.RequiredArgsConstructor;
11+
import org.springframework.http.ResponseEntity;
12+
import org.springframework.security.core.annotation.AuthenticationPrincipal;
13+
import org.springframework.web.bind.annotation.GetMapping;
14+
import org.springframework.web.bind.annotation.RequestMapping;
15+
import org.springframework.web.bind.annotation.RestController;
16+
17+
@Tag(name = "Alert", description = "알림 관련 API")
18+
@RestController
19+
@RequestMapping("/api/alerts")
20+
@RequiredArgsConstructor
21+
public class AlertController {
22+
23+
private final AlertService alertService;
24+
25+
@Operation(
26+
summary = "미읽은 알림 개수 조회",
27+
description = "사용자의 미읽은 알림 개수를 조회합니다. 활동 알림과 키워드 알림의 개수를 각각 제공하며, 전체 개수도 함께 반환합니다."
28+
)
29+
@GetMapping("/unread-count")
30+
public ResponseEntity<ApiResponse<UnreadAlertCountResponseDTO>> getUnreadAlertCount(
31+
@Parameter(hidden = true) @AuthenticationPrincipal UserDetailsImpl user
32+
) {
33+
UnreadAlertCountResponseDTO result = alertService.getUnreadAlertCount(user.getId());
34+
return ResponseEntity.ok(ApiResponse.success(result));
35+
}
36+
}

src/main/java/com/example/cherrydan/activity/domain/ActivityAlert.java

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,11 +61,15 @@ public void markAsNotified() {
6161
public void markAsRead() {
6262
this.isRead = true;
6363
}
64-
64+
65+
public void hide() {
66+
this.isVisibleToUser = false;
67+
}
68+
6569
public String getNotificationTitle() {
6670
return alertType.getTitle();
6771
}
68-
72+
6973
public String getNotificationBody() {
7074
return alertType.getBodyTemplate(campaign.getTitle());
7175
}
Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,21 @@
1+
package com.example.cherrydan.activity.dto;
2+
3+
import io.swagger.v3.oas.annotations.media.Schema;
4+
import lombok.AllArgsConstructor;
5+
import lombok.Builder;
6+
import lombok.Getter;
7+
8+
@Getter
9+
@Builder
10+
@AllArgsConstructor
11+
public class UnreadAlertCountResponseDTO {
12+
13+
@Schema(description = "총 미읽은 알림 개수", example = "15", nullable = false, requiredMode = Schema.RequiredMode.REQUIRED)
14+
private Long totalCount;
15+
16+
@Schema(description = "활동 알림 미읽은 개수", example = "8", nullable = false, requiredMode = Schema.RequiredMode.REQUIRED)
17+
private Long activityAlertCount;
18+
19+
@Schema(description = "키워드 알림 미읽은 개수", example = "7", nullable = false, requiredMode = Schema.RequiredMode.REQUIRED)
20+
private Long keywordAlertCount;
21+
}

src/main/java/com/example/cherrydan/activity/repository/ActivityAlertRepository.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,4 +52,10 @@ public interface ActivityAlertRepository extends JpaRepository<ActivityAlert, Lo
5252
*/
5353
@Query("SELECT COUNT(aa) > 0 FROM ActivityAlert aa WHERE aa.user.id = :userId AND aa.campaign.id = :campaignId AND aa.isVisibleToUser = true")
5454
boolean existsByUserIdAndCampaignId(@Param("userId") Long userId, @Param("campaignId") Long campaignId);
55+
56+
/**
57+
* 사용자의 미읽은 활동 알림 개수 조회
58+
*/
59+
@Query("SELECT COUNT(aa) FROM ActivityAlert aa WHERE aa.user.id = :userId AND aa.isRead = false AND aa.isVisibleToUser = true")
60+
Long countUnreadByUserId(@Param("userId") Long userId);
5561
}

src/main/java/com/example/cherrydan/activity/service/ActivityAlertService.java

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -163,21 +163,22 @@ public Long getUserActivityAlertsCount(Long userId) {
163163

164164

165165
/**
166-
* 활동 알림 삭제 (배열)
166+
* 활동 알림 삭제 (소프트 삭제)
167167
*/
168168
@Transactional
169169
public void deleteActivityAlert(Long userId, List<Long> alertIds) {
170170
List<ActivityAlert> alerts = activityAlertRepository.findAllById(alertIds);
171-
171+
172172
// 모든 알림이 해당 사용자의 것인지 확인
173173
for (ActivityAlert alert : alerts) {
174174
if (!alert.getUser().getId().equals(userId)) {
175175
throw new UserException(ErrorMessage.ACTIVITY_ALERT_ACCESS_DENIED);
176176
}
177+
alert.hide();
177178
}
178-
179-
activityAlertRepository.deleteAll(alerts);
180-
log.info("활동 알림 삭제 완료: userId={}, count={}", userId, alertIds.size());
179+
180+
activityAlertRepository.saveAll(alerts);
181+
log.info("활동 알림 숨김 처리 완료: userId={}, count={}", userId, alertIds.size());
181182
}
182183

183184
/**
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package com.example.cherrydan.activity.service;
2+
3+
import com.example.cherrydan.activity.dto.UnreadAlertCountResponseDTO;
4+
import com.example.cherrydan.activity.repository.ActivityAlertRepository;
5+
import com.example.cherrydan.user.repository.KeywordCampaignAlertRepository;
6+
import lombok.RequiredArgsConstructor;
7+
import lombok.extern.slf4j.Slf4j;
8+
import org.springframework.stereotype.Service;
9+
import org.springframework.transaction.annotation.Transactional;
10+
11+
@Slf4j
12+
@Service
13+
@RequiredArgsConstructor
14+
public class AlertService {
15+
16+
private final ActivityAlertRepository activityAlertRepository;
17+
private final KeywordCampaignAlertRepository keywordCampaignAlertRepository;
18+
19+
/**
20+
* 사용자의 미읽은 알림 개수 조회
21+
*/
22+
@Transactional(readOnly = true)
23+
public UnreadAlertCountResponseDTO getUnreadAlertCount(Long userId) {
24+
Long activityCount = activityAlertRepository.countUnreadByUserId(userId);
25+
Long keywordCount = keywordCampaignAlertRepository.countUnreadByUserId(userId);
26+
27+
return UnreadAlertCountResponseDTO.builder()
28+
.totalCount(activityCount + keywordCount)
29+
.activityAlertCount(activityCount)
30+
.keywordAlertCount(keywordCount)
31+
.build();
32+
}
33+
}

src/main/java/com/example/cherrydan/sns/controller/SnsController.java

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -34,32 +34,32 @@ public class SnsController {
3434

3535
@Operation(summary = "OAuth 인증 URL 생성")
3636
@GetMapping("/oauth/{platform}/auth-url")
37-
public ApiResponse<String> getAuthUrl(@PathVariable("platform") String platform) {
37+
public ResponseEntity<ApiResponse<String>> getAuthUrl(@PathVariable("platform") String platform) {
3838
SnsPlatform snsPlatform = SnsPlatform.fromPlatformCode(platform);
3939
String authUrl = snsOAuthService.getAuthUrl(snsPlatform);
40-
return ApiResponse.success(snsPlatform.getDisplayName() + " 인증 URL 생성 성공", authUrl);
40+
return ResponseEntity.ok(ApiResponse.success(snsPlatform.getDisplayName() + " 인증 URL 생성 성공", authUrl));
4141
}
4242

4343
@Operation(summary = "OAuth 콜백 처리")
4444
@GetMapping("/oauth/{platform}/callback")
45-
public Mono<ApiResponse<SnsConnectionResponse>> handleOAuthCallback(
45+
public Mono<ResponseEntity<ApiResponse<SnsConnectionResponse>>> handleOAuthCallback(
4646
@AuthenticationPrincipal UserDetailsImpl userDetails,
4747
@PathVariable("platform") String platform,
4848
@RequestParam("code") String code) {
4949
SnsPlatform snsPlatform = SnsPlatform.fromPlatformCode(platform);
5050
User user = userService.getUserById(userDetails.getId());
51-
51+
5252
return snsOAuthService.connect(user, code, snsPlatform)
53-
.map(response -> ApiResponse.success(snsPlatform.getDisplayName() + " 연동이 완료되었습니다.", response));
53+
.map(response -> ResponseEntity.ok(ApiResponse.success(snsPlatform.getDisplayName() + " 연동이 완료되었습니다.", response)));
5454
}
5555

5656
@Operation(summary = "사용자 SNS 연동 목록 조회")
5757
@GetMapping("/connections")
58-
public ApiResponse<List<SnsConnectionResponse>> getConnections(
58+
public ResponseEntity<ApiResponse<List<SnsConnectionResponse>>> getConnections(
5959
@AuthenticationPrincipal UserDetailsImpl userDetails) {
6060
User user = userService.getUserById(userDetails.getId());
6161
List<SnsConnectionResponse> response = snsOAuthService.getUserSnsConnections(user);
62-
return ApiResponse.success("SNS 연동 목록 조회 성공", response);
62+
return ResponseEntity.ok(ApiResponse.success("SNS 연동 목록 조회 성공", response));
6363
}
6464

6565
@Operation(summary = "SNS 연동 해제")

0 commit comments

Comments
 (0)