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
5 changes: 5 additions & 0 deletions .github/workflows/cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ env:
OCCUPY_SECRET_DIR: src/main/resources
OCCUPY_SECRET_TEST_DIR: src/test/resources
OCCUPY_SECRET_DIR_FILE_NAME: application-secret.yml
FCM_DIR: src/main/resources/firebase
FCM_FILE_NAME: ootdzip-cf27f-firebase-adminsdk-iuig1-8969152a6a.json
FCM_KEY: ${{ secrets.FCM_KEY }}

jobs:
build-with-gradle:
Expand All @@ -30,6 +33,8 @@ jobs:
distribution: 'corretto'
- name: Secret 파일 복사
run: echo $OCCUPY_SECRET | base64 --decode > $OCCUPY_SECRET_DIR/$OCCUPY_SECRET_DIR_FILE_NAME && echo $OCCUPY_SECRET | base64 --decode > $OCCUPY_SECRET_TEST_DIR/$OCCUPY_SECRET_DIR_FILE_NAME
- name: FCM 키 파일 복사
run: echo $OCCUPY_SECRET | base64 --decode > FCM_DIR/FCM_FILE_NAME && echo FCM_KEY
- name: gradlew에 실행 권한 부여
run: chmod +x ./gradlew
- name: 프로젝트 빌드
Expand Down
5 changes: 5 additions & 0 deletions .github/workflows/dev_cicd.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ env:
OCCUPY_SECRET_DIR: src/main/resources
OCCUPY_SECRET_TEST_DIR: src/test/resources
OCCUPY_SECRET_DIR_FILE_NAME: application-secret.yml
FCM_DIR: src/main/resources/firebase
FCM_FILE_NAME: ootdzip-cf27f-firebase-adminsdk-iuig1-8969152a6a.json
FCM_KEY: ${{ secrets.FCM_KEY }}

jobs:
build-with-gradle:
Expand All @@ -30,6 +33,8 @@ jobs:
distribution: 'corretto'
- name: Secret 파일 복사
run: echo $OCCUPY_SECRET | base64 --decode > $OCCUPY_SECRET_DIR/$OCCUPY_SECRET_DIR_FILE_NAME && echo $OCCUPY_SECRET | base64 --decode > $OCCUPY_SECRET_TEST_DIR/$OCCUPY_SECRET_DIR_FILE_NAME
- name: FCM 키 파일 복사
run: echo $OCCUPY_SECRET | base64 --decode > FCM_DIR/FCM_FILE_NAME && echo FCM_KEY
- name: gradlew에 실행 권한 부여
run: chmod +x ./gradlew
- name: 프로젝트 빌드
Expand Down
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ out/
### key file ###
**/application-secret.yaml
**/resources/**/*.p8
**/resources/firebase/

### Querydsl ###
/src/main/generated/
Expand Down
13 changes: 13 additions & 0 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -30,20 +30,25 @@ dependencies {
implementation 'org.springframework.boot:spring-boot-starter-web'
implementation 'org.springframework.boot:spring-boot-starter-data-redis'
implementation 'org.springframework.boot:spring-boot-starter-validation'

// Swagger
implementation 'org.springdoc:springdoc-openapi-starter-webmvc-ui:2.0.2'

// Lombok
compileOnly 'org.projectlombok:lombok'
annotationProcessor 'org.projectlombok:lombok'

// Jackson Java 8 Date/Time 지원
implementation 'com.fasterxml.jackson.datatype:jackson-datatype-jsr310'

// JWT
implementation 'io.jsonwebtoken:jjwt-api:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-impl:0.12.5'
runtimeOnly 'io.jsonwebtoken:jjwt-jackson:0.12.5'

// 이미지 리사이즈
implementation 'net.coobird:thumbnailator:0.4.20'

// 파일 업로드
implementation 'org.springframework.cloud:spring-cloud-starter-aws:2.2.6.RELEASE'
implementation 'commons-io:commons-io:2.6'
Expand All @@ -54,12 +59,20 @@ dependencies {
annotationProcessor "com.querydsl:querydsl-apt:${dependencyManagement.importedProperties['querydsl.version']}:jakarta"
annotationProcessor "jakarta.annotation:jakarta.annotation-api"
annotationProcessor "jakarta.persistence:jakarta.persistence-api"

// MariaDB 연결
runtimeOnly 'org.mariadb.jdbc:mariadb-java-client'

// Redis 연결: Redisson
implementation 'org.redisson:redisson-spring-boot-starter:3.31.0'

// firebase for 푸쉬알람
implementation 'com.google.firebase:firebase-admin:9.2.0'


// Test

// test
testImplementation 'org.springframework.boot:spring-boot-starter-test'
testImplementation 'org.springframework.security:spring-security-test'
testImplementation 'org.mockito:mockito-core:3.11.2'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ private void notifyOotdComment(User receiver, User sender, String content, Strin
return;
}

// 앱 내 알람
eventPublisher.publishEvent(NotificationEvent.builder()
.receiver(receiver)
.sender(sender)
Expand Down
51 changes: 51 additions & 0 deletions src/main/java/zip/ootd/ootdzip/fcm/controller/FcmController.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
package zip.ootd.ootdzip.fcm.controller;

import org.springframework.web.bind.annotation.DeleteMapping;
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 io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.validation.Valid;
import lombok.RequiredArgsConstructor;
import zip.ootd.ootdzip.common.response.ApiResponse;
import zip.ootd.ootdzip.fcm.data.FcmPostReq;
import zip.ootd.ootdzip.fcm.service.FcmService;
import zip.ootd.ootdzip.user.service.UserService;

@RestController
@RequiredArgsConstructor
@Tag(name = "FCM 컨트롤러", description = "푸쉬 알람 설정할 때 사용 합니다.")
@RequestMapping("/api/v1/fcm")
public class FcmController {

private final FcmService fcmService;

private final UserService userService;

/**
* 푸쉬 알람을 허용한 유저로부터
* FCM 토큰값을 얻어와서 DB 에 저장해둡니다.
* 해당 토큰값은 디바이스 고유값으로 해당 값으로 FCM 이 디바이스에게 푸쉬알람을 보낼 수 있습니다.
* 유저가 앱을실행하고 로그인할 때마다 프론트는 해당 API 를통해 토큰값을 서버로 보냅니다.
*/
@PostMapping("")
public ApiResponse<Boolean> onFcmToken(@RequestBody @Valid FcmPostReq fcmPostReq) {

fcmService.onFcmToken(fcmPostReq, userService.getAuthenticatiedUser());

return new ApiResponse<>(true);
}

/**
* 사용자 토큰 상태를 off 하여 해당 기기는 알림을 받을 수 없습니다.
*/
@DeleteMapping("")
public ApiResponse<Boolean> offFcmToken(@RequestBody @Valid FcmPostReq fcmPostReq) {

fcmService.offFcmToken(fcmPostReq);

return new ApiResponse<>(true);
}
}
21 changes: 21 additions & 0 deletions src/main/java/zip/ootd/ootdzip/fcm/data/FcmMessageRes.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
package zip.ootd.ootdzip.fcm.data;

import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import zip.ootd.ootdzip.notification.domain.Notification;

@Getter
@Builder
public class FcmMessageRes {
private boolean validateOnly;
private FcmMessageRes.Message message;

@Builder
@AllArgsConstructor
@Getter
public static class Message {
private Notification notification;
private String token;
}
}
11 changes: 11 additions & 0 deletions src/main/java/zip/ootd/ootdzip/fcm/data/FcmPostReq.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package zip.ootd.ootdzip.fcm.data;

import jakarta.validation.constraints.NotNull;
import lombok.Data;

@Data
public class FcmPostReq {

@NotNull(message = "토큰값은 필수입니다.")
private String fcmToken;
}
92 changes: 92 additions & 0 deletions src/main/java/zip/ootd/ootdzip/fcm/domain/FcmInfo.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
package zip.ootd.ootdzip.fcm.domain;

import java.util.ArrayList;
import java.util.List;
import java.util.stream.Stream;

import jakarta.persistence.CascadeType;
import jakarta.persistence.Column;
import jakarta.persistence.Entity;
import jakarta.persistence.FetchType;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.OneToMany;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import zip.ootd.ootdzip.common.entity.BaseEntity;
import zip.ootd.ootdzip.notification.domain.Notification;
import zip.ootd.ootdzip.notification.domain.NotificationType;
import zip.ootd.ootdzip.user.domain.User;

@Entity
@Table(name = "fcm_infos")
@Getter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FcmInfo extends BaseEntity {

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "user_id", nullable = false)
private User user;

@Builder.Default
@Column(nullable = false)
private Boolean isPermission = true;

@Builder.Default
@Column(nullable = false)
private Boolean isLogin = true;

@Column(nullable = false)
private String fcmToken;

@Builder.Default
@OneToMany(mappedBy = "fcmInfo", cascade = CascadeType.ALL, orphanRemoval = true)
List<FcmNotificationType> fcmNotificationTypes = new ArrayList<>();

// fcm 기본생성자
// 모든 알람에 대해 허용으로 기본으로 생성합니다.
public static FcmInfo createDefaultFcmInfo(User user, String fcmToken) {

List<FcmNotificationType> fcmDefaultNotificationTypes = Stream.of(NotificationType.values())
.map(notificationType -> FcmNotificationType.builder()
.notificationType(notificationType).build()).toList();

FcmInfo fcmInfo = FcmInfo.builder()
.user(user)
.fcmToken(fcmToken)
.build();

fcmInfo.addFcmNotificationTypes(fcmDefaultNotificationTypes);
return fcmInfo;
}

// 해당 기기 사용자가 로그인을 했을 경우
public void login() {
this.isLogin = true;
}

// 해당 기기 사용자가 로그아웃을 했을 경우
public void logout() {
this.isLogin = false;
}

public boolean isExistAllowNotificationType(Notification notification) {
return fcmNotificationTypes.stream()
.anyMatch(fnt -> fnt.getNotificationType() == notification.getNotificationType() && fnt.getIsAllow());
}

// == 연관관계 메서드 == //
public void addFcmNotificationType(FcmNotificationType fcmNotificationType) {
fcmNotificationTypes.add(fcmNotificationType);
fcmNotificationType.setFcmInfo(this);
}

public void addFcmNotificationTypes(List<FcmNotificationType> fcmNotificationTypes) {
fcmNotificationTypes.forEach(this::addFcmNotificationType);
}
}
33 changes: 33 additions & 0 deletions src/main/java/zip/ootd/ootdzip/fcm/domain/FcmNotificationType.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
package zip.ootd.ootdzip.fcm.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.JoinColumn;
import jakarta.persistence.ManyToOne;
import jakarta.persistence.Table;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import zip.ootd.ootdzip.common.entity.BaseEntity;
import zip.ootd.ootdzip.notification.domain.NotificationType;

@Entity
@Table(name = "fcm_notification_types")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class FcmNotificationType extends BaseEntity {

@ManyToOne
@JoinColumn(name = "fcm_info_id", nullable = false)
private FcmInfo fcmInfo;

private NotificationType notificationType;

@Builder.Default
private Boolean isAllow = true;

}
14 changes: 14 additions & 0 deletions src/main/java/zip/ootd/ootdzip/fcm/repository/FcmRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package zip.ootd.ootdzip.fcm.repository;

import java.util.Optional;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

import zip.ootd.ootdzip.fcm.domain.FcmInfo;

@Repository
public interface FcmRepository extends JpaRepository<FcmInfo, Long> {

Optional<FcmInfo> findByFcmToken(String fcmToken);
}
Loading