Install the API:
nvm use
npm install
Run the tests:
npm test
Start the API:
DATA_URL="https://api.hurricane-response.org/api/v1/shelters/geo.json" MILE_RADIUS=30 PORT=3000 node -r esm index.js
Query the running API's webhook locally via cURL:
curl -XPOST --header "Content-Type: application/x-www-form-urlencoded" --data "Body=I'm in zip code 20149 and 70123" "http://localhost:3000/sms"
sms-location-bot is an Express app that can be run via e.g. node -r esm index.js, but see configuration documentation below.
It has a POST endpoint at /sms and is designed to be used as a Twilio webhook.
It also expects to be run in some kind of host that handles SSL termination on its behalf and so uses HTTP. (We've used Heroku for this, and a working Procfile is included in this repository.)
To run sms-location-bot, first get yourself a Twilio number that can receive and send SMS. Then, execute node index.js in some kind of container that is capable of terminating SSL and setting PORT, MILE_RADIUS and DATA_URL in the process environment (Heroku dyno, DO droplet, AWS Elastic Beanstalk, etc.) and point a stable DNS name at the running instance location.
Next, provide that DNS name plus /sms to your Twilio number's webhook for incoming messages:
Texts to your Twilio number will now process the incoming message for inputs (e.g., ZIP codes), look up relevant data in the DATA_URL file, and compile a response.
This repo is componentized in an attempt to isolate functionality specific to certain formats, contexts or external services. The repo's codebase includes:
/
|-index.js - the starting point for running the app
|-lib/ - folder containing componentized code
| |-server.js - the server-related code, called from index.js
| |-data_updater.js - the class handling data updates from the external data source (DATA_URL)
| |-zipcode_extractor.js - the class handling munging of incoming messages to extract zip codes
| |-shelters_finder.js - the adapter class handling all aspects of dealing with shelter data
| |-twilio_formatter.js - the adapter class handling outgoing message formatting
|-test/ - folder containing all tests
|-fixures/ - folder containing test fixtures
|-... test files for each component
Because the geodesy library uses ECMAScript Module syntax (import/export instead of require(...)/module.exports=), we had to upgrade the app to do so too. We use this library to compute distances between the input zip codes and the known locations.
In node.js LTS 8.x and 10.x, that syntax is only available behind the --experimental-modules flag of the node runtime.
As an alternative, the esm npm package auto-translates that module syntax on the fly. That package is included as a runtime dependency in our package.json, so that it's installed when you npm install the app.
You'll note that in the instructions above, as well as in the scripts section of the package.json and the Procfile, we've included the -r esm flag on the node and mocha. This tells node to require the esm package on start, before loading the app's code.
Here is a list of the configuration parameters sms-location-bot expects to find in its process environment:
PORT: the HTTP port for the Express server to listen onDATA_URL: the location of thegeo.json-style location fileMILE_RADIUS: the radius in miles to use for inclusion of a location in the result set sent via SMS to the user (default: 30)
sms-location-bot expects to find at DATA_URL a GeoJSON file describing the resources it is helping callers to locate. For more on the GeoJSON standard, see geojson.org.
The first use of sms-location-bot for the Hurricane Barry response in 2019 leveraged an existing geo.json file with
a shape like:
{"type":"FeatureCollection",
"features":[
{"type":"Feature",
"geometry":{"type":"Point","coordinates":[xx.nnnn,yy.nnnn]},
"properties":{"accepting":"yes",
"shelter":"Shelter Name","address":"Full mailing address","city":"CITY_NAME","state":"ST","county":"County Name","zip":"99999","phone":null,"updated_by":null,"notes":null,"volunteer_needs":null,"longitude":-99.7487,"latitude":40.7868,"supply_needs":null,"source":"Source org id","google_place_id":null,"special_needs":null,"id":2,"archived":false,"pets":"No","pets_notes":null,"needs":[],"updated_at":"2019-07-11T13:52:43-05:00","updatedAt":"2019-07-11T13:52:43-05:00","last_updated":"2019-07-11T13:52:43-05:00","cleanPhone":"badphone"}},
...
]
}The extractGeoJsonData function in lib/data_updater.js expects the standard GeoJSON format implicitly, as well as a zip property as part of the properties object of each feature in the features array.
Working with other data formats, even ones that depend on some other key than ZIP code like area code, date, etc. would need to alter extractGeoJsonData to suit.
In addition, the SheltersFinder class in lib/shelters_finder.js expects and makes use of several other property names within the properties object of each feature. Excluding the use of zip code for location query, this class can be considered an adapter to the data, containing all location-specific content. For different data, you can replace this class with something else, and call its primary finder interface (findShelters(...) in SheltersFinder) from lib/server.js. (This hasn't been tried, so please post an issue here if you find this is not true!)
Tests cover a majority of functionality of the classes in lib, but do not yet cover end-to-end testing of the API server portion of the code.
PRs are welcome to enhance our test coverage!
We try to keep the docs up-to-date with the codebase. To that end, we've used JSdoc in the code for function- and class-level documentation. This file is also kept up-to-date.
If you notice something missing, or an inaccuracy, in the documentation, please submit a PR to fix it. We appreciate your help!
