diff --git a/build.gradle.kts b/build.gradle.kts index d7b186b..a16454c 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -80,6 +80,9 @@ dependencies { // bad word filter implementation("io.github.vaneproject:badwordfiltering:1.0.0") + + //firebase + implementation("com.google.firebase:firebase-admin:9.1.1") } dependencyManagement { diff --git a/src/main/kotlin/com/example/onui/domain/analysis/service/AnalysisServiceImpl.kt b/src/main/kotlin/com/example/onui/domain/analysis/service/AnalysisServiceImpl.kt index e7ed7ea..3fa7a1a 100644 --- a/src/main/kotlin/com/example/onui/domain/analysis/service/AnalysisServiceImpl.kt +++ b/src/main/kotlin/com/example/onui/domain/analysis/service/AnalysisServiceImpl.kt @@ -31,6 +31,9 @@ class AnalysisServiceImpl( override fun test() { val user = userFacade.getCurrentUser() + + diaryRepository.deleteAllByUser(user) + val now = LocalDateTime.now() var i = now.minusDays(30); diff --git a/src/main/kotlin/com/example/onui/domain/auth/service/AuthServiceImpl.kt b/src/main/kotlin/com/example/onui/domain/auth/service/AuthServiceImpl.kt index 4dc3877..350ccfc 100644 --- a/src/main/kotlin/com/example/onui/domain/auth/service/AuthServiceImpl.kt +++ b/src/main/kotlin/com/example/onui/domain/auth/service/AuthServiceImpl.kt @@ -28,4 +28,12 @@ class AuthServiceImpl( } userRepository.delete(user) } + + @Transactional + fun applyDeviceToken(token: String) { + + val user = userFacade.getCurrentUser() + + user.applyDeviceToken(token) + } } \ No newline at end of file diff --git a/src/main/kotlin/com/example/onui/domain/auth/service/GoogleAuthServiceImpl.kt b/src/main/kotlin/com/example/onui/domain/auth/service/GoogleAuthServiceImpl.kt index a46ad93..0c21de9 100644 --- a/src/main/kotlin/com/example/onui/domain/auth/service/GoogleAuthServiceImpl.kt +++ b/src/main/kotlin/com/example/onui/domain/auth/service/GoogleAuthServiceImpl.kt @@ -80,8 +80,8 @@ class GoogleAuthServiceImpl( userRepository.findBySub(req.sub) ?: userRepository.save( User( - req.sub, - req.name!!, + sub = req.sub, + name = req.name!!, DEFAULT, themeRepository.findByIdOrNull(DEFAULT_ID)!! ) diff --git a/src/main/kotlin/com/example/onui/domain/diary/repository/DiaryRepository.kt b/src/main/kotlin/com/example/onui/domain/diary/repository/DiaryRepository.kt index 2d2e734..89a1ff6 100644 --- a/src/main/kotlin/com/example/onui/domain/diary/repository/DiaryRepository.kt +++ b/src/main/kotlin/com/example/onui/domain/diary/repository/DiaryRepository.kt @@ -16,4 +16,6 @@ interface DiaryRepository : JpaRepository { fun existsByIdAndIsPosted(id: UUID, isPosted: Boolean): Boolean fun findByIdAndIsPosted(id: UUID, isPosted: Boolean): Diary? + + fun deleteAllByUser(user: User) } \ No newline at end of file diff --git a/src/main/kotlin/com/example/onui/domain/diary/repository/QDiaryRepositoryImpl.kt b/src/main/kotlin/com/example/onui/domain/diary/repository/QDiaryRepositoryImpl.kt index 178060e..919c211 100644 --- a/src/main/kotlin/com/example/onui/domain/diary/repository/QDiaryRepositoryImpl.kt +++ b/src/main/kotlin/com/example/onui/domain/diary/repository/QDiaryRepositoryImpl.kt @@ -6,7 +6,9 @@ import com.querydsl.jpa.impl.JPAQueryFactory import org.springframework.stereotype.Repository import org.springframework.stereotype.Service import org.springframework.transaction.annotation.Transactional +import java.time.LocalDate import java.time.LocalDateTime +import java.time.LocalTime @Service @Transactional(readOnly = true) @@ -17,17 +19,17 @@ class QDiaryRepositoryImpl( override fun findThreeDayAgoByUser(user: User) = queryFactory.selectFrom(diary) .orderBy(diary.createdAt.desc()) - .where(diary.user.eq(user).and(diary.createdAt.after(LocalDateTime.now().minusDays(3)))) + .where(diary.user.eq(user).and(diary.createdAt.after(LocalDateTime.of(LocalDate.now().minusDays(3), LocalTime.of(0,0,0))))) .limit(3) .fetch().toMutableList() override fun findSevenDayAgoByUser(user: User) = queryFactory.selectFrom(diary) .orderBy(diary.createdAt.desc()) - .where(diary.user.eq(user).and(diary.createdAt.after(LocalDateTime.now().minusDays(7)))) + .where(diary.user.eq(user).and(diary.createdAt.after(LocalDateTime.of(LocalDate.now().minusDays(7), LocalTime.of(0,0,0))))) .fetch().map { it.toResponse() }.toMutableList() override fun findOneMonthAgoByUser(user: User) = queryFactory.selectFrom(diary) .orderBy(diary.createdAt.desc()) - .where(diary.user.eq(user).and(diary.createdAt.after(LocalDateTime.now().minusDays(30L)))) + .where(diary.user.eq(user).and(diary.createdAt.after(LocalDateTime.of(LocalDate.now().minusDays(30), LocalTime.of(0,0,0))))) .fetch().toMutableList() } \ No newline at end of file diff --git a/src/main/kotlin/com/example/onui/domain/user/entity/User.kt b/src/main/kotlin/com/example/onui/domain/user/entity/User.kt index 7b9a336..f04eee6 100644 --- a/src/main/kotlin/com/example/onui/domain/user/entity/User.kt +++ b/src/main/kotlin/com/example/onui/domain/user/entity/User.kt @@ -74,6 +74,14 @@ class User( var boughtTheme: MutableList = arrayListOf() protected set + @ElementCollection + var deviceToken: MutableSet = mutableSetOf() + protected set + + fun applyDeviceToken(token: String) { + this.deviceToken.add(token) + } + fun toResponse() = UserProfileResponse( this.sub, this.name, diff --git a/src/main/kotlin/com/example/onui/global/env/FCMProperty.kt b/src/main/kotlin/com/example/onui/global/env/FCMProperty.kt new file mode 100644 index 0000000..bd0a118 --- /dev/null +++ b/src/main/kotlin/com/example/onui/global/env/FCMProperty.kt @@ -0,0 +1,11 @@ +package com.example.onui.global.env + +import org.springframework.boot.context.properties.ConfigurationProperties +import org.springframework.boot.context.properties.ConstructorBinding + +@ConstructorBinding +@ConfigurationProperties("fcm.key") +data class FCMProperty( + val path: String, + val scope: String +) diff --git a/src/main/kotlin/com/example/onui/infra/fcm/FCMScheduling.kt b/src/main/kotlin/com/example/onui/infra/fcm/FCMScheduling.kt new file mode 100644 index 0000000..8a92396 --- /dev/null +++ b/src/main/kotlin/com/example/onui/infra/fcm/FCMScheduling.kt @@ -0,0 +1,23 @@ +package com.example.onui.infra.fcm + +import com.example.onui.domain.user.repository.UserRepository +import org.springframework.scheduling.annotation.Scheduled +import org.springframework.stereotype.Service +import org.springframework.transaction.annotation.Transactional + +@Service +class FCMScheduling( + private val notificationService: NotificationService, + private val userRepository: UserRepository +) { + + @Transactional + @Scheduled(cron = "0 20 8 * * ?", zone = "Asia/Seoul") + fun essentailHost() { + userRepository.findAll().parallelStream().peek { + notificationService.sendByTokenList( + it.deviceToken, "감정 기록하실 때가 됐어요!", "사용자님 오늘도 오누이에 감정을 기록해보아요!!" + ) + } + } +} \ No newline at end of file diff --git a/src/main/kotlin/com/example/onui/infra/fcm/NotificationService.kt b/src/main/kotlin/com/example/onui/infra/fcm/NotificationService.kt new file mode 100644 index 0000000..f2df891 --- /dev/null +++ b/src/main/kotlin/com/example/onui/infra/fcm/NotificationService.kt @@ -0,0 +1,6 @@ +package com.example.onui.infra.fcm + +interface NotificationService { + + fun sendByTokenList(tokenList: MutableSet, title: String, body: String) +} diff --git a/src/main/kotlin/com/example/onui/infra/fcm/NotificationServiceImpl.kt b/src/main/kotlin/com/example/onui/infra/fcm/NotificationServiceImpl.kt new file mode 100644 index 0000000..d979c4b --- /dev/null +++ b/src/main/kotlin/com/example/onui/infra/fcm/NotificationServiceImpl.kt @@ -0,0 +1,56 @@ +package com.example.onui.infra.fcm + +import com.example.onui.global.env.FCMProperty +import com.google.auth.oauth2.GoogleCredentials +import com.google.firebase.FirebaseApp +import com.google.firebase.FirebaseOptions +import com.google.firebase.messaging.* +import mu.KotlinLogging +import org.springframework.core.io.ClassPathResource +import org.springframework.stereotype.Service +import java.io.IOException +import java.time.LocalDateTime +import javax.annotation.PostConstruct + +@Service +class NotificationServiceImpl( + private val fcmProperty: FCMProperty +) : NotificationService { + + private companion object { + val logger = KotlinLogging.logger { } + } + + @PostConstruct + fun init() { + try { + val options = FirebaseOptions.builder() + .setCredentials( + GoogleCredentials + .fromStream(ClassPathResource(fcmProperty.path).getInputStream()) + .createScoped(listOf(fcmProperty.scope)) + ) + .build() + if (FirebaseApp.getApps().isEmpty()) { + FirebaseApp.initializeApp(options) + } + } catch (e: IOException) { + logger.error{ e.message } + throw RuntimeException(e.message) + } + } + + // 알림 보내기 + override fun sendByTokenList(tokenList: MutableSet, title: String, body: String) { + + val messages: MutableList = tokenList.stream().map { + Message.builder() + .putData("time", LocalDateTime.now().toString()) + .setNotification(Notification.builder().setTitle(title).setBody(body).build()) + .setToken(it) + .build() + }.toList() + + FirebaseMessaging.getInstance().sendAll(messages) + } +} diff --git a/src/main/kotlin/com/example/onui/infra/fcm/dto/reqeust/FCMNotificationRequest.kt b/src/main/kotlin/com/example/onui/infra/fcm/dto/reqeust/FCMNotificationRequest.kt new file mode 100644 index 0000000..d1dbdb2 --- /dev/null +++ b/src/main/kotlin/com/example/onui/infra/fcm/dto/reqeust/FCMNotificationRequest.kt @@ -0,0 +1,6 @@ +package com.example.onui.infra.fcm.dto.reqeust + +data class FCMNotificationRequest ( + val title: String, + val message: String +) diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml index b55fb43..94f590a 100644 --- a/src/main/resources/application.yml +++ b/src/main/resources/application.yml @@ -65,4 +65,9 @@ cloud: region: ap-northeast-2 gpt: - secret-key: ${GPT_KEY} \ No newline at end of file + secret-key: ${GPT_KEY} + +fcm: + key: + path: onui-fcm.json + scope: https://www.googleapis.com/auth/cloud-platform \ No newline at end of file diff --git a/src/main/resources/onui-fcm.json b/src/main/resources/onui-fcm.json new file mode 100644 index 0000000..bd43938 --- /dev/null +++ b/src/main/resources/onui-fcm.json @@ -0,0 +1,13 @@ +{ + "type": "service_account", + "project_id": "onui-399708", + "private_key_id": "ed3eeefe4fd58acbb799bee0b440c68265331a44", + "private_key": "-----BEGIN PRIVATE KEY-----\nMIIEvAIBADANBgkqhkiG9w0BAQEFAASCBKYwggSiAgEAAoIBAQDrmqmC9DZIMt4G\ndBh6u4D7M5u9tx3rgfLWZw5XIM0q7R9ax0HrEJZ+kfxO+5qIBDjdyBZnvZtvV8in\n/LIbj/zhBUrMu3uU4nHen03o/so9dgEzW8j8ln2PJHUz3DorpkzfmUQakdUgbOgE\nJxJSTHltVVZgOBiyItB3U6bO3EgWUSw64SI96sqc/jHXhzXHwJFC/xZNatgxJqKb\nqzhflwurC9a1VTZVaxipxsV4GqB/OlmvMSp4RvbWsUTOzI94+2QMrv0LtNnEj4WJ\nV3WOBf4+KEXpnGEQe26I3pzx5mpJ6kr5FbRjjyHyytu702klYsiu4amxUQmSU1Dj\n7HdaAG/LAgMBAAECggEAbCc03mEF1BHJuxOrMxgE5wuzWB0J1pTyKTroqdVsaWKt\nrInguCwGsbaJKKa2Mu8hPan+owO2qR+WhIrrJdzsvE0mH6KG4fsrQ4NEjAr+QsV7\nWpytQEpC/CVDyhkz+Nqf2lrsmPfN6tMjlNhswCvL6AUvH/9QUuHJaaWUYsxbKSab\ng/3m/FBRAAnst5cKupC1CTfh8eN+DYAsawA+/TxIiSWSr6gXItUyvOrAXvn7uegs\nwbb1SzKQp7ZwHHgF261LNjPuPilrJc7v9zI8TAr0aoje4u2Qxy3x08ScTG/Z6odM\n2PAJgaSPTOUEZQL7oG/IStyascZbVktO5u33ijsnKQKBgQD8Wyw/kQ+Tgn5XmfyW\nBJWjL1bx00mAY9OSRlUZiEvWJVacHivY1PS74h7e45dSzrdk3NvP7SEPxuXFLj1V\nGVveq3VLBIjqc0EzopAigtSOy+gmqQRi9CbvNR/aL/0xO1sH27hvuwF6+2edJAJs\nao9xeyf4alLhnqcbQkKu90mKUwKBgQDvAZDodbIvRrBvPlbUQGEZiOUxCNqm5HSL\namMeXVgAJvNyTs7wH4IP7B5bbK6KxmCQeLty8EZJRkEKNzvSGRrPVa96b3/Tduqm\n0uUPd+6g79VghECJLm+kZo9VUFkXrg6sh+yAqArN3x7aLqm08goSJ+TbE4tz/66z\neXdRdy2FqQKBgFj+rjQcrCmRM40hOPqO01ahM0BFCv7ENbC4LPq8HkJ/GHQmD8CA\nGW7I56ojCRKi4/omCT7imW4+7nkDPY3tS4DTZqH2D3LfAnd7NOl2yg8xycUYhft/\nrttdGMaRdfEOxaOX0QtWH8eHMZsxP8mMKtoSXJ42oNQAZ14tl+asPrsRAoGATaRq\nmLmzWtxR8LGFr4oCgTMRkW5Y6aKJoci7zl9weQwuRdIIM8VokReZfQW/ZeGv+P7f\nliUsEB28fz8WNdhl9zjUuqeCFQYqkGsucmn5oVqILMSJ2oa1SejvYz2o06J9rdqy\nH9F3QQ3cjfBevSNr1h4ToH+m69YBwNgSxOo8ZZECgYBDRSYUDj8KHFKsgrJW2it5\npFJvAdo0uZxt07lGPCUBHD4K2Ax441Q37G1zY7PSTwUhdX0mT+Nk/zehO3OEqcbm\nt0Dy4Q6UQBd7PQ1UZGrnMGgea4FU/ImQdOc3rPC5VrJYpifsl+JAXF5l5/y3J+4u\nUYoTahro+Tnm0haRCm355A==\n-----END PRIVATE KEY-----\n", + "client_email": "firebase-adminsdk-jgwl3@onui-399708.iam.gserviceaccount.com", + "client_id": "107821224243694744575", + "auth_uri": "https://accounts.google.com/o/oauth2/auth", + "token_uri": "https://oauth2.googleapis.com/token", + "auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs", + "client_x509_cert_url": "https://www.googleapis.com/robot/v1/metadata/x509/firebase-adminsdk-jgwl3%40onui-399708.iam.gserviceaccount.com", + "universe_domain": "googleapis.com" +}