45 |
46 |
--------------------------------------------------------------------------------
/public/node.svg:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
18 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # Contact Mapper
2 |
3 | This is a very basic demonstation app for [Heroku Connect](https://www.heroku.com/connect). It is based
4 | on the [Heroku Node starter app](https://github.com/heroku/node-js-getting-started).
5 |
6 | The application relies on Heroku Connect to sync Contact records into Postgres. We load contact
7 | records from the db, then use the Google Geolocation API to geocode the address so we can locate
8 | each contact properly on the map.
9 |
10 | For better performance address locations are stored back in our database so we only have to look
11 | them up once.
12 |
13 | 
14 |
15 | ## Install
16 |
17 | Use the Heroku Button below to clone the app:
18 |
19 | [](https://heroku.com/deploy?template=https://github.com/scottpersinger/contactmap-demoapp)
20 |
21 | ## Provision Heroku Connect
22 |
23 | Click on the Heroku Connect addon on your new app to access the Heroku Connect dashboard.
24 |
25 | Add a mapping for the `Contact` object in Salesforce, and select these fields:
26 |
27 | ```
28 | Email
29 | MailingCity
30 | MailingState
31 | MailingStreet
32 | Name
33 | ```
34 |
35 | And save the mapping. That's it! Once the data is synchronized just reload the Contact Map app to see 200
36 | Contact records placed on a Google Map of the United States.
37 |
38 | ## Understanding the app
39 |
40 | This app accesses data in a Postgres database. To access the database we use
41 | the [Node PG](https://www.npmjs.com/package/pg) postgres driver and the
42 | wonderful [Sequelize](http://sequelize.readthedocs.org/) ORM for Node.
43 |
44 | In [index.js](index.js) we define two model classes. One is the `Contact`
45 | class which pulls records from the `salesforce` schema which is synchronized
46 | by Heroku Connect. The second is the `Geocode` class which is simply used to
47 | store geolocation coordinates retrieved from the Google Geolocation API.
48 |
49 | Finally the [geocde.js](geocode.js) module takes care of sending a `Contact`
50 | address to the Google Geocode API to retrieve the latitude/longitude for
51 | displaying cards on the map.
52 |
53 | The list of Contacts with annotated Lat/Lng coordinates is passed to [index.html](index.html).
54 | The html page contains JS to create the map and annotate it with all the contacts.
55 |
56 |
57 |
--------------------------------------------------------------------------------
/index.js:
--------------------------------------------------------------------------------
1 | var Sequelize = require('sequelize');
2 | var async = require('async');
3 | var geocode = require('./geocode');
4 |
5 | // ************** DATABASE MODELS ***********************
6 |
7 | var dburl = process.env.DATABASE_URL || 'postgres://localhost/herokuconnect';
8 | var db = new Sequelize(dburl, {
9 | dialectOptions: {
10 | ssl: dburl.indexOf('localhost') == -1
11 | },
12 | logging: false
13 | });
14 |
15 |
16 | var Contact = db.define('Contact', {
17 | id: Sequelize.INTEGER,
18 | sfid: Sequelize.STRING,
19 | email: Sequelize.STRING,
20 | name: Sequelize.STRING,
21 | mailingcity: Sequelize.STRING,
22 | mailingstate: Sequelize.STRING,
23 | mailingstreet: Sequelize.STRING
24 | }, {
25 | timestamps: false,
26 | freezeTableName: true,
27 | schema: 'salesforce',
28 | tableName: 'contact'
29 | }
30 | );
31 |
32 | Geocode = db.define('geocode', {
33 | id: Sequelize.INTEGER,
34 | address: Sequelize.STRING,
35 | lat: Sequelize.FLOAT,
36 | lon: Sequelize.FLOAT
37 | });
38 |
39 | // Create geocode cache table if not exists
40 | db.sync();
41 |
42 | // ************** GEOCODING LOGIC ***********************
43 |
44 | var contact_locations = [];
45 |
46 | function geocode_contact(contact, callback) {
47 | if (contact.values.mailingstreet ||
48 | contact.values.mailingcity ||
49 | contact.values.mailingstate) {
50 |
51 | var addr = contact.values.mailingstreet + "," +
52 | contact.values.mailingcity + "," +
53 | contact.values.mailingstate;
54 |
55 | geocode(addr, function(geocode) {
56 | if (geocode) {
57 | callback(null, {name: escape(contact.values.name), lat: geocode.lat, lon:geocode.lon});
58 | } else {
59 | callback();
60 | }
61 | });
62 | } else {
63 | callback();
64 | }
65 | }
66 |
67 | function load_contacts(callback) {
68 | Contact.findAll({limit:200}).then(function(rows) {
69 | async.map(rows, geocode_contact, callback);
70 | });
71 | }
72 |
73 |
74 |
75 | // EXPRESS
76 |
77 | var express = require('express');
78 | var app = express();
79 |
80 | app.set('port', (process.env.PORT || 5000));
81 | app.use(express.static(__dirname + '/public'));
82 | app.set('views', './views');
83 | app.set('view engine', 'ejs');
84 |
85 | app.get('/', function(req, res) {
86 | load_contacts(function(error, contact_locations) {
87 | console.log("Locations: ", contact_locations);
88 | res.render('index', {contact_locations: contact_locations.filter(function(val) { return val })});
89 | });
90 | });
91 |
92 | app.get('/create', function(req, res){
93 | var create_url = 'https://connect.heroku.com/dashboard-next/create-connection';
94 | // Redirect to Heroku Connect dashboard to finish setup
95 | var hostRe = new RegExp(/^([^.]+)\.herokuapp\.com$/);
96 |
97 | var match = req.headers.host.match(hostRe);
98 |
99 | if (match) {
100 | res.redirect(create_url+'?create='+match[1]);
101 | } else {
102 | res.status(400).send("You need to be running on Heroku!");
103 | }
104 | });
105 |
106 | app.listen(app.get('port'), function() {
107 | console.log("Node app is running at localhost:" + app.get('port'));
108 | });
109 |
--------------------------------------------------------------------------------