Skip to content

Commit

Permalink
Merge pull request #80 from Donut-DONationUTile/feature/fcm
Browse files Browse the repository at this point in the history
Feature/fcm
  • Loading branch information
Ganghee-Lee-0522 authored Apr 30, 2024
2 parents 862ddba + 22b73f7 commit 77497f6
Show file tree
Hide file tree
Showing 17 changed files with 340 additions and 30 deletions.
2 changes: 1 addition & 1 deletion Donut-Server-yml
2 changes: 2 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -57,6 +57,8 @@ dependencies {
implementation 'io.jsonwebtoken:jjwt-jackson:0.11.5'
//WebClient
implementation group: 'org.springframework.boot', name: 'spring-boot-starter-webflux'
// firebase
implementation group: 'com.google.firebase', name: 'firebase-admin', version: '8.1.0'

}

Expand Down
93 changes: 93 additions & 0 deletions src/main/java/zero/eight/donut/config/firebase/FcmUtils.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
package zero.eight.donut.config.firebase;

import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.FirebaseMessagingException;
import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.Notification;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import zero.eight.donut.config.jwt.AuthUtils;
import zero.eight.donut.domain.FcmToken;
import zero.eight.donut.domain.Giver;
import zero.eight.donut.domain.Receiver;
import zero.eight.donut.dto.auth.Role;
import zero.eight.donut.dto.fcm.FcmMemberDto;
import zero.eight.donut.exception.Error;
import zero.eight.donut.repository.FcmTokenRepository;
import zero.eight.donut.repository.GiverRepository;
import zero.eight.donut.repository.ReceiverRepository;

import java.util.Optional;

@Slf4j
@RequiredArgsConstructor
public class FcmUtils {

private final AuthUtils authUtils;
private final GiverRepository giverRepository;
private final ReceiverRepository receiverRepository;
private final FcmTokenRepository fcmTokenRepository;
private final FirebaseMessaging firebaseMessaging;



public FcmMemberDto getMemberDto() throws Exception {
if (authUtils.getCurrentUserRole().equals(Role.ROLE_GIVER)) {
Giver giver = giverRepository.findByEmail(authUtils.getCurrentUserEmail()).orElseThrow(
() -> new Exception(Error.USERNAME_NOT_FOUND_EXCEPTION.getMessage())
);
return FcmMemberDto.builder()
.id(giver.getId())
.role(Role.ROLE_GIVER)
.build();
}
else {
Optional<Receiver> optionalReceiver = receiverRepository.findByName(authUtils.getCurrentUserEmail());
if (optionalReceiver.isEmpty()) {
throw new Exception(Error.USERNAME_NOT_FOUND_EXCEPTION.getMessage());
}

return FcmMemberDto.builder()
.id(optionalReceiver.get().getId())
.role(Role.ROLE_RECEIVER)
.build();
}
}

// FCM 메세지 전송
public String sendMessage(Long memberId, String title, String body) throws FirebaseMessagingException {
// 알림 수신자의 FCM 토큰 조회
String fcmToken = getFcmToken(memberId);
// FCM 메세지 생성
Message message = makeMessage(fcmToken, title, body);
// FCM 발신
return firebaseMessaging.send(message);
}

public String getFcmToken(Long memberId) {
Optional<FcmToken> fcmToken = fcmTokenRepository.findByMemberId(memberId);

if (fcmToken.isEmpty()) {
try {
throw new Exception(Error.FCM_TOKEN_NOT_FOUND_EXCEPTION.getMessage());
} catch (Exception e) {
throw new RuntimeException(e);
}
}

return fcmToken.get().getToken();
}

public Message makeMessage(String targetToken, String title, String body) {
// Notification 객체는 모바일 환경에서 제목과 본문을 표시
Notification notification = Notification.builder()
.setTitle(title)
.setBody(body)
.build();

return Message.builder()
.setNotification(notification)
.setToken(targetToken)
.build();
}
}
28 changes: 28 additions & 0 deletions src/main/java/zero/eight/donut/config/firebase/FirebaseConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
package zero.eight.donut.config.firebase;

import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import org.springframework.context.annotation.Configuration;

import javax.annotation.PostConstruct;
import java.io.FileInputStream;
import java.io.FileNotFoundException;

@Configuration
public class FirebaseConfig {

@PostConstruct
public void init() {
try {
FileInputStream serviceAccount =
new FileInputStream("src/main/resources/serviceAccountKey.json");
FirebaseOptions options = new FirebaseOptions.Builder()
.setCredentials(GoogleCredentials.fromStream(serviceAccount))
.build();
FirebaseApp.initializeApp(options);
} catch (Exception e) {
e.printStackTrace();
}
}
}
23 changes: 23 additions & 0 deletions src/main/java/zero/eight/donut/controller/FcmController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package zero.eight.donut.controller;

import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import zero.eight.donut.common.response.ApiResponse;
import zero.eight.donut.dto.fcm.FcmTokenRequestDto;
import zero.eight.donut.service.FcmService;

@RequiredArgsConstructor
@RequestMapping("/api/fcm")
@RestController
public class FcmController {

private final FcmService fcmService;

@PostMapping("/token")
public ApiResponse<?> createFcmToken(@RequestBody FcmTokenRequestDto requestDto) throws Exception {
return fcmService.registerFcmToken(requestDto);
}
}
38 changes: 38 additions & 0 deletions src/main/java/zero/eight/donut/domain/FcmToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package zero.eight.donut.domain;

import jakarta.persistence.*;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import zero.eight.donut.dto.auth.Role;

@Getter
@NoArgsConstructor
@Entity
public class FcmToken {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;

@Column(nullable = false)
private String token;

@Column(nullable = false, unique = true)
private Long memberId;

@Column(nullable = false)
private Role role;

@Builder // 롬복의 @Builder 애너테이션 적용
public FcmToken(String token, Long memberId, Role role) {
this.token = token;
this.memberId = memberId;
this.role = role;
}

public void updateToken(String token, Role role) {
this.token = token;
this.role = role;
}
}
12 changes: 12 additions & 0 deletions src/main/java/zero/eight/donut/dto/fcm/FcmMemberDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package zero.eight.donut.dto.fcm;

import lombok.Builder;
import lombok.Getter;
import zero.eight.donut.dto.auth.Role;

@Getter
@Builder
public class FcmMemberDto {
private Long id;
private Role role;
}
10 changes: 10 additions & 0 deletions src/main/java/zero/eight/donut/dto/fcm/FcmTokenRequestDto.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package zero.eight.donut.dto.fcm;

import lombok.Getter;
import lombok.RequiredArgsConstructor;

@RequiredArgsConstructor
@Getter
public class FcmTokenRequestDto {
private final String token;
}
5 changes: 5 additions & 0 deletions src/main/java/zero/eight/donut/exception/Error.java
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,15 @@ public enum Error {
ERROR(HttpStatus.BAD_REQUEST, "Request processing failed"),

// 400 BAD REQUEST
CONVERT_JSON_EXCEPTION(HttpStatus.BAD_REQUEST, "Failed to convert json object to JSON"),
FIREBASE_CONNECT_EXCEPTION(HttpStatus.BAD_REQUEST, "Firebase connect failed"),
GOOGLE_REQUEST_TOKEN_EXCEPTION(HttpStatus.BAD_REQUEST, "google oauth error"),
INSUFFICIENT_BALANCE_EXCEPTION(HttpStatus.BAD_REQUEST, "Insufficient balance"),
INSUFFICIENT_DONATION_EXCEPTION(HttpStatus.BAD_REQUEST, "Insufficient donation amount"),
INVALID_JSON_INPUT_EXCEPTION(HttpStatus.BAD_REQUEST, "입력 형식이 맞지 않습니다."),

// 401 UNAUTHORIZED
USERNAME_NOT_FOUND_EXCEPTION(HttpStatus.BAD_REQUEST, "Username not found"),
INVALID_ID_PASSWORD_EXCEPTION(HttpStatus.UNAUTHORIZED, "ID/password error"),
INVALID_GOOGLE_TOKEN_EXCEPTION(HttpStatus.UNAUTHORIZED, "Invalid Google Token"),
INVALID_JWT_EXCEPTION(HttpStatus.UNAUTHORIZED, "Invalid JWT"),
Expand All @@ -32,6 +36,7 @@ public enum Error {


// 404 NOT FOUND
FCM_TOKEN_NOT_FOUND_EXCEPTION(HttpStatus.NOT_FOUND, "FCM_token not found"),
GIFT_NOT_FOUND_EXCEPTION(HttpStatus.NOT_FOUND, "That gift does not exist"),
GIFTBOX_NOT_FOUND_EXCEPTION(HttpStatus.NOT_FOUND, "That giftbox does not exist"),
BENEFIT_NOT_FOUND_EXCEPTION(HttpStatus.NOT_FOUND, "Benefit info does not exist"),
Expand Down
1 change: 1 addition & 0 deletions src/main/java/zero/eight/donut/exception/Success.java
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public enum Success {
GET_HISTORY_RECEIVER_BENEFIT_SUCCESS(HttpStatus.OK, " Success in getting benefit history list"),

//201 CREATED SUCCESS
CREATE_FCM_TOKEN_SUCCESS(HttpStatus.CREATED, "Successfully update fcmToken"),
SEND_MESSAGE_SUCCESS(HttpStatus.CREATED, "Send a message successfully"),
UPLOAD_GIFT_SUCCESS(HttpStatus.CREATED, "Successfully upload gifticon"),
ASSIGN_BENEFIT_SUCCESS(HttpStatus.CREATED, "Successfully assigned benefits"),
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/zero/eight/donut/repository/FcmTokenRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
package zero.eight.donut.repository;

import org.springframework.data.jpa.repository.JpaRepository;
import zero.eight.donut.domain.FcmToken;

import java.util.Optional;

public interface FcmTokenRepository extends JpaRepository<FcmToken, Long> {
Optional<FcmToken> findByMemberId(Long memberId);
}
15 changes: 10 additions & 5 deletions src/main/java/zero/eight/donut/repository/GiftRepository.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import zero.eight.donut.domain.Gift;
import zero.eight.donut.domain.Giver;

import java.time.LocalDateTime;
import java.util.List;
Expand All @@ -14,17 +13,23 @@ public interface GiftRepository extends JpaRepository<Gift, Long> {
Optional<Gift> findById(Long giftId);
List<Gift> findAllByGiftboxId(Long giftbox_id);
List<Gift> findAllByGiverIdAndCreatedAtBetween(Long giverId, LocalDateTime startDate, LocalDateTime endDate);
@Query(value = "SELECT * FROM gift g WHERE g.store = :storeName AND g.is_assigned = false", nativeQuery = true)
List<Gift> findByStoreAndIsAssigned(String storeName);
@Query(value = "SELECT * FROM gift g WHERE g.store = :storeName AND g.is_assigned = false AND g.status = 'UNUSED'", nativeQuery = true)
List<Gift> findByStoreAndIsAssignedAndUnused(String storeName);
@Query(value = "SELECT SUM(g.price) FROM gift g WHERE g.is_assigned = false", nativeQuery = true)
int sumByNotAssigned();
@Query(value = "SELECT SUM(g.price) FROM gift g WHERE g.store = :storeName", nativeQuery = true)
int sumByStoreName(String storeName);
@Query(value = "SELECT SUM(g.price) FROM gift g WHERE g.store = :storeName AND g.is_assigned = false AND g.status = 'UNUSED'", nativeQuery = true)
int sumAvailableGiftsByStoreName(String storeName);

@Query(value = "SELECT * FROM gift g WHERE g.status = :status AND g.is_assigned = false AND g.auto_donation = true" +
"AND g.due_date BETWEEN :startDate AND :endDate", nativeQuery = true)
List<Gift> findAllByNotAssignedAndStatusAndDueDateBetween(String status,LocalDateTime startDate, LocalDateTime endDate);

@Query(value = "SELECT * FROM gift g WHERE g.status = 'STORED' AND g.is_assigned = false AND g.auto_donation = true AND g.due_date = :endDate", nativeQuery = true)
List<Gift> findAllByNotAssignedAndStoredAndAutoDonation(@Param("endDate") LocalDateTime endDate);

@Query(value = "SELECT * FROM gift g WHERE g.status = 'UNUSED' AND g.is_assigned = true AND g.due_date = :endDate", nativeQuery = true)
List<Gift> findAllByAssignedAndUnused(@Param("endDate") LocalDateTime endDate);

@Query(value = "SELECT * FROM gift g WHERE g.auto_donation = true AND g.status = 'STORED' AND g.giver_id = :giverId AND g.due_date >= :today", nativeQuery = true)
List<Gift> findAllByGiverAndStatusAndDueDateAfterOrToday(@Param("giverId") Long giverId, @Param("today") LocalDateTime today);

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,10 @@
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.query.Param;
import zero.eight.donut.domain.Gift;
import zero.eight.donut.domain.Giftbox;
import zero.eight.donut.domain.enums.Store;

import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
Expand All @@ -24,6 +24,9 @@ public interface GiftboxRepository extends JpaRepository<Giftbox, Long> {
@Query("SELECT gb FROM Giftbox gb WHERE gb.receiver.id = ?1 and gb.isAvailable = true")
List<Giftbox> findAllByReceiverIdAndIsAvailable(Long receiver_id);

@Query(value = "SELECT * FROM Giftbox gb WHERE gb.isAvailable = true AND gb.dueDate = :endDate", nativeQuery = true)
List<Giftbox> findAllByIsAvailableAndDueDate(@Param("endDate") LocalDateTime endDate);

// @Query("SELECT SUM(gb.amount) FROM Giftbox gb WHERE gb.store = ?1")
// Integer getSumByStore(Store store);

Expand Down
7 changes: 5 additions & 2 deletions src/main/java/zero/eight/donut/service/DonationService.java
Original file line number Diff line number Diff line change
@@ -1,12 +1,14 @@
package zero.eight.donut.service;

import com.google.firebase.messaging.FirebaseMessagingException;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import zero.eight.donut.common.response.ApiResponse;
import zero.eight.donut.config.firebase.FcmUtils;
import zero.eight.donut.config.jwt.AuthUtils;
import zero.eight.donut.domain.Gift;
import zero.eight.donut.domain.Giver;
Expand All @@ -26,18 +28,19 @@
@Service
public class DonationService {

private final AuthUtils authUtils;
private final FcmUtils fcmUtils;
private final SerialDonationService donationService;
private final GiftRepository giftRepository;

@Async
@Transactional
@Scheduled(cron = "0 0 0 * * *")
public void autoDonate(){
public void autoDonate() throws FirebaseMessagingException {
List<Gift> giftList = giftRepository.findAllByNotAssignedAndStatusAndDueDateBetween( "STORED", LocalDateTime.now(), LocalDateTime.now().minusDays(30));
for (Gift gift : giftList) {
gift.updateStatus("UNUSED");
giftRepository.save(gift);
fcmUtils.sendMessage(gift.getGiver().getId(), "wallet: D-30", "Your item" + gift.getProduct() + "is donated now!");
}
}

Expand Down
Loading

0 comments on commit 77497f6

Please sign in to comment.