diff --git a/backend/.gitignore b/backend/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/backend/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/backend/build.gradle.kts b/backend/build.gradle.kts new file mode 100644 index 0000000..a96f680 --- /dev/null +++ b/backend/build.gradle.kts @@ -0,0 +1,23 @@ +plugins { + id("kotlin-platform-jvm") + id("application") + id("com.github.johnrengelman.shadow") +} + +group = "net.underdesk" +version = "0.0.1" + +dependencies { + implementation(project(":shared")) + implementation(Dependencies.Kotlin.core) + implementation(Dependencies.Kotlin.coroutinesCore) + + implementation(Dependencies.Ktor.ktorCore) + + implementation(Dependencies.Firebase.adminSDK) +} + +application { + mainClass.set("net.underdesk.circolapp.backend.ServerKt") + mainClassName = "net.underdesk.circolapp.backend.ServerKt" +} diff --git a/backend/src/main/java/net/underdesk/circolapp/backend/JavaDatabase.kt b/backend/src/main/java/net/underdesk/circolapp/backend/JavaDatabase.kt new file mode 100644 index 0000000..9c8642f --- /dev/null +++ b/backend/src/main/java/net/underdesk/circolapp/backend/JavaDatabase.kt @@ -0,0 +1,41 @@ +/* + * Circolapp + * Copyright (C) 2019-2020 Matteo Schiff + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.underdesk.circolapp.backend + +import net.underdesk.circolapp.shared.data.AppDatabase +import net.underdesk.circolapp.shared.data.CircularDao +import net.underdesk.circolapp.shared.data.DatabaseDriverFactory + +object JavaDatabase { + + @Volatile + private var instance: AppDatabase? = null + + private fun getInstance(): AppDatabase { + return instance ?: synchronized(this) { + instance ?: AppDatabase( + DatabaseDriverFactory().createDriver() + ).also { instance = it } + } + } + + fun getDaoInstance(): CircularDao { + return CircularDao(getInstance()) + } +} diff --git a/backend/src/main/java/net/underdesk/circolapp/backend/PushNotificationUtils.kt b/backend/src/main/java/net/underdesk/circolapp/backend/PushNotificationUtils.kt new file mode 100644 index 0000000..968d08f --- /dev/null +++ b/backend/src/main/java/net/underdesk/circolapp/backend/PushNotificationUtils.kt @@ -0,0 +1,59 @@ +/* + * Circolapp + * Copyright (C) 2019-2020 Matteo Schiff + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.underdesk.circolapp.backend + +import com.google.firebase.messaging.FirebaseMessaging +import com.google.firebase.messaging.Message +import com.google.firebase.messaging.Notification +import java.time.LocalDateTime + +object PushNotificationUtils { + fun createPushNotification(topic: String) { + val message = Message.builder() + .putData("fetchCircular", "true") + .setTopic(topic) + .build() + + val response = FirebaseMessaging.getInstance().send(message) + + val current = LocalDateTime.now() + + print("Sent data push notification for topic $topic with response $response at time $current \n") + } + + fun createPushNotificationiOS(topic: String) { + val realTopic = topic + "IOS" + + val message = Message.builder() + .setNotification( + Notification.builder() + .setTitle("Nuove circolari") + .setBody("La tua scuola ha pubblicato nuove circolari") + .build() + ) + .setTopic(realTopic) + .build() + + val response = FirebaseMessaging.getInstance().send(message) + + val current = LocalDateTime.now() + + print("Sent managed push notification for topic $realTopic with response $response at time $current \n") + } +} diff --git a/backend/src/main/java/net/underdesk/circolapp/backend/Server.kt b/backend/src/main/java/net/underdesk/circolapp/backend/Server.kt new file mode 100644 index 0000000..660ce28 --- /dev/null +++ b/backend/src/main/java/net/underdesk/circolapp/backend/Server.kt @@ -0,0 +1,47 @@ +/* + * Circolapp + * Copyright (C) 2019-2020 Matteo Schiff + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.underdesk.circolapp.backend + +import com.google.auth.oauth2.GoogleCredentials +import com.google.firebase.FirebaseApp +import com.google.firebase.FirebaseOptions +import kotlinx.coroutines.delay +import kotlinx.coroutines.runBlocking + +const val CHECK_DELAY_MILLIS = 900000L + +fun main(args: Array) { + print("Starting Circolapp Push microservice \n") + val options = FirebaseOptions.builder() + .setCredentials(GoogleCredentials.getApplicationDefault()) + .build() + + FirebaseApp.initializeApp(options) + + val serverUtils = ServerUtils() + + print("Microservice started! \n") + runBlocking { + while (true) { + serverUtils.checkServers() + + delay(CHECK_DELAY_MILLIS) + } + } +} diff --git a/backend/src/main/java/net/underdesk/circolapp/backend/ServerUtils.kt b/backend/src/main/java/net/underdesk/circolapp/backend/ServerUtils.kt new file mode 100644 index 0000000..87b2745 --- /dev/null +++ b/backend/src/main/java/net/underdesk/circolapp/backend/ServerUtils.kt @@ -0,0 +1,79 @@ +/* + * Circolapp + * Copyright (C) 2019-2020 Matteo Schiff + * + * This program is free software: you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation, either version 3 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License + * along with this program. If not, see . + */ + +package net.underdesk.circolapp.backend + +import net.underdesk.circolapp.shared.data.Circular +import net.underdesk.circolapp.shared.server.KtorFactory +import net.underdesk.circolapp.shared.server.Server +import net.underdesk.circolapp.shared.server.ServerAPI + +class ServerUtils { + private val serverList: MutableList = mutableListOf() + private val ktorClient = KtorFactory().createClient() + private val circularDao = JavaDatabase.getDaoInstance() + + init { + for (serverId in ServerAPI.Companion.Servers.values()) { + serverList.add(ServerAPI.createServer(serverId, ktorClient)) + } + } + + suspend fun checkServers() { + for (server in serverList) { + checkServer(server) + } + } + + private suspend fun updateCirculars(server: Server): Pair, Int> { + var onlyNewCirculars = listOf() + + var errorCode = 0 + val result = server.getCircularsFromServer() + if (result.second == ServerAPI.Companion.Result.ERROR) + return Pair(emptyList(), -1) + + val oldCirculars = circularDao.getCirculars(server.serverID) + val newCirculars = result.first + + if (newCirculars.size != oldCirculars.size) { + if (newCirculars.size < oldCirculars.size) { + circularDao.deleteAll() + errorCode = 1 + } + + val oldCircularsSize = + if (newCirculars.size < oldCirculars.size) 0 else oldCirculars.size + + val circularCount = newCirculars.size - oldCircularsSize + onlyNewCirculars = newCirculars.subList(0, circularCount) + + circularDao.insertAll(newCirculars) + } + return Pair(onlyNewCirculars, errorCode) + } + + private suspend fun checkServer(server: Server) { + val newCirculars = updateCirculars(server) + + if (newCirculars.second != -1 && newCirculars.first.isNotEmpty()) { + PushNotificationUtils.createPushNotification(ServerAPI.getServerTopic(server.serverID)) + PushNotificationUtils.createPushNotificationiOS(ServerAPI.getServerTopic(server.serverID)) + } + } +} diff --git a/build.gradle.kts b/build.gradle.kts index f717292..66f8121 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -17,6 +17,7 @@ buildscript { classpath(Config.Plugin.ktlint) classpath(Config.Plugin.aboutLibraries) classpath(Config.Plugin.dependencies) + classpath(Config.Plugin.shadow) // NOTE: Do not place your application dependencies here; they belong // in the individual module build.gradle files } diff --git a/buildSrc/src/main/kotlin/Config.kt b/buildSrc/src/main/kotlin/Config.kt index 611e78d..14ece82 100644 --- a/buildSrc/src/main/kotlin/Config.kt +++ b/buildSrc/src/main/kotlin/Config.kt @@ -13,6 +13,8 @@ object Config { "com.mikepenz.aboutlibraries.plugin:aboutlibraries-plugin:${Dependencies.AboutLibraries.version}" const val dependencies = "com.github.ben-manes:gradle-versions-plugin:0.36.0" + const val shadow = + "com.github.jengelman.gradle.plugins:shadow:5.2.0" } object Android { diff --git a/buildSrc/src/main/kotlin/Dependencies.kt b/buildSrc/src/main/kotlin/Dependencies.kt index 6bf6790..4df22ba 100644 --- a/buildSrc/src/main/kotlin/Dependencies.kt +++ b/buildSrc/src/main/kotlin/Dependencies.kt @@ -30,6 +30,8 @@ object Dependencies { object Firebase { const val bom = "com.google.firebase:firebase-bom:26.1.1" const val messaging = "com.google.firebase:firebase-messaging-ktx" + + const val adminSDK = "com.google.firebase:firebase-admin:7.0.1" } object Ktor { diff --git a/settings.gradle.kts b/settings.gradle.kts index 019e466..eceaa5a 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,3 +1,4 @@ +include(":backend") include(":shared") include(":app") rootProject.name = "Circolapp" diff --git a/shared/src/jvmMain/kotlin/net/underdesk/circolapp/shared/server b/shared/src/jvmMain/kotlin/net/underdesk/circolapp/shared/server new file mode 120000 index 0000000..f1ad6f1 --- /dev/null +++ b/shared/src/jvmMain/kotlin/net/underdesk/circolapp/shared/server @@ -0,0 +1 @@ +../../../../../../androidMain/kotlin/net/underdesk/circolapp/shared/server/ \ No newline at end of file