Skip to content

Commit

Permalink
Merge pull request #24 from benweedon/basic_sms_report
Browse files Browse the repository at this point in the history
Support reporting raids with SMS
  • Loading branch information
joshmcfarlane committed Nov 5, 2017
2 parents 53d09a3 + 0536be7 commit 942181f
Show file tree
Hide file tree
Showing 6 changed files with 126 additions and 28 deletions.
13 changes: 2 additions & 11 deletions app/controllers/AlertController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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}
Expand All @@ -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(",")))
}
Expand Down
18 changes: 17 additions & 1 deletion app/controllers/TwilioController.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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._
Expand All @@ -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)
}
Expand All @@ -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)
}
}
}
}
19 changes: 18 additions & 1 deletion app/models/Alert.scala
Original file line number Diff line number Diff line change
@@ -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()
}
}
}
24 changes: 23 additions & 1 deletion app/models/SubscriberTransitions.scala
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -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)
}
Expand Down
1 change: 1 addition & 0 deletions conf/messages
Original file line number Diff line number Diff line change
Expand Up @@ -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.
79 changes: 65 additions & 14 deletions test/models/SubscriberTransitionsSpec.scala
Original file line number Diff line number Diff line change
Expand Up @@ -5,85 +5,136 @@ 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")
}
}

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")
}
}
}
}

0 comments on commit 942181f

Please sign in to comment.