From c16a31a8abd38779dbadbc219746ca21b78aa9de Mon Sep 17 00:00:00 2001 From: Ben Weedon Date: Sat, 11 Nov 2017 16:32:04 -0800 Subject: [PATCH 1/2] Implement basic google geocoding for alert web form This change will make a call to the Google Maps geocoding API to get the canonical address text and latitude/longitude of the address. --- README.md | 6 ++++-- app/controllers/AlertController.scala | 20 ++++++++++++++++++-- app/models/Address.scala | 23 +++++++++++++++++++++++ build.sbt | 16 +++++++++------- 4 files changed, 54 insertions(+), 11 deletions(-) create mode 100644 app/models/Address.scala diff --git a/README.md b/README.md index e843117..c45c009 100644 --- a/README.md +++ b/README.md @@ -1,13 +1,13 @@ # Sms Alert System for Scala [![Build Status](https://travis-ci.org/ColectivaLegal/ScalaSmsAlertSystem.svg?branch=master)](https://travis-ci.org/ColectivaLegal/ScalaSmsAlertSystem) -Below you will find basic setup instructions for developing the SysmAlertSystem. +Below you will find basic setup instructions for developing the SysmAlertSystem. To begin with, please ensure you have the following installed on your local development system: * JDK 8 * [SBT](http://www.scala-sbt.org/download.html) -JDK8 is currently required due to some incompatibilities between SBT's ivy implementation and JDK9 +JDK8 is currently required due to some incompatibilities between SBT's ivy implementation and JDK9 ## Twilio Setup @@ -45,6 +45,8 @@ export TWILIO_USERNAME="..." export TWILIO_PASSWORD="..." # the phone number you created export TWILIO_PHONE="..." +# your Google Maps geocoding API key +export MAPS_API_KEY="..." sbt run ``` diff --git a/app/controllers/AlertController.scala b/app/controllers/AlertController.scala index 6bab741..a9dd743 100644 --- a/app/controllers/AlertController.scala +++ b/app/controllers/AlertController.scala @@ -2,6 +2,8 @@ package controllers import javax.inject.{Inject, Singleton} +import models.Address +import models.Alert import models.SubscriberRepository import play.api.data.Form import play.api.data.Forms._ @@ -18,7 +20,7 @@ class AlertController @Inject()(cc: ControllerComponents, repo: SubscriberReposi val alertForm = Form( mapping( "address" -> nonEmptyText - )(models.Alert.apply)(models.Alert.unapply) + )(Alert.apply)(Alert.unapply) ) def get = Action { implicit request => Ok(views.html.alert(alertForm)) @@ -30,8 +32,22 @@ class AlertController @Inject()(cc: ControllerComponents, repo: SubscriberReposi Future.successful(BadRequest(views.html.alert(formWithErrors))) }, alert => { + val addresses = Address.geocode(alert.address) + if (addresses.length == 0) { + // TODO (benweedon 11/11/2017): Build in a mechanism for users to + // retry if no address match was found. + throw new IllegalArgumentException("no matching addresses found") + } else if (addresses.length > 1) { + // TODO (benweedon 11/11/2017): Build in a mechanism for users to + // validate which of the potential addresses they intended. + throw new IllegalArgumentException("the address entered was ambiguous") + } + // TODO (benweedon 11/11/2017): Find a better way to do this than + // defining a new alert. Perhaps, for example, the form should just + // produce an address string, rather than an alert object. + val alertToSend = Alert(addresses.head.toString) repo.listActive().map { subscribers => - val messages = alert.sendAlert(subscribers, messagesApi) + val messages = alertToSend.sendAlert(subscribers, messagesApi) Redirect(routes.HomeController.index()) .flashing("success" -> ("Done! Messages sent with IDs " + messages.map(_.getSid()).mkString(","))) } diff --git a/app/models/Address.scala b/app/models/Address.scala new file mode 100644 index 0000000..d987e2d --- /dev/null +++ b/app/models/Address.scala @@ -0,0 +1,23 @@ +package models + +import scala.collection.mutable.ListBuffer + +import com.google.maps.{GeoApiContext, GeocodingApi} +import com.google.maps.model.GeocodingResult + +case class Address(address: String, lat: Double, lng: Double) { + override def toString = address +} + +object Address { + private val _context = new GeoApiContext.Builder().apiKey(sys.env("MAPS_API_KEY")).build(); + + def geocode(address: String) = { + val results = GeocodingApi.geocode(_context, address).await() + var addresses = new ListBuffer[Address]() + for (result <- results) { + addresses += Address(result.formattedAddress, result.geometry.location.lat, result.geometry.location.lng) + } + addresses.to[List] + } +} diff --git a/build.sbt b/build.sbt index 1a73016..ffb53df 100644 --- a/build.sbt +++ b/build.sbt @@ -1,13 +1,13 @@ name := "SmsAlertSystemV2" - -version := "1.0" - + +version := "1.0" + lazy val `smsalertsystemv2` = (project in file(".")).enablePlugins(PlayScala) resolvers += "scalaz-bintray" at "https://dl.bintray.com/scalaz/releases" - + resolvers += "Akka Snapshot Repository" at "http://repo.akka.io/snapshots/" - + scalaVersion := "2.12.2" libraryDependencies ++= Seq( @@ -20,5 +20,7 @@ libraryDependencies ++= Seq( "com.typesafe.play" %% "play-slick-evolutions" % "3.0.2", "com.h2database" % "h2" % "1.4.194", "com.typesafe.akka" %% "akka-testkit" % "2.5.4" % "test", - "org.scalatest" %% "scalatest" % "3.0.4" % "test" -) \ No newline at end of file + "org.scalatest" %% "scalatest" % "3.0.4" % "test", + "com.google.maps" % "google-maps-services" % "0.2.4", + "org.slf4j" % "slf4j-nop" % "1.7.25" // needed for com.google.maps +) From 9dc4b793d837b0e8255ecb3f4ecf272698eec266 Mon Sep 17 00:00:00 2001 From: Ben Weedon Date: Sat, 11 Nov 2017 16:43:30 -0800 Subject: [PATCH 2/2] Implement basic google geocoding for sms reporting --- app/controllers/AlertController.scala | 3 +-- app/controllers/TwilioController.scala | 14 ++++++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/app/controllers/AlertController.scala b/app/controllers/AlertController.scala index a9dd743..f137a15 100644 --- a/app/controllers/AlertController.scala +++ b/app/controllers/AlertController.scala @@ -2,8 +2,7 @@ package controllers import javax.inject.{Inject, Singleton} -import models.Address -import models.Alert +import models.{Address, Alert} import models.SubscriberRepository import play.api.data.Form import play.api.data.Forms._ diff --git a/app/controllers/TwilioController.scala b/app/controllers/TwilioController.scala index fa7eac3..72fa836 100644 --- a/app/controllers/TwilioController.scala +++ b/app/controllers/TwilioController.scala @@ -3,7 +3,7 @@ package controllers import javax.inject.{Inject, Singleton} import com.twilio.twiml.{Body, Message, MessagingResponse} -import models.{Alert} +import models.{Address, Alert} import models.{SubscriberAction, AlertAction} import models.{SubscriberRepository, SubscriberTransitions} import play.api.data.Form @@ -51,7 +51,17 @@ class TwilioController @Inject()(cc: ControllerComponents, repo: SubscriberRepos def performAction(action: SubscriberAction) = { action match { case AlertAction(addr) => - val alert = Alert(addr) + val addresses = Address.geocode(addr) + if (addresses.length == 0) { + // TODO (benweedon 11/11/2017): Build in a mechanism for users to + // retry if no address match was found. + throw new IllegalArgumentException("no matching addresses found") + } else if (addresses.length > 1) { + // TODO (benweedon 11/11/2017): Build in a mechanism for users to + // validate which of the potential addresses they intended. + throw new IllegalArgumentException("the address entered was ambiguous") + } + val alert = Alert(addresses.head.toString) repo.listActive().map { subscribers => alert.sendAlert(subscribers, messagesApi) }