Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support reporting raids with SMS #24

Merged
merged 1 commit into from
Nov 5, 2017
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
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")
}
}
}
}