Skip to content

Commit

Permalink
추가: FCM 알림 추가
Browse files Browse the repository at this point in the history
  • Loading branch information
rrosiee committed May 30, 2024
1 parent 73ce57d commit a43525d
Show file tree
Hide file tree
Showing 35 changed files with 352 additions and 415 deletions.
20 changes: 13 additions & 7 deletions .github/workflows/main.yml
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,14 @@ jobs:
chmod +x ./gradlew
./gradlew clean build -x test
# dockerfile을 통해 이미지를 빌드하고, 이를 docker repo로 push 합니다.
# 이 때 사용되는 ${{ secrets.DOCKER_REPO }}/directors-dev 가 위에서 만든 도커 repository 입니다.
- name: create firebase key
run: |
cd ./src/main/resources
ls -a .
touch ./firebase-service-key.json
echo "${{ secrets.FIREBASE_KEY }}" > ./firebase-service-key.json
shell: bash

- name: Docker build & push to docker repo
run: |
docker login -u ${{ secrets.DOCKER_USERNAME }} -p ${{ secrets.DOCKER_PASSWORD }}
Expand All @@ -43,8 +49,8 @@ jobs:
key: ${{ secrets.KEY }}
envs: GITHUB_SHA
script: |
sudo docker stop ticats
sudo docker rm ticats
sudo docker image rm gabang2/ticats:latest
sudo docker pull gabang2/ticats:latest
sudo docker run -d -p 8080:8080 --name ticats ${{ secrets.ENVS }} gabang2/ticats:latest
sudo docker stop ticats || true
sudo docker rm ticats || true
sudo docker image rm ${{ secrets.DOCKER_USERNAME }}/ticats:latest || true
sudo docker pull ${{ secrets.DOCKER_USERNAME }}/ticats:latest
sudo docker run -d -p 8080:8080 --name ticats -v ${{ secrets.DOCKER_USERNAME }}/ticats:latest
8 changes: 7 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -41,4 +41,10 @@ src/main/generated/
src/main/generated

### env ###
.env
.env

### static ###
src/main/java/project/backend/domain/notice
src/main/resources/static
src/main/resources/templates
src/main/resources/firebase-service-key.json
6 changes: 6 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,10 @@ repositories {
def queryDslVersion = '5.0.0'

dependencies {
// Spring boot web
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-jpa'
implementation 'org.springframework.boot:spring-boot-starter-thymeleaf'

compileOnly 'org.projectlombok:lombok'
runtimeOnly 'com.mysql:mysql-connector-j'
Expand Down Expand Up @@ -113,6 +115,10 @@ dependencies {
// jsoup(html parsing)
implementation 'org.jsoup:jsoup:1.14.3'

// FCM
implementation 'com.google.firebase:firebase-admin:8.2.0'
implementation 'com.squareup.okhttp3:okhttp:4.9.3'

}

tasks.named('test') {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
import project.backend.domain.culturalevnetcategory.entity.CategoryTitle;
import project.backend.domain.member.entity.Member;
import project.backend.domain.member.service.MemberJwtService;
import project.backend.domain.notification.service.NotificationService;
import project.backend.domain.visit.entity.CulturalEventVisit;
import project.backend.domain.visit.repository.CulturalEventVisitRepository;
import project.backend.global.error.exception.BusinessException;
Expand All @@ -31,6 +32,7 @@ public class CulturalEventService {
private final CulturalEventLikeRepository culturalEventLikeRepository;
private final MemberJwtService memberJwtService;
private final CulturalEventVisitRepository culturalEventVisitRepository;
private final NotificationService notificationService;

public List<CulturalEvent> getCulturalEventList(int page, int size, List<CategoryTitle> categories, String ordering, Boolean isOpened, Double latitude, Double longitude) {
return culturalEventRepository.getCulturalEventList(page, size, categories, ordering, isOpened, latitude, longitude);
Expand All @@ -51,17 +53,29 @@ public void like(Long id) {
CulturalEventLike culturalEventLike = new CulturalEventLike();
culturalEventLike.setCulturalEventLike(member, getCulturalEvent(id));
culturalEventLikeRepository.save(culturalEventLike);

// Set Ticket Open Notification
LocalDateTime ticketOpenDate = culturalEventLike.getCulturalEvent().getTicketOpenDate();
LocalDateTime currentTimePlus30Minutes = LocalDateTime.now().plusMinutes(30);

if (ticketOpenDate.isAfter(currentTimePlus30Minutes)) {
notificationService.createCulturalEventLikeNotification(culturalEventLike);
}
}

public void unLike(Long id) {

Member member = memberJwtService.getMember();
CulturalEvent culturalEvent = getCulturalEvent(id);
Optional<CulturalEventLike> culturalEventLike = culturalEvent.findMemberLike(member);
Optional<CulturalEventLike> culturalEventLikeOptional = culturalEvent.findMemberLike(member);

if (culturalEventLike.isEmpty()) {
if (culturalEventLikeOptional.isEmpty()) {
return;
}
culturalEventLikeRepository.delete(culturalEventLike.get());

CulturalEventLike culturalEventLike = culturalEventLikeOptional.get();
notificationService.deleteCulturalEventLikeNotification(culturalEventLike);
culturalEventLikeRepository.delete(culturalEventLike);
}

public void visit(CulturalEvent culturalEvent) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package project.backend.domain.fcm.controller;

import io.swagger.annotations.Api;
import lombok.RequiredArgsConstructor;
import org.springframework.web.bind.annotation.*;
import project.backend.domain.fcm.service.FcmService;

@RestController
@RequestMapping("/api/fcm")
@RequiredArgsConstructor
@Api(tags = "테스트용 FCM - 삭제 예정")
public class FcmController {

private final FcmService fcmService;

@PostMapping("/send")
public String sendNotification(@RequestParam String token,
@RequestParam String title,
@RequestParam String body){
fcmService.sendNotificationByToken(token, title, body);
return "Notification sent successfully";
}
}
51 changes: 51 additions & 0 deletions src/main/java/project/backend/domain/fcm/service/FcmService.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package project.backend.domain.fcm.service;

import com.google.firebase.messaging.FirebaseMessaging;
import com.google.firebase.messaging.Message;
import com.google.firebase.messaging.Notification;
import org.springframework.stereotype.Service;
import project.backend.domain.member.entity.Member;

@Service
public class FcmService {
public void sendNotification(Member member, String title, String body) {
String fcmToken = member.getFcmToken();

if (fcmToken != null) {
Notification notification = Notification.builder()
.setTitle(title)
.setBody(body)
.build();

Message message = Message.builder()
.setToken(fcmToken)
.setNotification(notification)
.build();
try {
String response = FirebaseMessaging.getInstance().send(message);
System.out.println("Successfully sent message: " + response);
} catch (Exception e) {
e.printStackTrace();
}
}
}

public void sendNotificationByToken(String token, String title, String body) {
Notification notification = Notification.builder()
.setTitle(title)
.setBody(body)
.build();

Message message = Message.builder()
.setToken(token)
.setNotification(notification)
.build();

try {
String response = FirebaseMessaging.getInstance().send(message);
System.out.println("Successfully sent message: " + response);
} catch (Exception e) {
e.printStackTrace();
}
}
}
Original file line number Diff line number Diff line change
@@ -1,18 +1,26 @@
package project.backend.domain.like;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import project.backend.domain.like.entity.CulturalEventLike;
import project.backend.domain.notification.service.NotificationService;

import javax.persistence.PrePersist;
import javax.persistence.PreRemove;



@Component
public class CulturalEventLikeListener {

@PrePersist
public void postPersist(CulturalEventLike culturalEventLike) {
public void prePersist(CulturalEventLike culturalEventLike) {
culturalEventLike.culturalEvent.increaseLikeCount();
}

@PreRemove
public void postRemove(CulturalEventLike culturalEventLike) {
public void preRemove(CulturalEventLike culturalEventLike) {
culturalEventLike.deleteCulturalEventLike();
culturalEventLike.culturalEvent.decreaseLikeCount();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import project.backend.domain.like.CulturalEventLikeListener;
import project.backend.domain.culturalevent.entity.CulturalEvent;
import project.backend.domain.member.entity.Member;
import project.backend.domain.notification.entity.Notification;

import javax.persistence.*;
import java.util.Optional;
Expand All @@ -24,6 +25,9 @@ public class CulturalEventLike extends BaseEntity {
@ManyToOne(fetch = FetchType.LAZY)
public CulturalEvent culturalEvent;

@OneToOne(mappedBy = "culturalEventLike", cascade = CascadeType.ALL, fetch = FetchType.LAZY, orphanRemoval = true)
private Notification notification;

// == 연관관계 매핑 == //
public void setCulturalEventLike(Member member, CulturalEvent culturalEvent) {
if (this.member != null) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,13 @@ public ResponseEntity nicknameValidation(@Valid @RequestBody MemberNicknameValid
return ResponseEntity.status(HttpStatus.OK).body(null);
}

@ApiOperation(value = "FCM 토큰 등록")
@PostMapping("/fcm-token")
public ResponseEntity setFcmToken(@Valid @RequestBody MemberFcmTokenDto request) {
memberService.setFcmToken(request.fcmToken);
return ResponseEntity.status(HttpStatus.OK).body(null);
}

@ApiIgnore
@GetMapping("/{memberId}") // todo : 관리자 권한 있어야 실행 가능한 것으로 바꾸기
public ResponseEntity getMember(
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
package project.backend.domain.member.dto;

import io.swagger.v3.oas.annotations.media.Schema;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Pattern;
import javax.validation.constraints.Size;

@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MemberFcmTokenDto {
@NotNull(message = "fcmToken은 필수 값입니다.")
public String fcmToken;
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.NoArgsConstructor;
import project.backend.domain.member.entity.GENDER;
import project.backend.domain.member.entity.Gender;

import javax.validation.constraints.Email;
import javax.validation.constraints.NotNull;
Expand Down Expand Up @@ -32,7 +32,7 @@ public class MemberSignupDto {

@NotNull(message = "nickname은 필수값입니다.")
@Schema(description = "성별", example = "FEMALE", required = true)
public GENDER gender;
public Gender gender;

@Schema(description = "마케팅 정보 수신 및 이용 동의", example = "true", required = false)
public Boolean isMarketingAgree;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@
import lombok.Getter;

@Getter
public enum GENDER {
public enum Gender {
MALE("남성"),
FEMALE("여성");

private final String status;

GENDER(String status) {
Gender(String status) {
this.status = status;
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@
import project.backend.domain.onboardingmembercategory.entity.OnboardingMemberCategory;
import project.backend.domain.ticket.entity.Ticket;
import project.backend.domain.member.dto.MemberPatchRequestDto;
import project.backend.domain.traffic.entity.Traffic;
import project.backend.domain.visit.entity.CulturalEventVisit;

import javax.persistence.*;
Expand All @@ -33,7 +32,7 @@ public class Member extends BaseEntity {
public SocialType socialType;

@Enumerated(value = EnumType.STRING)
public GENDER gender;
public Gender gender;

public String socialId;

Expand All @@ -49,6 +48,8 @@ public class Member extends BaseEntity {

public String refreshToken;

public String fcmToken;

public Boolean isSignup = false;

public Boolean isMarketingAgree = false;
Expand All @@ -68,9 +69,6 @@ public class Member extends BaseEntity {
@OneToMany(mappedBy = "member", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
public List<CulturalEventVisit> culturalEventVisitList = new ArrayList<>();

@OneToMany(mappedBy = "member", cascade = {CascadeType.PERSIST, CascadeType.REMOVE})
private List<Traffic> traffics = new ArrayList<>();

@Builder
public Member(SocialType socialType, String socialId, String nickname, String profileUrl, String refreshToken) {
this.socialType = socialType;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,20 +5,16 @@
import org.springframework.transaction.annotation.Transactional;
import project.backend.domain.category.service.CategoryService;
import project.backend.domain.member.dto.*;
import project.backend.domain.member.entity.Agree;
import project.backend.domain.member.entity.SocialType;
import project.backend.domain.member.entity.Member;
import project.backend.domain.member.mapper.MemberMapper;
import project.backend.domain.member.repository.MemberRepository;
import project.backend.domain.memberTicketLike.repository.MemberTicketLikeRepository;
import project.backend.domain.onboardingmembercategory.service.OnboardingMemberCategoryService;
import project.backend.domain.ticket.repository.TicketRepository;
import project.backend.global.error.exception.BusinessException;
import project.backend.global.error.exception.ErrorCode;

import java.time.LocalDateTime;
import java.util.List;
import java.util.stream.Collectors;

@Service
@RequiredArgsConstructor
Expand Down Expand Up @@ -93,6 +89,18 @@ public Member setMemberSignup(MemberSignupDto memberSignupDto) {
return member;
}

/**
* FCM 토큰 등록
* @param fcmToken
* @return Member
*/
public void setFcmToken(String fcmToken) {
Member member = memberJwtService.getMember();
member.fcmToken = fcmToken;
memberRepository.save(member);
}


@Transactional(readOnly = true)
public Member getMemberBySocialIdAndSocialType(String socialId, SocialType socialType) {
return memberRepository.findFirstBySocialIdAndSocialType(socialId, socialType).orElseThrow(() -> new BusinessException(ErrorCode.MEMBER_NOT_FOUND));
Expand Down
Loading

0 comments on commit a43525d

Please sign in to comment.