diff --git a/app/controllers/AlertController.scala b/app/controllers/AlertController.scala index 7b68652..6bab741 100644 --- a/app/controllers/AlertController.scala +++ b/app/controllers/AlertController.scala @@ -2,12 +2,10 @@ package controllers import javax.inject.{Inject, Singleton} -import com.twilio.rest.api.v2010.account.Message -import com.twilio.`type`.PhoneNumber import models.SubscriberRepository import play.api.data.Form import play.api.data.Forms._ -import play.api.i18n.{Lang, MessagesApi} +import play.api.i18n.MessagesApi import play.api.mvc.{AbstractController, ControllerComponents} import scala.concurrent.{ExecutionContext, Future} @@ -33,14 +31,7 @@ class AlertController @Inject()(cc: ControllerComponents, repo: SubscriberReposi }, alert => { repo.listActive().map { subscribers => - val messages = subscribers.map { subscriber => - implicit val lang: Lang = Lang.get("en").get - Message - .creator(new PhoneNumber(subscriber.phone), // to - new PhoneNumber(sys.env("TWILIO_PHONE")), // from - messagesApi("action_alert", alert.address)) - .create() - } + val messages = alert.sendAlert(subscribers, messagesApi) Redirect(routes.HomeController.index()) .flashing("success" -> ("Done! Messages sent with IDs " + messages.map(_.getSid()).mkString(","))) } diff --git a/app/controllers/TwilioController.scala b/app/controllers/TwilioController.scala index 83c3128..fa7eac3 100644 --- a/app/controllers/TwilioController.scala +++ b/app/controllers/TwilioController.scala @@ -3,6 +3,8 @@ package controllers import javax.inject.{Inject, Singleton} import com.twilio.twiml.{Body, Message, MessagingResponse} +import models.{Alert} +import models.{SubscriberAction, AlertAction} import models.{SubscriberRepository, SubscriberTransitions} import play.api.data.Form import play.api.data.Forms._ @@ -28,7 +30,11 @@ class TwilioController @Inject()(cc: ControllerComponents, repo: SubscriberRepos repo.getOrCreate(twilioData.from).map { subscriber => implicit val lang: Lang = Lang.get("en").get val subscriberTransition = new SubscriberTransitions(subscriber) - val (responseMessage, updatedSubscriber) = subscriberTransition.receiveInput(twilioData.body) + val action = subscriberTransition.action(twilioData.body) + if (action.isDefined) { + performAction(action.get) + } + val (responseMessage, updatedSubscriber) = subscriberTransition.transition(twilioData.body) if (updatedSubscriber.isDefined) { repo.update(updatedSubscriber.get) } @@ -41,4 +47,14 @@ class TwilioController @Inject()(cc: ControllerComponents, repo: SubscriberRepos Ok(response.toXml()).as("application/xml") } } + + def performAction(action: SubscriberAction) = { + action match { + case AlertAction(addr) => + val alert = Alert(addr) + repo.listActive().map { subscribers => + alert.sendAlert(subscribers, messagesApi) + } + } + } } diff --git a/app/models/Alert.scala b/app/models/Alert.scala index 1c37c17..74d7a7c 100644 --- a/app/models/Alert.scala +++ b/app/models/Alert.scala @@ -1,3 +1,20 @@ package models -case class Alert(address: String) +import com.twilio.rest.api.v2010.account.Message +import com.twilio.`type`.PhoneNumber +import play.api.i18n.{Lang, MessagesApi} + +import scala.concurrent.ExecutionContext + +case class Alert(address: String) { + def sendAlert(subscribers: Seq[Subscriber], messagesApi: MessagesApi) = { + subscribers.map { subscriber => + implicit val lang: Lang = Lang.get("en").get + Message + .creator(new PhoneNumber(subscriber.phone), // to + new PhoneNumber(sys.env("TWILIO_PHONE")), // from + messagesApi("action_alert", address)) + .create() + } + } +} diff --git a/app/models/SubscriberTransitions.scala b/app/models/SubscriberTransitions.scala index 5da6d26..da94d12 100644 --- a/app/models/SubscriberTransitions.scala +++ b/app/models/SubscriberTransitions.scala @@ -2,8 +2,28 @@ package models import models.SubscriberTransitions.{Complete, SelectingLanguage, SubscriptionState, Unsubscribed} +sealed trait SubscriberAction +case class AlertAction(addr: String) extends SubscriberAction + class SubscriberTransitions(subscriber : Subscriber) { - def receiveInput(input : String):(String, Option[Subscriber]) = { + def action(input: String): Option[SubscriberAction] = { + val trimmedInput = input.trim + val currentstate = SubscriberTransitions.withName(subscriber.state); + currentstate match { + case Unsubscribed => + None + case SelectingLanguage => + None + case Complete => + if (trimmedInput.length > 6 && trimmedInput.substring(0, 6).equalsIgnoreCase("report")) { + return Some(AlertAction(trimmedInput.substring(6).trim)) + } else { + return None + } + } + } + + def transition(input : String):(String, Option[Subscriber]) = { val trimmedInput = input.trim val currentstate = SubscriberTransitions.withName(subscriber.state); currentstate match { @@ -28,6 +48,8 @@ class SubscriberTransitions(subscriber : Subscriber) { } else if (trimmedInput.equalsIgnoreCase("leave")) { val newSubscriber = subscriber.copy(state = Unsubscribed.name) return ("unsubscribed_msg", Some(newSubscriber)) + } else if (trimmedInput.length > 6 && trimmedInput.substring(0, 6).equalsIgnoreCase("report")) { + return ("report_msg", None) } else { return ("error_msg", None) } diff --git a/conf/messages b/conf/messages index a379ddc..84537d3 100644 --- a/conf/messages +++ b/conf/messages @@ -12,3 +12,4 @@ subscribe_help_msg=We were unable to understand your message. Text "join" to sub unsubscribed_msg=You have been unsubscribed from receiving text message alerts. You may re-join by texting "join" to this number. unsupported_lang_msg=We were unable to understand your language choice. welcome_msg=Thank you for joining the WA Immigrant Solidarity Network's Text Message alert system to provide alerts when there is immigration (ICE) activity in our state. +report_msg=Thank you for reporting ICE activity. An alert will be sent out to all subscribers. diff --git a/test/models/SubscriberTransitionsSpec.scala b/test/models/SubscriberTransitionsSpec.scala index 170b1bb..1ad5dab 100644 --- a/test/models/SubscriberTransitionsSpec.scala +++ b/test/models/SubscriberTransitionsSpec.scala @@ -5,46 +5,47 @@ import org.scalatest.{FunSpec, Matchers} class SubscriberTransitionsSpec extends FunSpec with Matchers { describe("The subscriber transitions") { describe ("when a user is unsubscribed") { - val subscriber = Subscriber(1, "555-555-5555", None, SubscriberTransitions.Unsubscribed.name); + val subscriber = Subscriber(1, "555-555-5555", None, SubscriberTransitions.Unsubscribed.name) val subscriberTransitions = new SubscriberTransitions(subscriber) it ("should return the basic help message if an unknown message is passed") { - val (message, newSubscriber) = subscriberTransitions.receiveInput("Hi") + val (message, newSubscriber) = subscriberTransitions.transition("Hi") message shouldEqual "subscribe_help_msg" newSubscriber shouldBe None } it ("should transition the subscriber to selecting language if join is sent") { - val (message, newSubscriber) = subscriberTransitions.receiveInput("join") + val (message, newSubscriber) = subscriberTransitions.transition("join") message shouldEqual "language_selection_msg" newSubscriber.get.state shouldEqual SubscriberTransitions.SelectingLanguage.name } it ("should transition the subscriber to selecting language if join with surrounding whitespace is sent") { - val (message, newSubscriber) = subscriberTransitions.receiveInput(" \t\tjoin \n \n\t\n ") + val (message, newSubscriber) = subscriberTransitions.transition(" \t\tjoin \n \n\t\n ") message shouldEqual "language_selection_msg" newSubscriber.get.state shouldEqual SubscriberTransitions.SelectingLanguage.name } } + describe("when a user is choosing a language") { - val subscriber = Subscriber(1, "555-555-5555", None, SubscriberTransitions.SelectingLanguage.name); + val subscriber = Subscriber(1, "555-555-5555", None, SubscriberTransitions.SelectingLanguage.name) val subscriberTransitions = new SubscriberTransitions(subscriber) it("should return a help message if an unknown language is selected") { - val (message, newSubscriber) = subscriberTransitions.receiveInput("Hi") + val (message, newSubscriber) = subscriberTransitions.transition("Hi") message shouldEqual "unsupported_lang_msg" newSubscriber shouldBe None } it("should transition the subscriber to completed if a language is selected") { - val (message, newSubscriber) = subscriberTransitions.receiveInput("1") + val (message, newSubscriber) = subscriberTransitions.transition("1") message shouldEqual "confirmation_msg" newSubscriber.get.state shouldEqual SubscriberTransitions.Complete.name newSubscriber.get.language shouldEqual Some("eng") } it("should transition the subscriber to completed if a language with surrounding whitespace is selected") { - val (message, newSubscriber) = subscriberTransitions.receiveInput(" \n\n1 \t\n ") + val (message, newSubscriber) = subscriberTransitions.transition(" \n\n1 \t\n ") message shouldEqual "confirmation_msg" newSubscriber.get.state shouldEqual SubscriberTransitions.Complete.name newSubscriber.get.language shouldEqual Some("eng") @@ -52,38 +53,88 @@ class SubscriberTransitionsSpec extends FunSpec with Matchers { } describe("when a user has completed the subscription process") { - val subscriber = Subscriber(1, "555-555-5555", None, SubscriberTransitions.Complete.name); + val subscriber = Subscriber(1, "555-555-5555", None, SubscriberTransitions.Complete.name) val subscriberTransitions = new SubscriberTransitions(subscriber) it ("should return information on how to unsubscribe or change languages if an unknown message is sent while subscribed") { - val (message, newSubscriber) = subscriberTransitions.receiveInput("Hi") + val (message, newSubscriber) = subscriberTransitions.transition("Hi") message shouldEqual "error_msg" newSubscriber shouldBe None } it ("should return to the select language state if the appropriate message is sent") { - val (message, newSubscriber) = subscriberTransitions.receiveInput("change language") + val (message, newSubscriber) = subscriberTransitions.transition("change language") message shouldEqual "language_selection_msg" newSubscriber.get.state shouldEqual SubscriberTransitions.SelectingLanguage.name } it ("should return to the select language state if the appropriate message with surrounding whitespace is sent") { - val (message, newSubscriber) = subscriberTransitions.receiveInput(" \t change language \n") + val (message, newSubscriber) = subscriberTransitions.transition(" \t change language \n") message shouldEqual "language_selection_msg" newSubscriber.get.state shouldEqual SubscriberTransitions.SelectingLanguage.name } it ("should unsubscribe the user if the appropriate message is sent") { - val (message, newSubscriber) = subscriberTransitions.receiveInput("leave") + val (message, newSubscriber) = subscriberTransitions.transition("leave") message shouldEqual "unsubscribed_msg" newSubscriber.get.state shouldEqual SubscriberTransitions.Unsubscribed.name } it ("should unsubscribe the user if the appropriate message with surrounding whitespace is sent") { - val (message, newSubscriber) = subscriberTransitions.receiveInput(" \n leave \t") + val (message, newSubscriber) = subscriberTransitions.transition(" \n leave \t") message shouldEqual "unsubscribed_msg" newSubscriber.get.state shouldEqual SubscriberTransitions.Unsubscribed.name } + + it ("should stay in the complete state if a report is sent") { + val (message, newSubscriber) = subscriberTransitions.transition("report 1111 My Address Lane") + message shouldEqual "report_msg" + newSubscriber shouldBe None + } + + it ("should stay in the complete state if a report with surrounding whitespace is sent") { + val (message, newSubscriber) = subscriberTransitions.transition(" \t\n report 1111 My Address Lane\n\t\n ") + message shouldEqual "report_msg" + newSubscriber shouldBe None + } + } + } + + describe("The subscriber actions") { + describe ("when a user is unsubscribed") { + val subscriber = Subscriber(1, "555-555-5555", None, SubscriberTransitions.Unsubscribed.name) + val subscriberTransitions = new SubscriberTransitions(subscriber) + + it("should return None") { + val action = subscriberTransitions.action("Hi") + action shouldBe None + } + } + + describe("when a user is choosing a language") { + val subscriber = Subscriber(1, "555-555-5555", None, SubscriberTransitions.SelectingLanguage.name) + val subscriberTransitions = new SubscriberTransitions(subscriber) + + it("should return None") { + val action = subscriberTransitions.action("Hi") + action shouldBe None + } + } + + describe("when a user has completed the subscription process") { + val subscriber = Subscriber(1, "555-555-5555", None, SubscriberTransitions.Complete.name) + val subscriberTransitions = new SubscriberTransitions(subscriber) + + it ("should return None if a report is not sent") { + val action = subscriberTransitions.action("Hi") + action shouldBe None + } + + it ("should return the alert action if a report is sent") { + val action = subscriberTransitions.action("report 1111 My Address Lane") + action shouldBe 'isDefined + action.get shouldEqual AlertAction("1111 My Address Lane") + } } } }