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
39 changes: 39 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
# This workflow uses actions that are not certified by GitHub.
# They are provided by a third-party and are governed by
# separate terms of service, privacy policy, and support
# documentation.

name: Release
# Run workflow on commits to the `main` branch
on:
workflow_dispatch:
push:
branches:
- 'main'
# - '*' # matches every branch that doesn't contain a '/'
# - '*/*' # matches every branch containing a single '/'
# - '**' # matches every branch
# - '!main' # excludes main

permissions:
contents: read

jobs:
release:
runs-on: self-hosted
# runs-on: ubuntu-latest
env:
# define Java options for both official sbt and sbt-extras
JAVA_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8
JVM_OPTS: -Xms2048M -Xmx2048M -Xss6M -XX:ReservedCodeCacheSize=256M -Dfile.encoding=UTF-8
steps:
- name: Checkout
uses: actions/checkout@v3
- name: Set up JDK 8
uses: actions/setup-java@v3
with:
java-version: '8'
distribution: 'temurin'
cache: 'sbt'
- name: Run tests & publish
run: sbt test publish
9 changes: 9 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -1 +1,10 @@
<!---
This file is auto-generate by a github hook please modify r.md if you don't want to loose your work
-->
![Build Status](https://github.com/SOFTNETWORK-APP/generic-notification-api/workflows/Build/badge.svg)
[![Scala Steward badge](https://img.shields.io/badge/Scala_Steward-helping-blue.svg?style=flat&logo=data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAA4AAAAQCAMAAAARSr4IAAAAVFBMVEUAAACHjojlOy5NWlrKzcYRKjGFjIbp293YycuLa3pYY2LSqql4f3pCUFTgSjNodYRmcXUsPD/NTTbjRS+2jomhgnzNc223cGvZS0HaSD0XLjbaSjElhIr+AAAAAXRSTlMAQObYZgAAAHlJREFUCNdNyosOwyAIhWHAQS1Vt7a77/3fcxxdmv0xwmckutAR1nkm4ggbyEcg/wWmlGLDAA3oL50xi6fk5ffZ3E2E3QfZDCcCN2YtbEWZt+Drc6u6rlqv7Uk0LdKqqr5rk2UCRXOk0vmQKGfc94nOJyQjouF9H/wCc9gECEYfONoAAAAASUVORK5CYII=)](https://scala-steward.org)
[![codecov](https://codecov.io/gh/SOFTNETWORK-APP/generic-notification-api/branch/feature%2Fspi/graph/badge.svg)](https://codecov.io/gh/SOFTNETWORK-APP/generic-notification-api/)
[![Codacy Badge](https://app.codacy.com/project/badge/Grade/122252a6bdfb41c3af16d31f8cefaecc)](https://www.codacy.com/gh/SOFTNETWORK-APP/generic-notification-api/dashboard?utm_source=github.com&amp;utm_medium=referral&amp;utm_content=SOFTNETWORK-APP/generic-notification-api&amp;utm_campaign=Badge_Grade)
[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)

# generic-notification-api
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,11 @@ import akka.actor.typed.ActorSystem
import app.softnetwork.notification.handlers.NotificationHandler
import app.softnetwork.notification.launch.NotificationApplication
import app.softnetwork.notification.model.Notification
import app.softnetwork.notification.peristence.query.{
import app.softnetwork.notification.persistence.query.{
NotificationCommandProcessorStream,
Scheduler2NotificationProcessorStream
}
import app.softnetwork.notification.peristence.typed.{
import app.softnetwork.notification.persistence.typed.{
AllNotificationsBehavior,
NotificationBehavior
}
Expand Down
2 changes: 1 addition & 1 deletion build.sbt
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ ThisBuild / organization := "app.softnetwork"

name := "notification"

ThisBuild / version := "0.3-SNAPSHOT"
ThisBuild / version := "0.1-SNAPSHOT"

ThisBuild / scalaVersion := "2.12.15"

Expand Down
10 changes: 10 additions & 0 deletions common/src/main/protobuf/model/notifications.proto
Original file line number Diff line number Diff line change
Expand Up @@ -132,4 +132,14 @@ message Push{
required string id = 21 [default = ""];
required int32 badge = 22 [default = 0];
optional string sound = 23;
optional string app = 24;
}

message PushPayload{
option (scalapb.message).extends = "ProtobufDomainObject";
required string app = 1;
required string title = 2;
required string body = 3;
required int32 badge = 4 [default = 0];
optional string sound = 5;
}
5 changes: 0 additions & 5 deletions common/src/main/resources/reference.conf
Original file line number Diff line number Diff line change
Expand Up @@ -40,11 +40,6 @@ notification {
dry-run = ${?NOTIFICATION_PUSH_APNS_DRY_RUN}
}

gcm {
apiKey = ""
apiKey = ${?NOTIFICATION_PUSH_GCM_API_KEY}
}

fcm {
databaseUrl = ""
databaseUrl = ${?NOTIFICATION_PUSH_FCM_DATABASE_URL}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package app.softnetwork.notification.config

case class EventStreams(externalToNotificationTag: String)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package app.softnetwork.notification.config

case class NotificationConfig(eventStreams: EventStreams, akkaNodeRole: String)
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
package app.softnetwork.notification.config

import com.typesafe.config.{Config, ConfigFactory}
import com.typesafe.scalalogging.StrictLogging
import configs.Configs

import scala.language.implicitConversions

trait NotificationSettings extends StrictLogging {

lazy val config: Config = ConfigFactory.load()

lazy val NotificationConfig: NotificationConfig =
Configs[NotificationConfig].get(config, "notification").toEither match {
case Left(configError) =>
logger.error(s"Something went wrong with the provided arguments $configError")
throw configError.configException
case Right(r) => r
}

}

object NotificationSettings extends NotificationSettings

This file was deleted.

138 changes: 138 additions & 0 deletions common/src/main/scala/app/softnetwork/notification/spi/package.scala
Original file line number Diff line number Diff line change
@@ -0,0 +1,138 @@
package app.softnetwork.notification

import akka.actor.typed.ActorSystem
import app.softnetwork.notification.model.Notification
import org.softnetwork.notification.model.{
Mail,
NotificationAck,
NotificationStatusResult,
Platform,
Push,
PushPayload,
SMS
}

import java.util.Date
import scala.language.implicitConversions

package object spi {

trait NotificationProvider {
type N <: Notification

def send(notification: N)(implicit system: ActorSystem[_]): NotificationAck

def !(notification: N)(implicit system: ActorSystem[_]): NotificationAck = send(notification)

def ack(notification: N)(implicit system: ActorSystem[_]): NotificationAck =
NotificationAck(notification.ackUuid, notification.results, new Date())

def ?(notification: N)(implicit system: ActorSystem[_]): NotificationAck = ack(notification)
}

trait MailProvider {
def sendMail(notification: Mail)(implicit system: ActorSystem[_]): NotificationAck
def ackMail(notification: Mail)(implicit system: ActorSystem[_]): NotificationAck =
NotificationAck(notification.ackUuid, notification.results, new Date())
}

trait SMSProvider {
def sendSMS(notification: SMS)(implicit system: ActorSystem[_]): NotificationAck
def ackSMS(notification: SMS)(implicit system: ActorSystem[_]): NotificationAck =
NotificationAck(notification.ackUuid, notification.results, new Date())
}

trait PushProvider {
def bulkSize = 100
def sendPush(notification: Push)(implicit system: ActorSystem[_]): NotificationAck
def ackPush(notification: Push)(implicit system: ActorSystem[_]): NotificationAck =
NotificationAck(notification.ackUuid, notification.results, new Date())
}

implicit def toPushPayload(notification: Push): PushPayload = {
PushPayload.defaultInstance
.withApp(
notification.app.getOrElse(notification.from.alias.getOrElse(notification.from.value))
)
.withTitle(notification.subject)
.withBody(notification.message)
.withBadge(notification.badge)
.copy(sound = notification.sound)
}

trait AndroidProvider extends PushProvider {
def pushToAndroid(payload: PushPayload, devices: Seq[String])(implicit
system: ActorSystem[_]
): Seq[NotificationStatusResult]
override def sendPush(notification: Push)(implicit system: ActorSystem[_]): NotificationAck = {
NotificationAck(
None,
pushToAndroid(
notification,
notification.devices.filter(_.platform == Platform.ANDROID).map(_.regId).distinct
).distinct,
new Date()
)
}
}

trait IosProvider extends PushProvider {
def pushToIos(payload: PushPayload, devices: Seq[String])(implicit
system: ActorSystem[_]
): Seq[NotificationStatusResult]
override def sendPush(notification: Push)(implicit system: ActorSystem[_]): NotificationAck = {
NotificationAck(
None,
pushToIos(
notification,
notification.devices.filter(_.platform == Platform.IOS).map(_.regId).distinct
).distinct,
new Date()
)
}
}

trait AndroidAndIosProvider extends PushProvider with AndroidProvider with IosProvider {
final override def sendPush(
notification: Push
)(implicit system: ActorSystem[_]): NotificationAck = {
// split notification per platform
val (android, ios) = notification.devices.partition(_.platform == Platform.ANDROID)
// send notification to devices per platform
NotificationAck(
None,
pushToIos(notification, ios.map(_.regId).distinct) ++ pushToAndroid(
notification,
android.map(_.regId)
).distinct,
new Date()
)
}
}

trait MailAndSMSAndFcmAndIosProvider
extends NotificationProvider
with MailProvider
with SMSProvider
with AndroidAndIosProvider {
override type N = Notification

override def send(
notification: Notification
)(implicit system: ActorSystem[_]): NotificationAck = notification match {
case mail: Mail => sendMail(mail)
case sms: SMS => sendSMS(sms)
case push: Push => sendPush(push)
case _ => NotificationAck(notification.ackUuid, notification.results, new Date())
}

override def ack(notification: Notification)(implicit system: ActorSystem[_]): NotificationAck =
notification match {
case mail: Mail => ackMail(mail)
case sms: SMS => ackSMS(sms)
case push: Push => ackPush(push)
case _ => NotificationAck(notification.ackUuid, notification.results, new Date())
}
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package app.softnetwork.notification.config

case class ApnsConfig(topic: String, keystore: Keystore, dryRun: Boolean = false)
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package app.softnetwork.notification.config

case class FcmConfig(databaseUrl: String, googleCredentials: Option[String])
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package app.softnetwork.notification.config

case class Keystore(path: String, password: String = "")
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package app.softnetwork.notification.config

case class MailConfig(
host: String,
port: Int,
sslPort: Int,
username: String,
password: String,
sslEnabled: Boolean,
sslCheckServerIdentity: Boolean,
startTLSEnabled: Boolean,
socketConnectionTimeout: Int = 2000,
socketTimeout: Int = 2000
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package app.softnetwork.notification.config

import configs.Configs

trait MailSettings extends NotificationSettings {

lazy val MailConfig: MailConfig =
Configs[MailConfig].get(config, "notification.mail").toEither match {
case Left(configError) =>
logger.error(s"Something went wrong with the provided arguments $configError")
throw configError.configException
case Right(r) => r
}
}

object MailSettings extends MailSettings
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
package app.softnetwork.notification.config

case class PushConfig(apns: ApnsConfig, fcm: FcmConfig)
Loading