diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3aa66d4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules +gitignore diff --git a/Procfile b/Procfile new file mode 100644 index 0000000..e1d4131 --- /dev/null +++ b/Procfile @@ -0,0 +1 @@ +web: node app.js diff --git a/README.md b/README.md index cb007d2..2aea861 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,13 @@ -# project_what_have_you_done -Build an application to help track the legislative activities of your local representatives. +# REPRESENTED + +It's more important than ever to make your voice heard, and to make sure the people who speak for you have your best interests at heart. With this app you can find the Senators and Representatives for your zip code, getting access to both their most recent votes and their contact information. + +## See it in action + +Check it out [here](https://enigmatic-harbor-79584.herokuapp.com/)! + +## Built with + +* [Express](https://github.com/expressjs/express) - Web framework +* [Bootstrap](http://getbootstrap.com/) - Styling +* [Sunlight Labs](https://sunlightlabs.github.io/congress/index.html) - API diff --git a/app.js b/app.js new file mode 100644 index 0000000..1311727 --- /dev/null +++ b/app.js @@ -0,0 +1,50 @@ +const request = require('request'); +const port = process.env.PORT || '3000'; +var express = require('express'); +var path = require('path'); +var favicon = require('serve-favicon'); +var logger = require('morgan'); +var cookieParser = require('cookie-parser'); +var bodyParser = require('body-parser'); + +var index = require('./routes/index'); +var representatives = require('./routes/representatives'); +var zipcoderesults = require('./routes/zipcoderesults'); + +var app = express(); + +// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'hbs'); + +app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); +app.use(logger('dev')); +app.use(bodyParser.json()); +app.use(bodyParser.urlencoded({ extended: false })); +app.use(cookieParser()); +app.use(express.static(path.join(__dirname, 'public'))); + +app.use('/', index); +app.use('/representatives', representatives); +app.use('/zipcoderesults', zipcoderesults); + +// catch 404 and forward to error handler +app.use(function(req, res, next) { + var err = new Error('Not Found'); + err.status = 404; + next(err); +}); + +// error handler +app.use(function(err, req, res, next) { + // set locals, only providing error in development + res.locals.message = err.message; + res.locals.error = req.app.get('env') === 'development' ? err : {}; + + // render the error page + res.status(err.status || 500); + res.render('error'); +}); + +app.listen(port); +module.exports = app; diff --git a/bin/www b/bin/www new file mode 100755 index 0000000..22f988a --- /dev/null +++ b/bin/www @@ -0,0 +1,90 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var app = require('../app'); +var debug = require('debug')('project-what-have-you-done:server'); +var http = require('http'); + +/** + * Get port from environment and store in Express. + */ + +var port = normalizePort(process.env.PORT || '3000'); +app.set('port', port); + +/** + * Create HTTP server. + */ + +var server = http.createServer(app); + +/** + * Listen on provided port, on all network interfaces. + */ + +server.listen(port); +server.on('error', onError); +server.on('listening', onListening); + +/** + * Normalize a port into a number, string, or false. + */ + +function normalizePort(val) { + var port = parseInt(val, 10); + + if (isNaN(port)) { + // named pipe + return val; + } + + if (port >= 0) { + // port number + return port; + } + + return false; +} + +/** + * Event listener for HTTP server "error" event. + */ + +function onError(error) { + if (error.syscall !== 'listen') { + throw error; + } + + var bind = typeof port === 'string' + ? 'Pipe ' + port + : 'Port ' + port; + + // handle specific listen errors with friendly messages + switch (error.code) { + case 'EACCES': + console.error(bind + ' requires elevated privileges'); + process.exit(1); + break; + case 'EADDRINUSE': + console.error(bind + ' is already in use'); + process.exit(1); + break; + default: + throw error; + } +} + +/** + * Event listener for HTTP server "listening" event. + */ + +function onListening() { + var addr = server.address(); + var bind = typeof addr === 'string' + ? 'pipe ' + addr + : 'port ' + addr.port; + debug('Listening on ' + bind); +} diff --git a/models/representatives.js b/models/representatives.js new file mode 100644 index 0000000..6dc1038 --- /dev/null +++ b/models/representatives.js @@ -0,0 +1,18 @@ +class Representative { + constructor(id, name, chamber, party, phone, website) { + this.id = id; + this.name = name; + this.chamber = chamber; + this.party = party; + this.phone = phone; + this.website = website; + } +}; + +var representatives = [ +]; + +module.exports = { + Representative, + representatives +}; diff --git a/models/votes.js b/models/votes.js new file mode 100644 index 0000000..b1aa9a5 --- /dev/null +++ b/models/votes.js @@ -0,0 +1,16 @@ +class Vote { + constructor(billID, billURL, billTitle, repVote) { + this.billID = billID; + this.billURL = billURL; + this.billTitle = billTitle; + this.repVote = repVote; + } +}; + +var votes = [ +]; + +module.exports = { + Vote, + votes +}; diff --git a/package.json b/package.json new file mode 100644 index 0000000..8e486cc --- /dev/null +++ b/package.json @@ -0,0 +1,23 @@ +{ + "name": "project-what-have-you-done", + "version": "0.0.0", + "engines": { + "node": "6.10.0", + "npm": "3.10.x" + }, + "private": true, + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1", + "start": "node index.js" + }, + "dependencies": { + "body-parser": "~1.16.0", + "cookie-parser": "~1.4.3", + "debug": "~2.6.0", + "express": "~4.14.1", + "hbs": "~4.0.1", + "morgan": "~1.7.0", + "request": "^2.81.0", + "serve-favicon": "~2.3.2" + } +} diff --git a/public/favicon.ico b/public/favicon.ico new file mode 100644 index 0000000..b85d6a3 Binary files /dev/null and b/public/favicon.ico differ diff --git a/public/images/Thumb Up-50.png b/public/images/Thumb Up-50.png new file mode 100644 index 0000000..c417b0a Binary files /dev/null and b/public/images/Thumb Up-50.png differ diff --git a/public/images/Thumbs Down-50.png b/public/images/Thumbs Down-50.png new file mode 100644 index 0000000..2cd3458 Binary files /dev/null and b/public/images/Thumbs Down-50.png differ diff --git a/public/images/US Capitol-96.png b/public/images/US Capitol-96.png new file mode 100644 index 0000000..b85d6a3 Binary files /dev/null and b/public/images/US Capitol-96.png differ diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css new file mode 100644 index 0000000..ce0c4cf --- /dev/null +++ b/public/stylesheets/style.css @@ -0,0 +1,21 @@ +body { + height: 100%; + /*margin: 0;*/ + padding: 0px 0px 20px 0px; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +} + +.wrapper { + min-height: 100%; + padding-bottom:45px; +} + +a { + color: #00B7FF; +} + + +.footer, +.push { + height: 20px; +} diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000..43464ff --- /dev/null +++ b/routes/index.js @@ -0,0 +1,13 @@ +var express = require('express'); +var router = express.Router(); +var {Representative, representatives} = require('../models/representatives') + +router.use('/', function (req, res, next) { + next() +}) + +router.get('/', function(req, res, next) { + res.render('index'); +}); + +module.exports = router; diff --git a/routes/representatives.js b/routes/representatives.js new file mode 100644 index 0000000..8032275 --- /dev/null +++ b/routes/representatives.js @@ -0,0 +1,59 @@ +var express = require('express'); +var router = express.Router(); +const request = require('request'); +var { Representative, representatives } = require('../models/representatives'); +var { Vote, votes } = require('../models/votes'); + +const findRep = name => { + return representatives.find(representative => name == representative.name); +}; + +router.use('/:name', function(req, res, next) { + var representative = findRep(req.params.name); + + var options = { + url: 'https://congress.api.sunlightfoundation.com/' + + 'votes?fields=voter_ids,question,bill&voter_ids.' + + representative.id + + '__exists=true&bill_id__exists=true&vote_type=passage' + }; + + function voteData(error, response, body) { + if (!error && response.statusCode == 200) { + var info = JSON.parse(body); + + if (votes.length > 0) { + votes = []; + } + + for (var i = 0; i < info.results.length; i++) { + if ('bill' in info.results[i]) { + votes.push( + new Vote( + info.results[i].bill.bill_id, + info.results[i].bill.urls.govtrack, + info.results[i].bill.official_title, + info.results[i].voter_ids[representative.id] === 'Yea' + ? true + : false + ) + ); + } else { + // Exclude votes for non-bills + } + } + next(); + } + } + request(options, voteData); +}); + +router.get('/:name', function(req, res, next) { + var representative = findRep(req.params.name); + res.render('representatives', { + representative: representative, + votes: votes + }); +}); + +module.exports = router; diff --git a/routes/zipcoderesults.js b/routes/zipcoderesults.js new file mode 100644 index 0000000..20481cb --- /dev/null +++ b/routes/zipcoderesults.js @@ -0,0 +1,51 @@ +var express = require('express'); +var router = express.Router(); +const request = require('request'); +var { Representative, representatives } = require('../models/representatives'); + +router.use('/', function(req, res, next) { + var zipcode = req.query.zipcode; + var options = { + url: 'https://congress.api.sunlightfoundation.com/legislators/locate/?zip=' + + zipcode + }; + + function repData(error, response, body) { + if (!error && response.statusCode == 200) { + var info = JSON.parse(body); + + if (representatives.length > 0) { + representatives = []; + } + + for (var i = 0; i < info.results.length; i++) { + representatives.push( + new Representative( + info.results[i].bioguide_id, + info.results[i].first_name + ' ' + info.results[i].last_name, + info.results[i].chamber, + info.results[i].party, + info.results[i].phone, + info.results[i].website + ) + ); + } + next(); + } else { + console.log('Something went wrong while updating reps based on zip'); + } + } + + var getReps = function(zipcode) { + request(options, repData); + }; + + getReps(zipcode); +}); + +router.get('/', function(req, res, next) { + let zipcode = req.query.zipcode; + res.render('zipcoderesults', { representatives, zipcode }); +}); + +module.exports = router; diff --git a/views/error.hbs b/views/error.hbs new file mode 100644 index 0000000..0659765 --- /dev/null +++ b/views/error.hbs @@ -0,0 +1,3 @@ +
{{error.stack}}
diff --git a/views/index.hbs b/views/index.hbs
new file mode 100644
index 0000000..f793c6a
--- /dev/null
+++ b/views/index.hbs
@@ -0,0 +1,15 @@
+
+
+
+
+