Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

v1.3.0 #272

Merged
merged 3 commits into from
Feb 4, 2024
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
3 changes: 3 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,9 @@ dependencies {
// AWS
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'

// Firebase-admin
implementation 'com.google.firebase:firebase-admin:9.2.0'

// Test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testCompileOnly 'org.projectlombok:lombok'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
import com.depromeet.domain.mission.domain.Mission;
import com.depromeet.domain.missionRecord.domain.ImageUploadStatus;
import com.depromeet.domain.missionRecord.domain.MissionRecord;
import com.depromeet.domain.notification.application.NotificationService;
import com.depromeet.domain.notification.domain.NotificationType;
import com.depromeet.global.config.fcm.FcmService;
import com.depromeet.global.error.exception.CustomException;
import com.depromeet.global.error.exception.ErrorCode;
import com.depromeet.global.util.MemberUtil;
Expand All @@ -26,9 +29,14 @@
@RequiredArgsConstructor
@Transactional
public class FollowService {
private final NotificationService notificationService;
private final MemberUtil memberUtil;
private final MemberRepository memberRepository;
private final MemberRelationRepository memberRelationRepository;
private final FcmService fcmService;

private static final String PUSH_SERVICE_TITLE = "10MM";
private static final String PUSH_SERVICE_CONTENT = "%s님이 회원님을 팔로우하기 시작했습니다🥳";

public void createFollow(FollowCreateRequest request) {
final Member currentMember = memberUtil.getCurrentMember();
Expand All @@ -43,6 +51,14 @@ public void createFollow(FollowCreateRequest request) {

MemberRelation memberRelation =
MemberRelation.createMemberRelation(currentMember, targetMember);

fcmService.sendMessageSync(
targetMember.getFcmInfo().getFcmToken(),
PUSH_SERVICE_TITLE,
String.format(PUSH_SERVICE_CONTENT, currentMember.getUsername()));
notificationService.createNotification(
NotificationType.FOLLOW, currentMember, targetMember);

memberRelationRepository.save(memberRelation);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import com.depromeet.domain.member.application.MemberService;
import com.depromeet.domain.member.dto.request.NicknameCheckRequest;
import com.depromeet.domain.member.dto.request.NicknameUpdateRequest;
import com.depromeet.domain.member.dto.request.UpdateFcmTokenRequest;
import com.depromeet.domain.member.dto.response.MemberFindOneResponse;
import com.depromeet.domain.member.dto.response.MemberSearchResponse;
import com.depromeet.domain.member.dto.response.MemberSocialInfoResponse;
Expand Down Expand Up @@ -79,4 +80,19 @@ public ResponseEntity<Void> memberNicknameUpdate(
memberService.updateMemberNickname(reqest);
return ResponseEntity.ok().build();
}

@Operation(summary = "토글 여부 변경", description = "기존 토글 값을 변경합니다.")
@PatchMapping("/alarm")
public ResponseEntity<Void> memberToggleAppAlarmStateUpdate() {
memberService.toggleAppAlarm();
return ResponseEntity.ok().build();
}

@Operation(summary = "FCM 토큰 갱신", description = "FCM 토큰을 갱신합니다.")
@PatchMapping("/fcm-token")
public ResponseEntity<Void> memberFcmTokenUpdate(
@Valid @RequestBody UpdateFcmTokenRequest updateFcmTokenRequest) {
memberService.updateFcmToken(updateFcmTokenRequest);
return ResponseEntity.ok().build();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
import com.depromeet.domain.member.domain.Profile;
import com.depromeet.domain.member.dto.request.NicknameCheckRequest;
import com.depromeet.domain.member.dto.request.NicknameUpdateRequest;
import com.depromeet.domain.member.dto.request.UpdateFcmTokenRequest;
import com.depromeet.domain.member.dto.response.MemberFindOneResponse;
import com.depromeet.domain.member.dto.response.MemberSearchResponse;
import com.depromeet.domain.member.dto.response.MemberSocialInfoResponse;
Expand Down Expand Up @@ -163,4 +164,14 @@ private ImageFileExtension getImageFileExtension(Profile profile) {
}
return imageFileExtension;
}

public void toggleAppAlarm() {
final Member currentMember = memberUtil.getCurrentMember();
currentMember.toggleAppAlarmState(currentMember.getFcmInfo());
}

public void updateFcmToken(UpdateFcmTokenRequest updateFcmTokenRequest) {
final Member currentMember = memberUtil.getCurrentMember();
currentMember.updateFcmToken(currentMember.getFcmInfo(), updateFcmTokenRequest.fcmToken());
}
}
42 changes: 42 additions & 0 deletions src/main/java/com/depromeet/domain/member/domain/FcmInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
package com.depromeet.domain.member.domain;

import jakarta.persistence.Embeddable;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Embeddable
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class FcmInfo {

private String fcmToken;
private Boolean appAlarm;

@Builder(access = AccessLevel.PRIVATE)
private FcmInfo(String fcmToken, Boolean appAlarm) {
this.fcmToken = fcmToken;
this.appAlarm = appAlarm;
}

public static FcmInfo createFcmInfo() {
return FcmInfo.builder().fcmToken("").appAlarm(true).build();
}

public static FcmInfo toggleAlarm(FcmInfo fcmState) {
return new FcmInfo(fcmState.getFcmToken(), !fcmState.getAppAlarm());
}

public static FcmInfo disableAlarm(FcmInfo fcmInfo) {
return new FcmInfo(fcmInfo.getFcmToken(), false);
}

public static FcmInfo deleteToken(FcmInfo fcmInfo) {
return new FcmInfo("", fcmInfo.getAppAlarm());
}

public static FcmInfo updateToken(FcmInfo fcmState, String fcmToken) {
return new FcmInfo(fcmToken, fcmState.getAppAlarm());
}
}
14 changes: 14 additions & 0 deletions src/main/java/com/depromeet/domain/member/domain/Member.java
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ public class Member extends BaseTimeEntity {

@Embedded private OauthInfo oauthInfo;

@Embedded private FcmInfo fcmInfo = FcmInfo.createFcmInfo();

@Enumerated(EnumType.STRING)
private MemberStatus status;

Expand All @@ -58,6 +60,7 @@ public class Member extends BaseTimeEntity {
private Member(
Profile profile,
OauthInfo oauthInfo,
FcmInfo fcmInfo,
MemberStatus status,
MemberRole role,
MemberVisibility visibility,
Expand All @@ -66,6 +69,7 @@ private Member(
String password) {
this.profile = profile;
this.oauthInfo = oauthInfo;
this.fcmInfo = fcmInfo;
this.status = status;
this.role = role;
this.visibility = visibility;
Expand All @@ -78,6 +82,7 @@ public static Member createNormalMember(OauthInfo oauthInfo, String nickname) {
return Member.builder()
.profile(Profile.createProfile(nickname, null))
.oauthInfo(oauthInfo)
.fcmInfo(FcmInfo.createFcmInfo())
.status(MemberStatus.NORMAL)
.role(MemberRole.USER)
.visibility(MemberVisibility.PUBLIC)
Expand Down Expand Up @@ -108,6 +113,15 @@ public void withdrawal() {
throw new CustomException(ErrorCode.MEMBER_ALREADY_DELETED);
}
this.status = MemberStatus.DELETED;
this.fcmInfo = FcmInfo.disableAlarm(FcmInfo.createFcmInfo());
}

public void toggleAppAlarmState(FcmInfo fcmState) {
this.fcmInfo = FcmInfo.toggleAlarm(fcmState);
}

public void updateFcmToken(FcmInfo fcmState, String fcmToken) {
this.fcmInfo = FcmInfo.updateToken(fcmState, fcmToken);
}

public void updateNickname(String nickname) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.depromeet.domain.member.dto.request;

import io.swagger.v3.oas.annotations.media.Schema;

public record UpdateFcmTokenRequest(
@Schema(description = "FCM 토큰", defaultValue = "fcm-token-value") String fcmToken) {}
Empty file.
Empty file.
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.depromeet.domain.notification.application;

import com.depromeet.domain.member.domain.Member;
import com.depromeet.domain.notification.dao.NotificationRepository;
import com.depromeet.domain.notification.domain.Notification;
import com.depromeet.domain.notification.domain.NotificationType;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

@Service
@RequiredArgsConstructor
@Transactional
public class NotificationService {

private final NotificationRepository notificationRepository;

public void createNotification(
NotificationType notificationType, Member currentMember, Member targetMember) {
Notification notification =
Notification.createNotification(notificationType, currentMember, targetMember);
notificationRepository.save(notification);
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package com.depromeet.domain.notification.dao;

import com.depromeet.domain.notification.domain.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,56 @@
package com.depromeet.domain.notification.domain;

import com.depromeet.domain.common.model.BaseTimeEntity;
import com.depromeet.domain.member.domain.Member;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.EnumType;
import jakarta.persistence.Enumerated;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.OneToOne;
import lombok.AccessLevel;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;

@Entity
@Getter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
public class Notification extends BaseTimeEntity {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
@Column(name = "notification_id")
private Long id;

@Enumerated(EnumType.STRING)
private NotificationType notificationType;

@OneToOne
@JoinColumn(name = "source_id")
private Member sourceMember;

@OneToOne
@JoinColumn(name = "target_id")
private Member targetMember;

@Builder(access = AccessLevel.PRIVATE)
private Notification(
NotificationType notificationType, Member sourceMember, Member targetMember) {
this.notificationType = notificationType;
this.sourceMember = sourceMember;
this.targetMember = targetMember;
}

public static Notification createNotification(
NotificationType notificationType, Member currentMember, Member targetMember) {
return Notification.builder()
.notificationType(notificationType)
.sourceMember(currentMember)
.targetMember(targetMember)
.build();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
package com.depromeet.domain.notification.domain;

import lombok.AllArgsConstructor;
import lombok.Getter;

@Getter
@AllArgsConstructor
public enum NotificationType {
FOLLOW("팔로우"),
;

private final String value;
}
Empty file.
38 changes: 38 additions & 0 deletions src/main/java/com/depromeet/global/config/fcm/FcmConfig.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
package com.depromeet.global.config.fcm;

import com.google.auth.oauth2.GoogleCredentials;
import com.google.firebase.FirebaseApp;
import com.google.firebase.FirebaseOptions;
import jakarta.annotation.PostConstruct;
import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;

@Configuration
@Slf4j
public class FcmConfig {

@Value("${fcm.certification}")
private String fcmCertification;

@PostConstruct
public void init() {
try {
if (FirebaseApp.getApps().isEmpty()) {
FirebaseOptions options =
new FirebaseOptions.Builder()
.setCredentials(
GoogleCredentials.fromStream(
new ByteArrayInputStream(
fcmCertification.getBytes(
StandardCharsets.UTF_8))))
.build();
FirebaseApp.initializeApp(options);
}
} catch (Exception e) {
log.error("FCM initializing Exception: {}", e.getStackTrace()[0]);
}
}
}
40 changes: 40 additions & 0 deletions src/main/java/com/depromeet/global/config/fcm/FcmService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
package com.depromeet.global.config.fcm;

import com.google.api.core.ApiFuture;
import com.google.firebase.messaging.*;
import java.util.List;
import org.springframework.stereotype.Service;

@Service
public class FcmService {

/**
* 참고: https://firebase.google.com/support/release-notes/admin/java 위 레퍼런스에 의거하여
* sendMulticastAsync 는 Deprecated 되어 sendEachForMulticastAsync
*
* @param tokenList: 푸시 토큰 리스트
* @param title: 알림 제목
* @param content: 알림 내용
* @return ApiFuture<BatchResponse>
*/
public ApiFuture<BatchResponse> sendGroupMessageAsync(
List<String> tokenList, String title, String content) {
MulticastMessage multicast =
MulticastMessage.builder()
.addAllTokens(tokenList)
.setNotification(
Notification.builder().setTitle(title).setBody(content).build())
.build();
return FirebaseMessaging.getInstance().sendEachForMulticastAsync(multicast);
}

public ApiFuture<String> sendMessageSync(String token, String title, String content) {
Message message =
Message.builder()
.setToken(token)
.setNotification(
Notification.builder().setTitle(title).setBody(content).build())
.build();
return FirebaseMessaging.getInstance().sendAsync(message);
}
}
3 changes: 3 additions & 0 deletions src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,6 @@ springdoc:
logging:
level:
com.depromeet.domain.*.api.*: debug

fcm:
certification: ${FCM_CERTIFICATION:}
Loading
Loading