diff --git a/backend/streetdrop-notification/src/main/java/com/depromeet/controller/PushController.java b/backend/streetdrop-notification/src/main/java/com/depromeet/controller/PushController.java index 922fd7e9..f403196a 100644 --- a/backend/streetdrop-notification/src/main/java/com/depromeet/controller/PushController.java +++ b/backend/streetdrop-notification/src/main/java/com/depromeet/controller/PushController.java @@ -14,6 +14,7 @@ @RequiredArgsConstructor @RequestMapping("/push") public class PushController { + private final PushService pushService; @PostMapping("/send") @@ -30,4 +31,5 @@ public void sendAllPush(@RequestBody AllPushRequestDto allPushRequestDto) { public void sendTopicPush(@RequestBody TopicPushRequestDto topicPushRequestDto) { pushService.sendTopicPush(topicPushRequestDto); } + } diff --git a/backend/streetdrop-notification/src/main/java/com/depromeet/controller/TokenController.java b/backend/streetdrop-notification/src/main/java/com/depromeet/controller/TokenController.java index 33bd431c..c957bab6 100644 --- a/backend/streetdrop-notification/src/main/java/com/depromeet/controller/TokenController.java +++ b/backend/streetdrop-notification/src/main/java/com/depromeet/controller/TokenController.java @@ -13,17 +13,12 @@ public class TokenController { private final TokenService tokenService; @PostMapping - public void createToken(@RequestBody TokenRequestDto tokenRequestDto) { - tokenService.createToken(tokenRequestDto); + public void saveToken(@RequestBody TokenRequestDto tokenRequestDto) { + tokenService.saveToken(tokenRequestDto); } - @PutMapping - public void updateToken(@RequestBody TokenRequestDto tokenRequestDto) { - tokenService.updateToken(tokenRequestDto); - } - - @DeleteMapping - public void deleteToken(@RequestBody Long userId) { + @DeleteMapping("/{userId}") + public void deleteToken(@PathVariable("userId") Long userId) { tokenService.deleteToken(userId); } diff --git a/backend/streetdrop-notification/src/main/java/com/depromeet/domain/UserDevice.java b/backend/streetdrop-notification/src/main/java/com/depromeet/domain/UserDevice.java index cd368f57..6f08645b 100644 --- a/backend/streetdrop-notification/src/main/java/com/depromeet/domain/UserDevice.java +++ b/backend/streetdrop-notification/src/main/java/com/depromeet/domain/UserDevice.java @@ -2,6 +2,7 @@ import com.depromeet.domain.vo.OsType; import lombok.Builder; +import lombok.Getter; import org.springframework.data.annotation.CreatedDate; import org.springframework.data.annotation.Id; import org.springframework.data.annotation.LastModifiedDate; @@ -9,6 +10,7 @@ import java.util.Date; +@Getter @Builder @Document(collection = "user_device") public class UserDevice { @@ -24,4 +26,10 @@ public class UserDevice { private Date createdAt; @LastModifiedDate private Date modifiedAt; + + public UserDevice updateDeviceToken(String deviceToken) { + this.deviceToken = deviceToken; + return this; + } + } \ No newline at end of file diff --git a/backend/streetdrop-notification/src/main/java/com/depromeet/repository/MemoryTokenRepository.java b/backend/streetdrop-notification/src/main/java/com/depromeet/repository/MemoryTokenRepository.java deleted file mode 100644 index 5f9858ee..00000000 --- a/backend/streetdrop-notification/src/main/java/com/depromeet/repository/MemoryTokenRepository.java +++ /dev/null @@ -1,46 +0,0 @@ -package com.depromeet.repository; - -import org.springframework.stereotype.Repository; - -import java.util.List; -import java.util.Map; -import java.util.Optional; -import java.util.concurrent.ConcurrentHashMap; - -@Repository -public class MemoryTokenRepository implements TokenRepository { - - private final Map tokenMap = new ConcurrentHashMap<>(); - - @Override - public void save(Long userId, String token) { - tokenMap.put(userId, token); - } - - @Override - public Optional findByUserId(Long userId) { - return Optional.ofNullable(tokenMap.get(userId)); - } - - @Override - public List findByUserIds(List userIds) { - return userIds.stream() - .map(tokenMap::get) - .toList(); - } - - @Override - public List findAll() { - return tokenMap.values().stream().toList(); - } - - @Override - public void update(Long userId, String token) { - tokenMap.put(userId, token); - } - - @Override - public void delete(Long userId) { - tokenMap.remove(userId); - } -} diff --git a/backend/streetdrop-notification/src/main/java/com/depromeet/repository/TokenRepository.java b/backend/streetdrop-notification/src/main/java/com/depromeet/repository/TokenRepository.java deleted file mode 100644 index c624ee02..00000000 --- a/backend/streetdrop-notification/src/main/java/com/depromeet/repository/TokenRepository.java +++ /dev/null @@ -1,18 +0,0 @@ -package com.depromeet.repository; - -import java.util.List; -import java.util.Optional; - -public interface TokenRepository { - void save(Long userId, String token); - - Optional findByUserId(Long userId); - - List findByUserIds(List userIds); - - List findAll(); - - void update(Long userId, String token); - - void delete(Long userId); -} diff --git a/backend/streetdrop-notification/src/main/java/com/depromeet/repository/UserDeviceRepository.java b/backend/streetdrop-notification/src/main/java/com/depromeet/repository/UserDeviceRepository.java new file mode 100644 index 00000000..2397440d --- /dev/null +++ b/backend/streetdrop-notification/src/main/java/com/depromeet/repository/UserDeviceRepository.java @@ -0,0 +1,21 @@ +package com.depromeet.repository; + +import com.depromeet.domain.UserDevice; +import org.springframework.data.mongodb.repository.MongoRepository; +import org.springframework.data.mongodb.repository.Query; + +import java.util.List; +import java.util.Optional; + +public interface UserDeviceRepository extends MongoRepository { + + @Query(value = "{ 'userId' : ?0 }") + Optional findByUserId(Long userId); + + @Query(value = "{ 'userId' : ?0 }", fields = "{ 'deviceToken' : 1 }") + Optional findDeviceTokenByUserId(Long userId); + + @Query(value = "{}", fields = "{ 'deviceToken' : 1 }") + List findAllDeviceTokens(); + +} diff --git a/backend/streetdrop-notification/src/main/java/com/depromeet/service/NotificationService.java b/backend/streetdrop-notification/src/main/java/com/depromeet/service/NotificationService.java new file mode 100644 index 00000000..4b0050eb --- /dev/null +++ b/backend/streetdrop-notification/src/main/java/com/depromeet/service/NotificationService.java @@ -0,0 +1,90 @@ +package com.depromeet.service; + +import com.depromeet.domain.Notification; +import com.depromeet.domain.Target; +import com.depromeet.domain.User; +import com.depromeet.domain.UserDevice; +import com.depromeet.domain.vo.Channel; +import com.depromeet.dto.request.AllPushRequestDto; +import com.depromeet.dto.request.PushRequestDto; +import com.depromeet.dto.request.TopicPushRequestDto; +import com.depromeet.repository.NotificationRepository; +import com.depromeet.repository.UserDeviceRepository; +import lombok.RequiredArgsConstructor; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.ArrayList; +import java.util.List; + +@Service +@RequiredArgsConstructor +public class NotificationService { + + private final NotificationRepository notificationRepository; + private final UserDeviceRepository userDeviceRepository; + + @Transactional + public void save(PushRequestDto pushRequestDto) { + List notificationList = new ArrayList<>(); + + for (Long userId : pushRequestDto.getUserIds()) { + var userDevice = userDeviceRepository.findByUserId(userId) + .orElseThrow(() -> new RuntimeException("Token not found for userId: " + userId)); + User user = User.builder() + .userId(userId) + .deviceToken(userDevice.getDeviceToken()) + .build(); + + Target target = Target.builder() + .channel(Channel.GENERAL) + .build(); + + Notification notification = Notification.builder() + .user(user) + .target(target) + .title(pushRequestDto.getTitle()) + .content(pushRequestDto.getContent()) + .build(); + + notificationList.add(notification); + } + + notificationRepository.saveAll(notificationList); + } + + + @Transactional + public void save(AllPushRequestDto pushRequestDto) { + List notificationList = new ArrayList<>(); + + List userDevices = userDeviceRepository.findAll(); + for (UserDevice userDevice : userDevices) { + User user = User.builder() + .userId(userDevice.getUserId()) + .deviceToken(userDevice.getDeviceToken()) + .build(); + + Target target = Target.builder() + .channel(Channel.GENERAL) + .build(); + + Notification notification = Notification.builder() + .user(user) + .target(target) + .title(pushRequestDto.getTitle()) + .content(pushRequestDto.getContent()) + .build(); + + notificationList.add(notification); + } + + notificationRepository.saveAll(notificationList); + } + + @Transactional + public void save(TopicPushRequestDto tokenPushRequestDto) { + // TODO: 토픽 저장에 따른 스키마 변경 후 구체화 + } + +} diff --git a/backend/streetdrop-notification/src/main/java/com/depromeet/service/PushService.java b/backend/streetdrop-notification/src/main/java/com/depromeet/service/PushService.java index 75208b5f..2db6a588 100644 --- a/backend/streetdrop-notification/src/main/java/com/depromeet/service/PushService.java +++ b/backend/streetdrop-notification/src/main/java/com/depromeet/service/PushService.java @@ -4,10 +4,11 @@ import com.depromeet.dto.request.PushRequestDto; import com.depromeet.dto.request.TopicPushRequestDto; import com.depromeet.external.fcm.FcmService; -import com.depromeet.repository.TokenRepository; +import com.depromeet.repository.UserDeviceRepository; import com.google.firebase.messaging.FirebaseMessagingException; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import java.util.List; @@ -15,14 +16,17 @@ @RequiredArgsConstructor public class PushService { - private final TokenRepository tokenRepository; + private final UserDeviceRepository userDeviceRepository; + private final NotificationService notificationService; private final FcmService fcmService; + @Transactional public void sendPush(PushRequestDto pushRequestDto) { - List tokens = tokenRepository.findByUserIds(pushRequestDto.getUserIds()); - if (tokens.isEmpty()) { - throw new RuntimeException("token not found"); - } + List tokens = pushRequestDto.getUserIds().stream() + .map(userId -> userDeviceRepository.findDeviceTokenByUserId(userId) + .orElseThrow(() -> new RuntimeException("Token not found for userId: " + userId))) + .toList(); + try { if (tokens.size() == 1) { fcmService.sendMessageSync(tokens.get(0), pushRequestDto.getContent()); @@ -35,10 +39,13 @@ public void sendPush(PushRequestDto pushRequestDto) { } } catch (FirebaseMessagingException e) { } + + notificationService.save(pushRequestDto); } + @Transactional public void sendAllPush(AllPushRequestDto pushRequestDto) { - List tokens = tokenRepository.findAll(); + List tokens = userDeviceRepository.findAllDeviceTokens(); try { if (pushRequestDto.getTitle() != null) { fcmService.sendMulticastMessageSync(tokens, pushRequestDto.getTitle(), pushRequestDto.getContent()); @@ -47,8 +54,11 @@ public void sendAllPush(AllPushRequestDto pushRequestDto) { } } catch (FirebaseMessagingException e) { } + + notificationService.save(pushRequestDto); } + @Transactional public void sendTopicPush(TopicPushRequestDto tokenPushRequestDto) { try { if (tokenPushRequestDto.getTitle() != null) { @@ -58,6 +68,8 @@ public void sendTopicPush(TopicPushRequestDto tokenPushRequestDto) { } } catch (FirebaseMessagingException e) { } + + notificationService.save(tokenPushRequestDto); } } diff --git a/backend/streetdrop-notification/src/main/java/com/depromeet/service/TokenService.java b/backend/streetdrop-notification/src/main/java/com/depromeet/service/TokenService.java index 0fe99e58..8f89154f 100644 --- a/backend/streetdrop-notification/src/main/java/com/depromeet/service/TokenService.java +++ b/backend/streetdrop-notification/src/main/java/com/depromeet/service/TokenService.java @@ -1,25 +1,39 @@ package com.depromeet.service; +import com.depromeet.domain.UserDevice; import com.depromeet.dto.request.TokenRequestDto; -import com.depromeet.repository.TokenRepository; +import com.depromeet.repository.UserDeviceRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.Optional; @Service @RequiredArgsConstructor public class TokenService { - private final TokenRepository tokenRepository; - - public void createToken(TokenRequestDto tokenRequestDto) { - tokenRepository.save(tokenRequestDto.getUserId(), tokenRequestDto.getToken()); - } + private final UserDeviceRepository userDeviceRepository; - public void updateToken(TokenRequestDto tokenRequestDto) { - tokenRepository.update(tokenRequestDto.getUserId(), tokenRequestDto.getToken()); + @Transactional + public void saveToken(TokenRequestDto tokenRequestDto) { + Optional userDevice = userDeviceRepository.findByUserId(tokenRequestDto.getUserId()); + if (userDevice.isPresent()) { + UserDevice updatedUserDevice = userDevice.get().updateDeviceToken(tokenRequestDto.getToken()); + userDeviceRepository.save(updatedUserDevice); + } else { + UserDevice createdUserDevice = UserDevice.builder() + .userId(tokenRequestDto.getUserId()) + .deviceToken(tokenRequestDto.getToken()) + .build(); + userDeviceRepository.save(createdUserDevice); + } } + @Transactional public void deleteToken(Long userId) { - tokenRepository.delete(userId); + var userDevice = userDeviceRepository.findByUserId(userId) + .orElseThrow(() -> new RuntimeException("Token not found for userId: " + userId)); + userDeviceRepository.delete(userDevice); } } diff --git a/backend/streetdrop-notification/src/main/java/com/depromeet/service/TopicService.java b/backend/streetdrop-notification/src/main/java/com/depromeet/service/TopicService.java index fddf6e94..a647dc53 100644 --- a/backend/streetdrop-notification/src/main/java/com/depromeet/service/TopicService.java +++ b/backend/streetdrop-notification/src/main/java/com/depromeet/service/TopicService.java @@ -2,7 +2,7 @@ import com.depromeet.dto.request.TopicSubscribeRequestDto; import com.depromeet.external.fcm.FcmService; -import com.depromeet.repository.TokenRepository; +import com.depromeet.repository.UserDeviceRepository; import lombok.RequiredArgsConstructor; import org.springframework.stereotype.Service; @@ -12,11 +12,11 @@ @RequiredArgsConstructor public class TopicService { - private final TokenRepository tokenRepository; + private final UserDeviceRepository userDeviceRepository; private final FcmService fcmService; public void subscribeTopic(TopicSubscribeRequestDto topicSubscribeRequestDto) { - List tokens = tokenRepository.findByUserIds(topicSubscribeRequestDto.getUserIds()); + List tokens = getTokens(topicSubscribeRequestDto.getUserIds()); try { fcmService.subscribeTopicSync(topicSubscribeRequestDto.getTopic(), tokens); } catch (Exception e) { @@ -24,11 +24,18 @@ public void subscribeTopic(TopicSubscribeRequestDto topicSubscribeRequestDto) { } public void unsubscribeTopic(TopicSubscribeRequestDto topicSubscribeRequestDto) { - List tokens = tokenRepository.findByUserIds(topicSubscribeRequestDto.getUserIds()); + List tokens = getTokens(topicSubscribeRequestDto.getUserIds()); try { fcmService.subscribeTopicSync(topicSubscribeRequestDto.getTopic(), tokens); } catch (Exception e) { } } + private List getTokens(List userIds) { + return userIds.stream() + .map(userId -> userDeviceRepository.findDeviceTokenByUserId(userId) + .orElseThrow(() -> new RuntimeException("Token not found for userId: " + userId))) + .toList(); + } + }