diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..3c3629e --- /dev/null +++ b/.gitignore @@ -0,0 +1 @@ +node_modules diff --git a/README.md b/README.md index cb007d2..0d9ea9e 100644 --- a/README.md +++ b/README.md @@ -1,2 +1,4 @@ # project_what_have_you_done Build an application to help track the legislative activities of your local representatives. + +/Eric Glover diff --git a/app.js b/app.js new file mode 100644 index 0000000..83721d1 --- /dev/null +++ b/app.js @@ -0,0 +1,66 @@ +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 request = require('request'); + +var index = require('./routes/index'); +var legislator = require('./routes/legislator'); +var district = require('./routes/district'); +var users = require('./routes/users'); + +var app = express(); + +var port = process.env.PORT || '3000'; + +// 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'))); + +//landing page +app.use('/', index); + +//district pages +app.use('/district', district); + +//legislator page +app.use('/legislator', legislator); +app.use('/users', users); + +// 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); +app.listen( port, function( err ){ + if ( err ){ + console.log( err ) + } + console.log( `Running on port:${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/bill.js b/models/bill.js new file mode 100644 index 0000000..9b868f3 --- /dev/null +++ b/models/bill.js @@ -0,0 +1,10 @@ +class Bill { + constructor( bill_id ){ + this.bill_id = bill_id; + } + say_hello(){ + console.log( "Hello!!!" ); + } +} + +module.exports = Bill; diff --git a/models/rep.js b/models/rep.js new file mode 100644 index 0000000..a4a98ff --- /dev/null +++ b/models/rep.js @@ -0,0 +1,40 @@ +//representative class + //take a giant block of data, make into a class we care about +const log = console.log + +//the api gives results in this format + //['results':{}, 'count': #, 'page':{}] + +class Representative { + constructor( json_obj ){ + this.bioguide_id = json_obj.bioguide_id; + try { + this.basic_info = { + first_name : json_obj.first_name, + middle_name : json_obj.middle_name, + last_name : json_obj.last_name, + party : json_obj.party, + //party : this.party( json_obj.party ), + chamber : json_obj.chamber + } + this.contact_info = { + website: json_obj.website, + phone: json_obj.phone + } + } catch (e) { + console.log( e ); + } + } + say_hello(){ + log( `Hello, ${this.basic_info.first_name} here, ready to party.`); + } + party( char ){ + if (char == 'r'){ + return "Republican" + }else if (char == 'd'){ + return "Democrat" + } + } +} + +module.exports = Representative; diff --git a/package.json b/package.json new file mode 100644 index 0000000..f9cf5cd --- /dev/null +++ b/package.json @@ -0,0 +1,18 @@ +{ + "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" + } +} diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css new file mode 100644 index 0000000..e6a836b --- /dev/null +++ b/public/stylesheets/style.css @@ -0,0 +1,92 @@ +body { + padding: 50px; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; + font-size: 14px; + background-color: #EEE; +} +#main-column{ + display: flex; + flex-flow: column nowrap; + width: 400px; + margin: auto; +} +a { + color: #00B7FF; +} +a:hover{ + color: yellow; +} +#top{ + display: flex; + flex-flow: column nowrap; + align-items: center; + margin: 10px; +} +.details { + font-size: 12px; +} +.highlight { + background-color: yellow; +} +form,#zip-code { + background-color: white; + width: 350px; + margin: auto; + display: flex; + flex-flow: column nowrap; + align-items: center; +} +form * { + margin: 10px; +} + +.title { + background-color: yellow; +} +#zip-form-wrapper{ + display: flex; + flex-flow: row nowrap; + border: 0; + margin: 0; + padding: 10px; +} +#contact_info{ + font-size: 16px; + padding: 8px; + border: 1px solid grey; + background-color: white; +} +#contact_info div{ + margin-bottom: 8px; +} + +#contact_info div * { + padding: 4px; + margin: 0; +} +#display{ + font-size: 24px; +} +.center { + margin: auto; + text-align: center; +} +.center-things { + margin: auto; + display: flex; + flex-flow: column nowrap; + align-items: center; +} +#recent-bills { + width: 70%; + margin-top: 40px; +} +.bill { + background-color: white; + margin-top: 30px; + border: 1px solid black; + text-align: left; + font-size: 20px; + width: 100%; + padding: 16px; +} diff --git a/routes/district.js b/routes/district.js new file mode 100644 index 0000000..bf7ddb5 --- /dev/null +++ b/routes/district.js @@ -0,0 +1,54 @@ +var express = require('express'); +var router = express.Router(); + +const request = require('request'); +const Rep = require('../models/rep'); + +//var zip = 53075; +var zip; +var rep_url = `https://congress.api.sunlightfoundation.com/legislators/locate?zip=${zip}` + + +var parse = function( j_string ){ + return JSON.parse( j_string ); +} + +//return a promise ( I swear I'll have the data later...I'm good for it) +//get the list of reps +var get_reps = function( ){ + var promise = new Promise( function( resolve, reject){ + var rep_url = `https://congress.api.sunlightfoundation.com/legislators/locate?zip=${zip}`; + request( rep_url, function( err, res, body){ + if ( err ){ + console.log( err ) + reject( err ); + } + var j_obj = parse( body ); + //the api gives results in this format + //['results':{}, 'count': #, 'page':{}] + var reps = []; + for( var i = 0; i < j_obj.count; i++){ + var my_rep = new Rep( j_obj.results[i] ); + reps.push( my_rep ); + } + resolve( reps ); + }) + }) + return promise; +} +router.get('/', function(req, res, next) { + //make a promise + zip = req.query.zip || 65201; + debugger; + var rep_promise = get_reps(); + Promise.all( [rep_promise] ).then( function( value ){ + debugger; + res.render('district', { zip: zip, reps: value[0] }); + }, function( error ){ + debugger; + console.log("error: " + error ); + }) + +}); + +module.exports = router diff --git a/routes/index.js b/routes/index.js new file mode 100644 index 0000000..200e30b --- /dev/null +++ b/routes/index.js @@ -0,0 +1,82 @@ +var express = require('express'); +var router = express.Router(); +const request = require('request'); +const Rep = require('../models/rep'); + +var bill_url; + +//Bills +//https://congress.api.sunlightfoundation.com/votes?order=voted_at&chamber=house +//https://congress.api.sunlightfoundation.com/votes?order=voted_at&chamber=senate +//https://congress.api.sunlightfoundation.com//bills?bill_id=hr2810-115 + + +//Votes +//NOTE: votes are supposed to be found here but the API is messed up +//https://congress.api.sunlightfoundation.com/votes/voter_id=G000546 + +var get_bills = function( bill_id ){ + //make a promise + var promise = new Promise( function( resolve, reject){ + var url = `https://congress.api.sunlightfoundation.com//bills?bill_id=${bill_id}` + request( url, function( err, res, body){ + if ( err ){ + reject( err ); + } + var result = parse( body ).results; + resolve( result ); + }) + }) +} +var parse = function( j_string ){ + return JSON.parse( j_string ); +} +var get_recent_bills = function(chamber){ + //make promise + var promise = new Promise( function( resolve, rejcet ){ + var url = `https://congress.api.sunlightfoundation.com/votes?order=voted_at&chamber=${chamber}` + request( url, function( err, res, body){ + if ( err ){ + reject( err ); + } + var results = parse( body ).results; + resolve( results ); + }) + }) + return promise; +} + +//return a promise ( I swear I'll have the data later...I'm good for it) +//get the list of reps +var get_reps = function( ){ + var promise = new Promise( function( resolve, reject){ + request( rep_url, function( err, res, body){ + if ( err ){ + console.log( err ) + reject( err ); + } + //body is a giant json formatted string + var j_obj = JSON.parse( body ); + //the api gives results in this format + //['results':{}, 'count': #, 'page':{}] + //debugger; + var reps = []; + for( var i = 0; i < j_obj.count; i++){ + var my_rep = new Rep( j_obj.results[i] ); + reps.push( my_rep ); + } + + //my_rep.say_hello(); + resolve( reps ); + }) + }) + return promise; +} + +/* GET home page. */ +router.get('/', function(req, res, next) { + debugger; + res.render('index'); +}); + +module.exports = router; diff --git a/routes/legislator.js b/routes/legislator.js new file mode 100644 index 0000000..ad362d0 --- /dev/null +++ b/routes/legislator.js @@ -0,0 +1,124 @@ +var express = require('express'); +var router = express.Router(); +const request = require('request'); + +const Rep = require('../models/rep'); +const Bill = require('../models/bill') + + + +var parse = function( j_string ){ + return JSON.parse( j_string ); +} + +//call api for more info on bill and return a promise +var get_bill = function( bill_id ){ + //make a promise + var promise = new Promise( function( resolve, reject){ + + var url = `https://congress.api.sunlightfoundation.com//bills?bill_id=${bill_id}` + request( url, function( err, res, body){ + debugger; + if ( err ){ + reject( err ); + }else if ( res.statusCode == 404 ){ + reject( 404 ); + throw( 404 ); + } + debugger; + //getting '

Not Found

' = body + var result = parse( body ).results; + resolve( result ); + }) + }) + return promise; +} + +//get all recent bill id +//from a chamber +//of type passage +var get_recent_bills = function(chamber){ + //make promise + var promise = new Promise( function( resolve, rejcet ){ + var url = `https://congress.api.sunlightfoundation.com/votes?order=voted_at&vote_type=passage&chamber=${chamber}` + request( url, function( err, res, body){ + if ( err ){ + reject( err ); + } + //get bill ids + // results = [ {}, {} ] + var results = parse( body ).results; + + /* The API'S throwing me 404's so I'm just going to list bill_id + var bill_ids = results.map( function( element){ + var bill_id = element.bill_id; + return bill_id; + }) + */ + var bill_ids = results; + resolve( bill_ids ); + }) + }) + return promise; +} +var get_rep = function( url ){ + var promise = new Promise( function( resolve, reject){ + request( url, function( err, res, body){ + if ( err ){ + console.log( err ) + reject( err ); + } + + var rep_obj = parse( body).results; + var rep = new Rep( rep_obj[0] ); + resolve( rep ); + }) + }) + return promise; +} + +//https://congress.api.sunlightfoundation.com/legislators?bioguide_id=G000585 +/* Legislator page. */ +router.get('/:bioguide_id', function(req, res, next) { + var rep_id = req.params.bioguide_id; + + //get rep + var url = `https://congress.api.sunlightfoundation.com/legislators?bioguide_id=${rep_id}`; + var rep = get_rep(url); + var bills; + var bill_ids; + rep.then(function(value){ + bill_ids = get_recent_bills( value.basic_info.chamber ); + + //get bills voted on in that chamber + bill_ids.then( function(bill_value ){ + //call api + /* + var bill_promises = value.map( function( element ){ + //debugger; + var bill_promise = get_bill( element ); + + return bill_promise; + }) + //for each id make a Bill + Promise.all( bill_promises ).then( function( value ) { + //create new bills + debugger; + bills = value.map( function( element ){ + var bill = new Bill( element ); + return bill; + }) + res.render('legislator', { bills: bills, rep: rep}); + }) + bills = value.map( function( element ){ + var bill = new Bill( element ); + return bill; + }) + */ + res.render('legislator', { bills: bill_value, rep: value}); + }) + + }) +}); + +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/views/district.hbs b/views/district.hbs new file mode 100644 index 0000000..9f52845 --- /dev/null +++ b/views/district.hbs @@ -0,0 +1,18 @@ + + District:{{zip}} + + +
+

What Have You Done

+

{{zip}}

+
+
+ {{#each reps as |rep| }} +
+

{{rep.basic_info.first_name}} {{rep.basic_info.middle_name}} {{rep.basic_info.last_name}}

+
+ {{/each}} +
+ +
+ 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 @@ +

{{message}}

+

{{error.status}}

+
{{error.stack}}
diff --git a/views/index.hbs b/views/index.hbs new file mode 100644 index 0000000..d4b1af7 --- /dev/null +++ b/views/index.hbs @@ -0,0 +1,17 @@ + + WHAT HAVE YOU DONE + + +
+

What Have You Done

+ +

Discover the voting records of your local representatives

+

Then pester their offices!

+ +
+ + + +
+
+ diff --git a/views/layout.hbs b/views/layout.hbs new file mode 100644 index 0000000..068eb6b --- /dev/null +++ b/views/layout.hbs @@ -0,0 +1,10 @@ + + + + {{title}} + + + + {{{body}}} + + diff --git a/views/legislator.hbs b/views/legislator.hbs new file mode 100644 index 0000000..66ae500 --- /dev/null +++ b/views/legislator.hbs @@ -0,0 +1,29 @@ + + District + + +
+
+ +

{{rep.basic_info.first_name}} {{rep.basic_info.middle_name}} {{rep.basic_info.last_name}}

+
+
+
+

Phone

+

{{rep.contact_info.phone}}

+
+
+

Website

+ {{rep.contact_info.website}} +
+
+
+

Recent Votes

+ {{#each bills as |bill| }} +
+

Bill: {{bill.bill_id}}

+
+ {{/each}} +
+
+