Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -71,7 +71,13 @@ dependencies {
// Apple id_token signature verify
implementation 'com.nimbusds:nimbus-jose-jwt:9.31'

<<<<<<< HEAD
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'
=======

// fcm
implementation 'com.google.firebase:firebase-admin:9.2.0'
>>>>>>> 7451396 (feat: fcm 연결)
}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ public class MatchingResponseDto {
private final String walkerProfile;
private final String walkMatchingStatus;
private final Long boardId;
private final String content;


public MatchingResponseDto() {
Expand All @@ -36,5 +37,6 @@ public MatchingResponseDto() {
this.walkerProfile = null;
this.walkMatchingStatus = null;
this.boardId = null;
this.content = null;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -82,7 +82,6 @@ public Optional<ApplyInfoDto> getApplyInfoResponse(Long boardId, Long memberId,
@Override
public List<MatchingResponseDto> getApplyInfoResponses(Long memberId, WalkMatchingStatus status, String delYn) {
QDog dog = QDog.dog;
QMember member = QMember.member;
QBoard board = QBoard.board;
QApply apply = QApply.apply;
QAddress address = QAddress.address;
Expand All @@ -97,13 +96,11 @@ public List<MatchingResponseDto> getApplyInfoResponses(Long memberId, WalkMatchi
}
// 매칭 확정 : 지원 상태가 CONFIRMED이고 날짜 안 지남
else if (status.equals(WalkMatchingStatus.BEFORE)) {
System.out.println("hello");
builder.and(apply.matchingStatus.eq(MatchingStatus.CONFIRMED))
.and(board.startTime.after(now));
}
// 산책 완료 : 지원 상태가 CONFIRMED이고 날짜 지남
else if (status.equals(WalkMatchingStatus.AFTER)) {
System.out.println("hello??");
builder.and(apply.matchingStatus.eq(MatchingStatus.CONFIRMED))
.and(board.startTime.before(now));
}
Expand Down Expand Up @@ -146,14 +143,15 @@ else if (status.equals(WalkMatchingStatus.REJECT)) {
Expressions.nullExpression(String.class),
Expressions.nullExpression(String.class),
Expressions.asString(status.name()).as("walkMatchingStatus"),
apply.boardId.as("boardId")
board.boardId.as("boardId"),
board.content.as("content")
))
.from(apply)
.leftJoin(board)
.join(board)
.on(board.boardId.eq(apply.boardId)
.and(board.delYn.eq(delYn))
)
.leftJoin(dog)
.join(dog)
.on(dog.dogId.eq(board.dogId)
.and(dog.delYn.eq(delYn)))
.where(apply.memberId.eq(memberId)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -399,7 +399,8 @@ else if (status.equals(WalkMatchingStatus.REJECT)) {
member.name.as("walkerName"), // member와의 조인을 통해 walkerName 설정
member.profile.as("walkerProfile"), // member와의 조인을 통해 walkerProfile 설정
Expressions.asString(status.name()).as("walkMatchingStatus"),
board.boardId.as("boardId")
board.boardId.as("boardId"),
board.content.as("content")
))
.from(board)
.join(dog)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package org.jullaene.walkmong_back.api.common.domain;

import jakarta.persistence.*;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.jullaene.walkmong_back.common.BaseEntity;

@Entity
@Table(name = "fcm_token")
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class FcmToken extends BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long fcmTokenId;

@Column(nullable = false)
private Long memberId;

@Column(nullable = false, unique = true)
private String token;

public FcmToken(Long memberId, String token) {
this.memberId = memberId;
this.token = token;
}

public Long getFcmTokenId () {
return this.fcmTokenId;
}

public String getToken () {
return this.token;
}

public void updateToken(String token) {
this.token = token;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package org.jullaene.walkmong_back.api.common.dto.req;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class FcmTokenReq {
private final Long memberId;
private final String token;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.jullaene.walkmong_back.api.common.dto.req;

import lombok.AllArgsConstructor;
import lombok.Getter;

import java.util.List;

@Getter
@AllArgsConstructor
public class MultiNotificationReq {
private final List<Long> memberIds;
private final String title;
private final String body;

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
package org.jullaene.walkmong_back.api.common.dto.req;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public class NotificationReq {
private final Long memberId;
private final String title;
private final String body;
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
package org.jullaene.walkmong_back.api.common.repository;

import org.jullaene.walkmong_back.api.common.domain.FcmToken;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import java.util.List;
import java.util.Optional;

@Repository
public interface FcmTokenRepository extends JpaRepository<FcmToken, Long> {
Optional<FcmToken> findByMemberIdAndDelYn(Long memberId, String delYn);

List<FcmToken> findAllByMemberIdInAndDelYn(List<Long> memberIds, String delYn);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
package org.jullaene.walkmong_back.api.common.rest;

import com.google.firebase.messaging.BatchResponse;
import lombok.RequiredArgsConstructor;
import org.jullaene.walkmong_back.api.common.dto.req.MultiNotificationReq;
import org.jullaene.walkmong_back.api.common.dto.req.NotificationReq;
import org.jullaene.walkmong_back.api.common.service.FcmService;
import org.jullaene.walkmong_back.api.common.service.FcmTokenService;
import org.jullaene.walkmong_back.common.BasicResponse;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;

@RestController
@RequiredArgsConstructor
@RequestMapping("/api/v1/notification")
public class FcmController {
private final FcmService fcmService;
private final FcmTokenService fcmTokenService;

/**
* FCM 토큰 등록/업데이트
* */
@PostMapping("/token")
public ResponseEntity<BasicResponse<Long>> registerToken(@RequestParam(value = "token") String token) {
return ResponseEntity.ok(BasicResponse.ofSuccess(fcmTokenService.saveOrUpdateToken(token)));
}

/**
* 단일 사용자에게 알림 전송
* */
@PostMapping("/send")
public ResponseEntity<String> sendNotification(@RequestBody NotificationReq request) {
String messageId = fcmService.sendNotification(
request.getMemberId(),
request.getTitle(),
request.getBody()
);

return ResponseEntity.ok(messageId);
}

/**
* 다중 사용자에게 알림 전송
* */
@PostMapping("/send/users")
public ResponseEntity<BatchResponse> sendToMultipleUsers(@RequestBody MultiNotificationReq request) {
BatchResponse response = fcmService.sendNotificationToMultipleUsers(
request.getMemberIds(),
request.getTitle(),
request.getBody()
);
return ResponseEntity.ok(response);
}

/**
* FCM Token 삭제 (soft delete)
* */
@DeleteMapping("/token")
public ResponseEntity<BasicResponse<String>> markDeletedFcmToken () {
return ResponseEntity.ok(BasicResponse.ofSuccess(fcmTokenService.removeToken()));
}


}
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
package org.jullaene.walkmong_back.api.common.rest;

import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.ResponseBody;

@Controller
@RequiredArgsConstructor
public class FcmPageController {

@Value("${firebase.config.apiKey}")
private String apiKey;

@Value("${firebase.config.authDomain}")
private String authDomain;

@Value("${firebase.config.projectId}")
private String projectId;

@Value("${firebase.config.storageBucket}")
private String storageBucket;

@Value("${firebase.config.messagingSenderId}")
private String messagingSenderId;

@Value("${firebase.config.appId}")
private String appId;

@Value("${firebase.config.vapidKey}")
private String vapidKey;
/**
* fcmToken 테스트 전
* fcmToken 발급을 위한 웹
* */
@GetMapping("/fcm")
public String fcmPage(Model model) {
model.addAttribute("apiKey", apiKey);
model.addAttribute("authDomain", authDomain);
model.addAttribute("projectId", projectId);
model.addAttribute("storageBucket", storageBucket);
model.addAttribute("messagingSenderId", messagingSenderId);
model.addAttribute("appId", appId);
model.addAttribute("vapidKey", vapidKey);
return "fcm";
}


/**
* Service Worker Script 반환
*/
@GetMapping(value = "/firebase-messaging-sw.js", produces = "application/javascript")
@ResponseBody
public String firebaseMessagingSw() {
return String.format("""
importScripts('https://cdnjs.cloudflare.com/ajax/libs/firebase/9.22.0/firebase-app-compat.min.js');
importScripts('https://cdnjs.cloudflare.com/ajax/libs/firebase/9.22.0/firebase-messaging-compat.min.js');

firebase.initializeApp({
apiKey: "%s",
authDomain: "%s",
projectId: "%s",
storageBucket: "%s",
messagingSenderId: "%s",
appId: "%s"
});

const messaging = firebase.messaging();

messaging.onBackgroundMessage((payload) => {
console.log('[firebase-messaging-sw.js] Received background message:', payload);

const notificationTitle = payload.data.title;
const notificationBody = payload.data.body;

const notificationOptions = {
body: notificationBody,
// icon: '/firebase-logo.png' // 원하는 아이콘 경로
};

return self.registration.showNotification(notificationTitle, notificationOptions);
});
""",
apiKey,
authDomain,
projectId,
storageBucket,
messagingSenderId,
appId
);
}

}
Loading
Loading