diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c2658d7 --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules/ diff --git a/README.md b/README.md index cb007d2..f04d701 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,6 @@ # project_what_have_you_done Build an application to help track the legislative activities of your local representatives. + +Richard Bell + +Live on Heroku: https://frozen-coast-62273.herokuapp.com diff --git a/app.js b/app.js new file mode 100644 index 0000000..eb47360 --- /dev/null +++ b/app.js @@ -0,0 +1,48 @@ +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 users = require('./routes/users'); +var legislators = require('./routes/legislators') + +var app = express(); + +// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'hbs'); + +// uncomment after placing your favicon in /public +//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('/users', users); +app.use('/legislators', legislators); + +// 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'); +}); + +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/package.json b/package.json new file mode 100644 index 0000000..dccf423 --- /dev/null +++ b/package.json @@ -0,0 +1,19 @@ +{ + "name": "project-what-have-you-done", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "node ./bin/www" + }, + "dependencies": { + "body-parser": "~1.17.1", + "cookie-parser": "~1.4.3", + "debug": "~2.6.3", + "express": "~4.15.2", + "hbs": "~4.0.1", + "morgan": "~1.8.1", + "request": "^2.81.0", + "serve-favicon": "~2.4.2", + "sunlight": "file:sunlight" + } +} diff --git a/public/javascripts/indexFormHandler.js b/public/javascripts/indexFormHandler.js new file mode 100644 index 0000000..6fffc84 --- /dev/null +++ b/public/javascripts/indexFormHandler.js @@ -0,0 +1,14 @@ +$(document).ready(function() { + console.log("ready to rock and roll"); + +}); + +var clickGo = function() { + var zipInput = $('#zipInput').val(); + $('.warning-message').remove(); + if (!(zipInput.length != 5 || isNaN(zipInput))) { + location.href = "/legislators/zip/" + zipInput; + } else { + $('#zipInput').after("") + }; +} diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css new file mode 100644 index 0000000..debf00f --- /dev/null +++ b/public/stylesheets/style.css @@ -0,0 +1,30 @@ +#zipInput { + width: 90px; +} + +.navbar-title { + font-size: 2.5em; + line-height: 3em; + padding-right: 20px; +} + +a { + color: #00B7FF; +} + +.navbar-brand { + padding-left: 50px; +} + +.warning-message { + margin-left: 10px; + line-height: 2.5em; +} + +.voting-record { + padding-left: 20px; +} + +body { + padding-top: 50px; + padding-bottom: 20px; } diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000..31e1fd8 --- /dev/null +++ b/routes/index.js @@ -0,0 +1,21 @@ +var express = require('express'); +var router = express.Router(); +const sunlight = require('sunlight'); + +/* GET home page. */ +router.get('/', function(req, res, next) { + res.render('index', { title: 'Voters are Watching' }); +}); + +router.get('/bill/:bill_id', function(req, res, next) { + sunlight.bill(req.params.bill_id, function(billDetails) { + + res.render('bill', { + title: 'Bill Details', + bill_id: req.params.bill_id, + bill: billDetails + }); +}); +}); + +module.exports = router; diff --git a/routes/legislators.js b/routes/legislators.js new file mode 100644 index 0000000..9166f9b --- /dev/null +++ b/routes/legislators.js @@ -0,0 +1,54 @@ +var express = require('express'); +var router = express.Router(); +const sunlight = require('sunlight'); + +/* GET home page. */ +router.get('/', function(req, res, next) { + res.redirect('/'); +}); + +router.get('/zip/:zip', function(req, res, next) { + var tempZip = req.params.zip; + if (tempZip.length != 5 || isNaN(tempZip)) { + res.redirect('/'); + } else { + sunlight.locate(tempZip, function(legislatorsByZip) { + + res.render('legislators', { + title: 'Your Local Legislators', + zip: req.params.zip, + legislators: legislatorsByZip + }); + }); + }; +}); + +router.get('/bioguide/:bioguide', function(req, res, next) { + var currentBioguide = req.params.bioguide; + + sunlight.legislator(currentBioguide, function(legislatorData) { + sunlight.votes(currentBioguide, function(legislatorVotes){ + + res.render('legislator', { + title: 'Legislator Voting Record', + bioguide: currentBioguide, + legislator: legislatorData, + votes: legislatorVotes + }); + + }); + }); + + + + + + + + +}); + + + + +module.exports = router; diff --git a/routes/users.js b/routes/users.js new file mode 100644 index 0000000..623e430 --- /dev/null +++ b/routes/users.js @@ -0,0 +1,9 @@ +var express = require('express'); +var router = express.Router(); + +/* GET users listing. */ +router.get('/', function(req, res, next) { + res.send('respond with a resource'); +}); + +module.exports = router; diff --git a/sunlight/package.json b/sunlight/package.json new file mode 100644 index 0000000..b5a1d0f --- /dev/null +++ b/sunlight/package.json @@ -0,0 +1,12 @@ +{ + "name": "sunlight", + "version": "1.0.0", + "description": "", + "main": "sunlight.js", + "scripts": { + "test": "echo \"Error: no test specified\" && exit 1" + }, + "keywords": [], + "author": "", + "license": "ISC" +} diff --git a/sunlight/sunlight.js b/sunlight/sunlight.js new file mode 100644 index 0000000..2a39aff --- /dev/null +++ b/sunlight/sunlight.js @@ -0,0 +1,123 @@ +const request = require('request'); + +const baseURL = 'https://congress.api.sunlightfoundation.com'; +const pathToZip = '/legislators/locate?zip='; +const pathToLegislator = '/legislators?bioguide_id='; +const pathToVote = '/votes?voter_ids.'; +const pathToVoteClose = '__exists=true'; +const voteFieldsToRequest = '&fields=roll_id,bill_id,question,result,url,voted_at,voters,breakdown' +const pathToBill = '/bills?bill_id='; + +var congress = { + + + idiots: function() { + console.log('Congress are idiots'); + }, + +// returns array with all legislators for that zip + locate: function(zip, callback) { + request(baseURL + pathToZip + zip, function(error, response, body) { + if (!error & response.statusCode === 200) { + callback(JSON.parse(body).results); + } + }) + }, + +// returns object containing legislator details + legislator: function(bioguide, callback) { + request(baseURL + pathToLegislator + bioguide, function(error, response, body) { + if (!error & response.statusCode === 200) { + callback(JSON.parse(body).results[0]); + } + }) + }, + +// returns an array of objects holding recent votes for offical with bioguide + votes: function(bioguide, callback) { + request(baseURL + pathToVote + bioguide + pathToVoteClose + voteFieldsToRequest, function(error, response, body) { + var recentVotes = []; + var voteResults = []; + var voteResult = {}; + if (!error & response.statusCode === 200) { + voteResults = JSON.parse(body).results; + voteResults.forEach(function(aVote){ + voteResult.roll_id = aVote.roll_id; + voteResult.bill_id = aVote.bill_id; + voteResult.question = aVote.question; + voteResult.result = aVote.result; + switch (aVote.result) { + case 'Passed': + voteResult.passed = true; + voteResult.failed = false; + break; + case 'Bill Passed': + voteResult.passed = true; + voteResult.failed = false; + break; + case 'Nomination Confirmed': + voteResult.passed = true; + voteResult.failed = false; + break; + case 'Cloture Motion Agreed to': + voteResult.passed = true; + voteResult.failed = false; + break; + case 'Joint Resolution Passed': + voteResult.passed = true; + voteResult.failed = false; + break; + case 'Resolution of Ratification Agreed to': + voteResult.passed = true; + voteResult.failed = false; + break; + case 'Motion to Proceed Agreed to': + voteResult.passed = true; + voteResult.failed = false; + break; + case 'Failed': + voteResult.passed = false; + voteResult.failed = true; + break; + default: + voteResult.passed = false; + voteResult.failed = false; + } + voteResult.url = aVote.urls; + voteResult.voted_at = aVote.voted_at; + voteResult.vote = aVote.voters[bioguide].vote; + switch (voteResult.vote) { + case 'Yea': + voteResult.yea = true; + voteResult.nay = false; + break; + case 'Nay': + voteResult.yea = false; + voteResult.nay = true; + break; + default: + voteResult.yea = false; + voteResult.nay = false; + + } + voteResult.breakdown = aVote.breakdown.total; + voteResult.breakdown.notVoting = aVote.breakdown.total['Not Voting']; + recentVotes.push(voteResult); + voteResult = {}; + }) + callback(recentVotes); + } + }) + }, + +// returns an object with key details of a bill_id + bill: function(bill_id, callback) { + request(baseURL + pathToBill + bill_id, function(error, response, body) { + if (!error & response.statusCode === 200) { + callback(JSON.parse(body).results[0]); + } + }) + } +} + +module.exports = congress; diff --git a/test_sunlight_API_module.js b/test_sunlight_API_module.js new file mode 100644 index 0000000..cb408b4 --- /dev/null +++ b/test_sunlight_API_module.js @@ -0,0 +1,10 @@ + +const sunlight = require('./sunlight/sunlight'); + + + +sunlight.idiots(); +//sunlight.locate('98019', function(legislators){console.log(legislators);}); +//sunlight.legislator('M001111', function(legislatorData){console.log(legislatorData);}); +//sunlight.votes('M001111', function(legislatorData){console.log(legislatorData[0].passed);}); +sunlight.bill('hjres42-115', function(bill){console.log(bill);}); diff --git a/views/bill.hbs b/views/bill.hbs new file mode 100644 index 0000000..d84ee5a --- /dev/null +++ b/views/bill.hbs @@ -0,0 +1,17 @@ + + +
Bill ID: {{bill_id}}
+Title: {{bill.official_title}}
+House Status {{bill.history.house_passage_result}}
+Senate Status {{bill.history.senate_passage_result}}
+Signed:{{bill.awaiting_signature}}
+Enacted at: {{bill.history.enacted_at}}
+ +Sponsor: {{bill.sponsor.title}} {{bill.sponsor.first_name}} {{bill.sponsor.last_name}}
+{{error.stack}}
diff --git a/views/index.hbs b/views/index.hbs
new file mode 100644
index 0000000..ad98c4d
--- /dev/null
+++ b/views/index.hbs
@@ -0,0 +1,22 @@
+Enter your zip code below and we'll show you a list of your federal legislators along with their most recent votes. Keep them accountable!
+ +{{legislator.title}} {{legislator.first_name}} {{legislator.middle_name}} {{legislator.last_name}}
+Phone: {{legislator.phone}}
+Email: {{legislator.oc_email}}
+Term Start: {{legislator.term_start}}
+Term End: {{legislator.term_end}}
+Twitter: @{{legislator.twitter_id}}
+Facebook: {{legislator.facebook_id}}
+ +
+ | Roll Call ID | +Question | +Bill ID | +Vote Outcome | +Your Rep Vote | + + {{#each votes as |vote|}} +||||
|---|---|---|---|---|---|---|---|---|
| {{vote.roll_id}} | +{{vote.question}} | +{{vote.bill_id}} | + {{#if vote.passed}} +{{vote.result}}
+
|
+ {{else}} {{#if vote.failed}}
+ {{vote.result}}
+
|
+ {{else }}
+
+
+ {{vote.result}}
+
|
+ {{/if}}{{/if}}
+ {{#if vote.yea}}
+ {{vote.vote}} | + {{else}} {{#if vote.nay}} +{{vote.vote}} | + {{else}}{{vote.vote}} | + {{/if}}{{/if}} +
Your federal legislators for zip: {{zip}}
+{{legislator.title}} {{legislator.first_name}} {{legislator.middle_name}} {{legislator.last_name}} + Voting Record
+Phone: {{legislator.phone}}
+Email: {{legislator.oc_email}}
+Term Start: {{legislator.term_start}}
+Term End: {{legislator.term_end}}
+Twitter: @{{legislator.twitter_id}}
+Facebook: {{legislator.facebook_id}}
+ +
+