diff --git a/src/docs/asciidoc/notification.adoc b/src/docs/asciidoc/notification.adoc index c595caa..a044971 100644 --- a/src/docs/asciidoc/notification.adoc +++ b/src/docs/asciidoc/notification.adoc @@ -25,6 +25,25 @@ include::{snippets}/notification-controller-test/유저에_해당하는_fcm_토 ===== FCM 메시지 발송 과정에서 오류가 발생한 경우 include::{snippets}/notification-controller-test/모종의_이유로_fcm_알림_발송과정에서_문제가_발생하면_에러가_발생한다/http-response.adoc[] +=== 알림 전체 발송 +어드민은 모든 유저에게 알림을 발송할 수 있습니다. + +==== 요청 형식 + +===== Request Header +include::{snippets}/notification-controller-test/fcm_토큰을_가진_유저_전체에게_알림을_발송한다/request-headers.adoc[] + +===== Request Body +include::{snippets}/notification-controller-test/fcm_토큰을_가진_유저_전체에게_알림을_발송한다/request-fields.adoc[] + +==== 응답 + +===== 정상 응답 +include::{snippets}/notification-controller-test/fcm_토큰을_가진_유저_전체에게_알림을_발송한다/http-response.adoc[] + +===== 모종의 이유로 알림 발송에 실패한 경우 +include::{snippets}/notification-controller-test/fcm_토큰을_가진_유저_전체에게_알림발송_과정에서_문제가_발생하면_에러가_발생한다/http-response.adoc[] + === 알림 전체 조회 유저는 알림을 전체 조회할 수 있습니다. diff --git a/src/main/java/com/bamdoliro/sinabro/application/letter/GenerateLetterUseCase.java b/src/main/java/com/bamdoliro/sinabro/application/letter/GenerateLetterUseCase.java index 0263d4c..d7c5f87 100644 --- a/src/main/java/com/bamdoliro/sinabro/application/letter/GenerateLetterUseCase.java +++ b/src/main/java/com/bamdoliro/sinabro/application/letter/GenerateLetterUseCase.java @@ -10,6 +10,7 @@ import com.bamdoliro.sinabro.infrastructure.persistence.diary.DiaryRepository; import com.bamdoliro.sinabro.infrastructure.persistence.letter.LetterRepository; import com.bamdoliro.sinabro.shared.annotation.UseCase; +import com.bamdoliro.sinabro.shared.response.IdResponse; import lombok.RequiredArgsConstructor; import org.springframework.transaction.annotation.Transactional; @@ -26,14 +27,16 @@ public class GenerateLetterUseCase { private final CharacterFacade characterFacade; @Transactional - public void execute(User user) { + public IdResponse execute(User user) { Character character = characterFacade.getCharacter(user); List emotionAndKeywords = extractEmotionAndKeywords(user); GenerateLetterResponse response = aiService.generateLetter(character.getType().getId(), character.getFriendship(), emotionAndKeywords); - letterRepository.save(new Letter(response.getContent(), user)); + Letter letter = letterRepository.save(new Letter(response.getContent(), user)); character.increaseFriendShip(); + + return new IdResponse(letter); } private List extractEmotionAndKeywords(User user) { diff --git a/src/main/java/com/bamdoliro/sinabro/application/notification/SendNotificationToAllUserUseCase.java b/src/main/java/com/bamdoliro/sinabro/application/notification/SendNotificationToAllUserUseCase.java new file mode 100644 index 0000000..f6baab8 --- /dev/null +++ b/src/main/java/com/bamdoliro/sinabro/application/notification/SendNotificationToAllUserUseCase.java @@ -0,0 +1,28 @@ +package com.bamdoliro.sinabro.application.notification; + +import com.bamdoliro.sinabro.domain.notification.domain.Notification; +import com.bamdoliro.sinabro.infrastructure.fcm.FCMService; +import com.bamdoliro.sinabro.infrastructure.persistence.fcm.token.FCMTokenRepository; +import com.bamdoliro.sinabro.infrastructure.persistence.notification.NotificationRepository; +import com.bamdoliro.sinabro.presentation.notification.dto.request.SendNotificationRequest; +import com.bamdoliro.sinabro.shared.annotation.UseCase; +import lombok.RequiredArgsConstructor; +import org.springframework.transaction.annotation.Transactional; + +@RequiredArgsConstructor +@UseCase +public class SendNotificationToAllUserUseCase { + + private final FCMService fcmService; + private final NotificationRepository notificationRepository; + private final FCMTokenRepository fcmTokenRepository; + + @Transactional + public void execute(SendNotificationRequest request) { + fcmTokenRepository.findAll() + .forEach(fcmToken -> { + fcmService.sendMessageTo(fcmToken.getToken(), request.getTitle(), request.getBody()); + notificationRepository.save(new Notification(request.getTitle(), request.getBody(), fcmToken.getUser())); + }); + } +} diff --git a/src/main/java/com/bamdoliro/sinabro/presentation/letter/LetterController.java b/src/main/java/com/bamdoliro/sinabro/presentation/letter/LetterController.java index cb09fe8..d6023ba 100644 --- a/src/main/java/com/bamdoliro/sinabro/presentation/letter/LetterController.java +++ b/src/main/java/com/bamdoliro/sinabro/presentation/letter/LetterController.java @@ -7,6 +7,7 @@ import com.bamdoliro.sinabro.presentation.letter.dto.response.LetterResponse; import com.bamdoliro.sinabro.presentation.letter.dto.response.ListLetterResponse; import com.bamdoliro.sinabro.shared.auth.AuthenticationPrincipal; +import com.bamdoliro.sinabro.shared.response.IdResponse; import com.bamdoliro.sinabro.shared.response.ListCommonResponse; import com.bamdoliro.sinabro.shared.response.SingleCommonResponse; import lombok.RequiredArgsConstructor; @@ -24,10 +25,10 @@ public class LetterController { @ResponseStatus(HttpStatus.CREATED) @PostMapping - public void generateLetter( + public IdResponse generateLetter( @AuthenticationPrincipal User user ) { - generateLetterUseCase.execute(user); + return generateLetterUseCase.execute(user); } @GetMapping diff --git a/src/main/java/com/bamdoliro/sinabro/presentation/notification/NotificationController.java b/src/main/java/com/bamdoliro/sinabro/presentation/notification/NotificationController.java index eb106d8..5abb5a9 100644 --- a/src/main/java/com/bamdoliro/sinabro/presentation/notification/NotificationController.java +++ b/src/main/java/com/bamdoliro/sinabro/presentation/notification/NotificationController.java @@ -1,11 +1,13 @@ package com.bamdoliro.sinabro.presentation.notification; import com.bamdoliro.sinabro.application.notification.QueryNotificationListUseCase; +import com.bamdoliro.sinabro.application.notification.SendNotificationToAllUserUseCase; import com.bamdoliro.sinabro.application.notification.SendNotificationUseCase; import com.bamdoliro.sinabro.domain.user.domain.User; import com.bamdoliro.sinabro.presentation.notification.dto.request.SendNotificationRequest; import com.bamdoliro.sinabro.presentation.notification.dto.response.ListNotificationResponse; import com.bamdoliro.sinabro.shared.auth.AuthenticationPrincipal; +import com.bamdoliro.sinabro.shared.auth.Authority; import com.bamdoliro.sinabro.shared.response.CommonResponse; import com.bamdoliro.sinabro.shared.response.ListCommonResponse; import jakarta.validation.Valid; @@ -19,6 +21,7 @@ public class NotificationController { private final SendNotificationUseCase sendNotificationUseCase; + private final SendNotificationToAllUserUseCase sendNotificationToAllUserUseCase; private final QueryNotificationListUseCase queryNotificationListUseCase; @ResponseStatus(HttpStatus.NO_CONTENT) @@ -30,6 +33,15 @@ public void sendNotification( sendNotificationUseCase.execute(user, request); } + @ResponseStatus(HttpStatus.NO_CONTENT) + @PostMapping("/all") + public void sendNotificationToAllUser( + @AuthenticationPrincipal(authority = Authority.ADMIN) User user, + @RequestBody @Valid SendNotificationRequest request + ) { + sendNotificationToAllUserUseCase.execute(request); + } + @GetMapping public ListCommonResponse queryNotification( @AuthenticationPrincipal User user diff --git a/src/main/java/com/bamdoliro/sinabro/presentation/notification/dto/request/SendNotificationRequest.java b/src/main/java/com/bamdoliro/sinabro/presentation/notification/dto/request/SendNotificationRequest.java index 72e9947..a2ca5ad 100644 --- a/src/main/java/com/bamdoliro/sinabro/presentation/notification/dto/request/SendNotificationRequest.java +++ b/src/main/java/com/bamdoliro/sinabro/presentation/notification/dto/request/SendNotificationRequest.java @@ -1,5 +1,6 @@ package com.bamdoliro.sinabro.presentation.notification.dto.request; +import jakarta.validation.constraints.NotBlank; import lombok.*; @Getter @@ -7,8 +8,10 @@ @AllArgsConstructor public class SendNotificationRequest { + @NotBlank(message = "필수값입니다.") private String title; + @NotBlank(message = "필수값입니다.") private String body; } diff --git a/src/main/java/com/bamdoliro/sinabro/shared/response/IdResponse.java b/src/main/java/com/bamdoliro/sinabro/shared/response/IdResponse.java index 2a0a117..0d49f00 100644 --- a/src/main/java/com/bamdoliro/sinabro/shared/response/IdResponse.java +++ b/src/main/java/com/bamdoliro/sinabro/shared/response/IdResponse.java @@ -1,6 +1,7 @@ package com.bamdoliro.sinabro.shared.response; import com.bamdoliro.sinabro.domain.diary.domain.Diary; +import com.bamdoliro.sinabro.domain.letter.domain.Letter; import com.bamdoliro.sinabro.domain.question.domain.Question; import lombok.AllArgsConstructor; import lombok.Getter; @@ -15,6 +16,10 @@ public IdResponse(Diary diary) { this.id = diary.getId(); } + public IdResponse(Letter letter) { + this.id = letter.getId(); + } + public IdResponse(Question question) { this.id = question.getId(); } diff --git a/src/test/java/com/bamdoliro/sinabro/presentation/notifcation/NotificationControllerTest.java b/src/test/java/com/bamdoliro/sinabro/presentation/notifcation/NotificationControllerTest.java index e7573d0..b5c39f3 100644 --- a/src/test/java/com/bamdoliro/sinabro/presentation/notifcation/NotificationControllerTest.java +++ b/src/test/java/com/bamdoliro/sinabro/presentation/notifcation/NotificationControllerTest.java @@ -136,4 +136,60 @@ public class NotificationControllerTest extends RestDocsTestSupport { verify(queryNotificationListUseCase, times(1)).execute(any(User.class)); } + + @Test + void FCM_토큰을_가진_유저_전체에게_알림을_발송한다() throws Exception { + User user = UserFixture.createAdmin(); + SendNotificationRequest request = new SendNotificationRequest("당신에게 편지가 왔어요.", "편지가 왔어요 어서 확인해보세요!"); + + given(authenticationArgumentResolver.supportsParameter(any(MethodParameter.class))).willReturn(true); + given(authenticationArgumentResolver.resolveArgument(any(), any(), any(), any())).willReturn(user); + + mockMvc.perform(post("/notifications/all") + .header(HttpHeaders.AUTHORIZATION, AuthFixture.createAuthHeader()) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(toJson(request)) + ) + + .andExpect(status().isNoContent()) + + .andDo(restDocs.document( + requestHeaders( + headerWithName(HttpHeaders.AUTHORIZATION) + .description("Bearer token") + ), + requestFields( + fieldWithPath("title") + .description("알림 제목"), + fieldWithPath("body") + .description("알림 내용") + ) + )); + + verify(sendNotificationToAllUserUseCase, times(1)).execute(any(SendNotificationRequest.class)); + } + + @Test + void FCM_토큰을_가진_유저_전체에게_알림발송_과정에서_문제가_발생하면_에러가_발생한다() throws Exception { + User user = UserFixture.createAdmin(); + SendNotificationRequest request = new SendNotificationRequest("당신에게 편지가 왔어요.", "편지가 왔어요 어서 확인해보세요!"); + + given(authenticationArgumentResolver.supportsParameter(any(MethodParameter.class))).willReturn(true); + given(authenticationArgumentResolver.resolveArgument(any(), any(), any(), any())).willReturn(user); + willThrow(new FailedToSendException()).given(sendNotificationToAllUserUseCase).execute(any(SendNotificationRequest.class)); + + mockMvc.perform(post("/notifications/all") + .header(HttpHeaders.AUTHORIZATION, AuthFixture.createAuthHeader()) + .accept(MediaType.APPLICATION_JSON) + .contentType(MediaType.APPLICATION_JSON) + .content(toJson(request)) + ) + + .andExpect(status().isInternalServerError()) + + .andDo(restDocs.document()); + + verify(sendNotificationToAllUserUseCase, times(1)).execute(any(SendNotificationRequest.class)); + } } diff --git a/src/test/java/com/bamdoliro/sinabro/shared/util/ControllerTest.java b/src/test/java/com/bamdoliro/sinabro/shared/util/ControllerTest.java index 07606d6..b49a9d9 100644 --- a/src/test/java/com/bamdoliro/sinabro/shared/util/ControllerTest.java +++ b/src/test/java/com/bamdoliro/sinabro/shared/util/ControllerTest.java @@ -6,6 +6,7 @@ import com.bamdoliro.sinabro.application.auth.*; import com.bamdoliro.sinabro.application.character.GetCharacterUseCase; import com.bamdoliro.sinabro.application.diary.*; +import com.bamdoliro.sinabro.application.notification.SendNotificationToAllUserUseCase; import com.bamdoliro.sinabro.application.question.*; import com.bamdoliro.sinabro.domain.auth.service.TokenService; import com.bamdoliro.sinabro.presentation.auth.AuthController; @@ -101,6 +102,9 @@ public abstract class ControllerTest { @MockBean protected QueryNotificationListUseCase queryNotificationListUseCase; + @MockBean + protected SendNotificationToAllUserUseCase sendNotificationToAllUserUseCase; + @MockBean protected SaveFCMTokenUseCase saveFCMTokenUseCase;