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 .github/workflows/deploy.yml
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,12 @@ jobs:
echo "${{ secrets.APPLICATION_DEV_YML }}" >> ./application-dev.yml
shell: bash

- name: Create Firebase Config Directory
run: |
mkdir -p src/main/resources/firebase
echo "${{ secrets.FIREBASE_SERVICE_KEY }}" | base64 --decode > src/main/resources/firebase/catchmate-9653a-firebase-adminsdk-kh06c-0315680471.json
shell: bash

# gradlew에 실행 권한을 부여합니다.
- name: Grant execute permission for gradlew
run: chmod +x gradlew
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -37,3 +37,4 @@ out/
.vscode/

application-dev.yml
/src/main/resources/firebase/catchmate-9653a-firebase-adminsdk-kh06c-0315680471.json
4 changes: 4 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,10 @@ dependencies {

// AWS S3
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

// Firebase
implementation 'com.google.firebase:firebase-admin:9.2.0'
implementation group: 'com.squareup.okhttp3', name: 'okhttp', version: '4.2.2'
}

//tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,8 @@
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import java.io.IOException;

@Tag(name = "직관 신청 관련 API")
@RestController
@RequestMapping("/enroll")
Expand Down Expand Up @@ -81,14 +83,14 @@ public NewEnrollCountInfo getNewEnrollmentListCount(@JwtValidation Long userId)
@PatchMapping("/{enrollId}/accept")
@Operation(summary = "받은 직관 신청 수락 API", description = "내가 받은 직관 신청을 수락하는 API 입니다.")
public UpdateEnrollInfo acceptEnroll(@PathVariable Long enrollId,
@JwtValidation Long userId) {
@JwtValidation Long userId) throws IOException {
return enrollService.acceptEnroll(enrollId, userId);
}

@PatchMapping("/{enrollId}/reject")
@Operation(summary = "받은 직관 신청 거절 API", description = "내가 받은 직관 신청을 거절하는 API 입니다.")
public UpdateEnrollInfo rejectEnroll(@PathVariable Long enrollId,
@JwtValidation Long userId) {
@JwtValidation Long userId) throws IOException {
return enrollService.rejectEnroll(enrollId, userId);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
import com.back.catchmate.domain.board.dto.BoardResponse.BoardInfo;
import com.back.catchmate.domain.board.entity.Board;
import com.back.catchmate.domain.enroll.dto.EnrollRequest.CreateEnrollRequest;
import com.back.catchmate.domain.enroll.dto.EnrollResponse;
import com.back.catchmate.domain.enroll.dto.EnrollResponse.CancelEnrollInfo;
import com.back.catchmate.domain.enroll.dto.EnrollResponse.CreateEnrollInfo;
import com.back.catchmate.domain.enroll.dto.EnrollResponse.EnrollReceiveInfo;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public interface EnrollService {

NewEnrollCountInfo getNewEnrollListCount(Long userId);

UpdateEnrollInfo acceptEnroll(Long enrollId, Long userId);
UpdateEnrollInfo acceptEnroll(Long enrollId, Long userId) throws IOException;

UpdateEnrollInfo rejectEnroll(Long enrollId, Long userId);
UpdateEnrollInfo rejectEnroll(Long enrollId, Long userId) throws IOException;
}
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import com.back.catchmate.domain.enroll.entity.AcceptStatus;
import com.back.catchmate.domain.enroll.entity.Enroll;
import com.back.catchmate.domain.enroll.repository.EnrollRepository;
import com.back.catchmate.domain.notification.service.FCMService;
import com.back.catchmate.domain.user.entity.User;
import com.back.catchmate.domain.user.repository.UserRepository;
import com.back.catchmate.global.error.ErrorCode;
Expand All @@ -23,12 +24,17 @@
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.io.IOException;

import static com.back.catchmate.domain.notification.message.NotificationMessages.*;

@Service
@RequiredArgsConstructor
public class EnrollServiceImpl implements EnrollService {
private final EnrollRepository enrollRepository;
private final UserRepository userRepository;
private final BoardRepository boardRepository;
private final FCMService fcmService;
private final EnrollConverter enrollConverter;

@Override
Expand Down Expand Up @@ -120,7 +126,7 @@ public EnrollResponse.NewEnrollCountInfo getNewEnrollListCount(Long userId) {

@Override
@Transactional
public UpdateEnrollInfo acceptEnroll(Long enrollId, Long userId) {
public UpdateEnrollInfo acceptEnroll(Long enrollId, Long userId) throws IOException {
User loginUser = userRepository.findById(userId)
.orElseThrow(() -> new BaseException(ErrorCode.USER_NOT_FOUND));

Expand All @@ -136,13 +142,16 @@ public UpdateEnrollInfo acceptEnroll(Long enrollId, Long userId) {
throw new BaseException(ErrorCode.ENROLL_ACCEPT_INVALID);
}

// 직관 신청자에게 수락 푸시 알림 메세지 전송
fcmService.sendMessage(enrollApplicant.getFcmToken(), ENROLLMENT_ACCEPT_TITLE, ENROLLMENT_ACCEPT_BODY, enroll.getBoard().getId());

enroll.setAcceptStatus(AcceptStatus.ACCEPTED);
return enrollConverter.toUpdateEnrollInfo(enroll, AcceptStatus.ACCEPTED);
}

@Override
@Transactional
public UpdateEnrollInfo rejectEnroll(Long enrollId, Long userId) {
public UpdateEnrollInfo rejectEnroll(Long enrollId, Long userId) throws IOException {
User loginUser = userRepository.findById(userId)
.orElseThrow(() -> new BaseException(ErrorCode.USER_NOT_FOUND));

Expand All @@ -158,6 +167,9 @@ public UpdateEnrollInfo rejectEnroll(Long enrollId, Long userId) {
throw new BaseException(ErrorCode.ENROLL_REJECT_INVALID);
}

// 직관 신청자에게 거절 푸시 알림 메세지 전송
fcmService.sendMessage(enrollApplicant.getFcmToken(), ENROLLMENT_REJECT_TITLE, ENROLLMENT_REJECT_BODY, enroll.getBoard().getId());

enroll.setAcceptStatus(AcceptStatus.REJECTED);
return enrollConverter.toUpdateEnrollInfo(enroll, AcceptStatus.REJECTED);
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.back.catchmate.domain.notification.converter;

import com.back.catchmate.domain.notification.dto.NotificationResponse.CreateNotificationInfo;
import com.back.catchmate.domain.notification.entity.Notification;
import org.springframework.stereotype.Component;

import java.time.LocalDateTime;

@Component
public class NotificationConverter {
public CreateNotificationInfo toCreateNotificationInfo(Notification notification) {
return CreateNotificationInfo.builder()
.notificationId(notification.getId())
.createdAt(LocalDateTime.now())
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package com.back.catchmate.domain.notification.dto;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;

@Getter
@Builder
@AllArgsConstructor
public class FCMMessageRequest {
private boolean validateOnly;
private Message message;

@Getter
@Builder
@AllArgsConstructor
public static class Message {
private Notification notification;
private String token;
private Data data;
}

@Getter
@Builder
@AllArgsConstructor
public static class Notification {
private String title;
private String body;
}

@Getter
@Builder
@AllArgsConstructor
public static class Data {
private String boardId;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
package com.back.catchmate.domain.notification.dto;

import com.back.catchmate.domain.board.dto.BoardResponse.BoardInfo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

import java.time.LocalDateTime;

public abstract class NotificationResponse {

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class NotificationInfo {
private Long notificationId;
private BoardInfo boardInfo;
private String title;
private String body;
private LocalDateTime createdAt;
private boolean isRead;
}

@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public static class CreateNotificationInfo {
private Long notificationId;
private LocalDateTime createdAt;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -46,4 +46,13 @@ public class Notification extends BaseTimeEntity {

@Column(nullable = false)
private boolean isRead;

// 알림 수신 여부 설정 메서드
public void markAsRead() {
this.isRead = true;
}

public boolean isNotRead() {
return !this.isRead;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
package com.back.catchmate.domain.notification.message;

public class NotificationMessages {
public static final String ENROLLMENT_ACCEPT_TITLE = "직관 신청 수락 안내 문자";
public static final String ENROLLMENT_REJECT_TITLE = "직관 신청 수락 안내 문자";
public static final String ENROLLMENT_ACCEPT_BODY = "귀하의 직관 신청이 승인되었습니다. 참여를 환영합니다!";
public static final String ENROLLMENT_REJECT_BODY = "귀하의 직관 신청이 거절되었습니다. 다음 기회에 참여를 부탁드립니다.";
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
package com.back.catchmate.domain.notification.repository;

import com.back.catchmate.domain.notification.entity.Notification;
import org.springframework.data.jpa.repository.JpaRepository;

public interface NotificationRepository extends JpaRepository<Notification, Long> {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
package com.back.catchmate.domain.notification.service;

import com.back.catchmate.domain.notification.dto.FCMMessageRequest;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.auth.oauth2.GoogleCredentials;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.io.ClassPathResource;
import org.springframework.http.HttpHeaders;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;

import java.io.IOException;
import java.util.List;

@Slf4j
@Service
@RequiredArgsConstructor
public class FCMService {
@Value("${fcm.firebase_config_path}")
private String FIREBASE_CONFIG_PATH;
@Value("${fcm.firebase_api_uri}")
private String FIREBASE_ALARM_SEND_API_URI;

private final ObjectMapper objectMapper;

// Firebase로 부터 Access Token을 가져오는 메서드
private String getAccessToken() throws IOException {
GoogleCredentials googleCredentials = GoogleCredentials
.fromStream(new ClassPathResource(FIREBASE_CONFIG_PATH).getInputStream())
.createScoped(List.of("https://www.googleapis.com/auth/cloud-platform"));

googleCredentials.refreshIfExpired();

return googleCredentials.getAccessToken().getTokenValue();
}

// 알림 파라미터들을 요구하는 body 형태로 가공
public String makeMessage(String targetToken, String title, String body, Long boardId) throws JsonProcessingException {
FCMMessageRequest fcmMessage = FCMMessageRequest.builder()
.message(
FCMMessageRequest.Message.builder()
.token(targetToken)
.notification(
FCMMessageRequest.Notification.builder()
.title(title)
.body(body)
.build()
)
.data(
FCMMessageRequest.Data.builder()
.boardId(String.valueOf(boardId))
.build()
)
.build()
)
.validateOnly(false)
.build();

return objectMapper.writeValueAsString(fcmMessage);
}

// 알림 푸쉬를 보내는 역할을 하는 메서드
@Async("asyncTask")
public void sendMessage(String targetToken, String title, String body, Long boardId) throws IOException {
String message = makeMessage(targetToken, title, body, boardId);

OkHttpClient client = new OkHttpClient();
RequestBody requestBody = RequestBody.create(message, MediaType.get("application/json; charset=utf-8"));

Request request = new Request.Builder()
.url(FIREBASE_ALARM_SEND_API_URI)
.post(requestBody)
.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + getAccessToken())
.addHeader(HttpHeaders.CONTENT_TYPE, "application/json; UTF-8")
.build();

Response response = client.newCall(request).execute();
log.info(response.body().string());
}
}
Loading