diff --git a/.gitignore b/.gitignore index e43b0f9..0d7e40d 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,4 @@ .DS_Store +/node_modules +npm-debug.log +db/* diff --git a/README.md b/README.md index e42eb13..db67cc5 100644 --- a/README.md +++ b/README.md @@ -1,5 +1,36 @@ -# Project: VideoStoreAPI - +# Carly and Elsa Project: VideoStoreAPI + +## Notes +### Endpoints +*DONE* GET "/customers" +// for routes below, will need to return results offset by page number +*DONE* GET "/customers/:name" // will be dynamic +*DONE* GET "/customers/:registered_at" +*DONE* GET "/customers/:postal_code" + +*DONE* GET "/customers/:id/current_movies" +*DONE* GET "/customers/:id/past_movies" // order by checkout date, include return date + +*DONE* GET "/movies" +// for routes below, will need to return results offset by page number +*DONE* GET "/movies/:title" // will be dynamic +*DONE* GET "/movies/:release_date" + +// for routes below, will need to pass an ordering parameter to order by customer id, name, or checkout date +*DONE* GET "/movies/:title/current_customers/:order" +*DONE* GET "/movies/:title/past_customers/:order" + +// normalize database or join everything??? +*DONE* GET "/movies/:title" + // includes synopsis, release date, inventory total, rentable boolean, list of customers that have it currently checkout out ...? + +*DONE* POST "/rentals/:customer_id/:movie_title" + // create association, checkout date, return date, charge account, returned boolean +*DONE* PATCH "/checkin/:customer_id/:movie_title" + // modify returned boolean +*DONE* GET "/rentals/overdue" + +--- The overall goal of this project is to create a system that a video store (remember those?) could use to track their inventory of rental videos and their collection of customers. We will use [NodeJS](https://nodejs.org/en/) to construct a RESTful API. The goal of this API is to quickly serve information about the store's video collection, customer information, and to update rental status. This repository provides two JSON datafiles to serve as the initial seeds for this system. @@ -18,7 +49,7 @@ We will use [NodeJS](https://nodejs.org/en/) to construct a RESTful API. The goa - `registered_at`: When the customer first visited the store - The customer's physical address, composed of: - `address` - - `city` + - `city` - `state` - `postal_code` - `phone`: Primary contact phone number @@ -91,4 +122,3 @@ The API you build should have the following capabilities. The schema of your dat - All endpoints must be tested. - We will use [Mocha](https://mochajs.org/) for tests. - There isn't a coverage requirement for this project, beyond demonstrating that every endpoint is covered by some manner of tests. - diff --git a/app.js b/app.js new file mode 100644 index 0000000..01c0916 --- /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 movies = require('./routes/movies'); +var movie_copies = require('./routes/movie_copies'); +var customers = require('./routes/customers'); +var rentals = require('./routes/rentals'); +var zomg = require('./routes/zomg'); + +var app = express(); + +// view engine setup +app.set('views', path.join(__dirname, 'views')); +app.set('view engine', 'jade'); + +// 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('/movies', movies); +app.use('/copies', movie_copies); +app.use('/customers', customers); +app.use('/rentals', rentals); +app.use('/zomg', zomg); + +// 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 handlers + +// development error handler +// will print stacktrace +if (app.get('env') === 'development') { + app.use(function(err, req, res, next) { + res.status(err.status || 500); + res.render('error', { + message: err.message, + error: err + }); + }); +} + +// production error handler +// no stacktraces leaked to user +app.use(function(err, req, res, next) { + res.status(err.status || 500); + res.render('error', { + message: err.message, + error: {} + }); +}); + + +module.exports = app; diff --git a/bin/www b/bin/www new file mode 100755 index 0000000..cc43085 --- /dev/null +++ b/bin/www @@ -0,0 +1,90 @@ +#!/usr/bin/env node + +/** + * Module dependencies. + */ + +var app = require('../app'); +var debug = require('debug')('C3Projects--VideoStoreAPI: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/controllers/customers.js b/controllers/customers.js new file mode 100644 index 0000000..3731391 --- /dev/null +++ b/controllers/customers.js @@ -0,0 +1,96 @@ +"use strict"; + +var sqlite3 = require('sqlite3').verbose(), + Customer = require('../models/customer'), + db_env = process.env.DB || 'development', + db; + +function addPercents(variable) { + var percented = "%" + variable + "%"; + return percented; +} + +exports.customersController = { + customers: function(req, res) { + var customer = new Customer(); + customer.all(function(error, result) { + return res.status(200).json(result); + }); + }, + + customers_current_movies: function(req, res) { + db = new sqlite3.Database('db/' + db_env + '.db'); + var id = req.params.id; + console.log("customer id " + id); + db.all("SELECT movies.title FROM rentals \ + INNER JOIN movie_copies ON rentals.movie_copy_id = movie_copies.id \ + INNER JOIN movies ON movie_copies.movie_id = movies.id \ + WHERE rentals.customer_id = ? AND rentals.return_status = 0", id, function(err, the_movies) { + if (err) { + console.log(err); + } + db.close(); + return res.status(200).json(the_movies); + }); + }, + + customers_past_movies: function(req, res) { + db = new sqlite3.Database('db/' + db_env + '.db'); + var id = req.params.id; + console.log("customer id " + id); + db.all("SELECT movies.title, rentals.return_date FROM rentals \ + INNER JOIN movie_copies ON rentals.movie_copy_id = movie_copies.id \ + INNER JOIN movies ON movie_copies.movie_id = movies.id \ + WHERE rentals.customer_id = ? AND rentals.return_status = 1 \ + ORDER BY rentals.return_date", id, function(err, the_movies) { + if (err) { + console.log(err); + } + db.close(); + return res.status(200).json(the_movies); + }); + }, + + customers_by_name: function(req, res) { + var name = req.params.name, + per_page = req.params.per_page, + page = req.params.pg; + page = page * per_page; + + name = addPercents(name); + + var customer = new Customer(); + customer.find_by_with_limit("name", name, per_page, page, function(error, result) { + return res.status(200).json(result); + }); + }, + + customers_by_register_date: function(req, res) { + var date = req.params.date, + per_page = req.params.per_page, + page = req.params.pg; + page = page * per_page; + + date = addPercents(date); + + var customer = new Customer(); + customer.find_by_with_limit("registered_at", date, per_page, page, function(error, result) { + return res.status(200).json(result); + }); + }, + + customers_by_postal_code: function(req, res) { + var zipcode = req.params.zipcode, + per_page = req.params.per_page, + page = req.params.pg; + page = page * per_page; + + zipcode = addPercents(zipcode); + + var customer = new Customer(); + customer.find_by_with_limit("postal_code", zipcode, per_page, page, function(error, result) { + return res.status(200).json(result); + }); + }, + +}; diff --git a/controllers/movie_copies.js b/controllers/movie_copies.js new file mode 100644 index 0000000..842a169 --- /dev/null +++ b/controllers/movie_copies.js @@ -0,0 +1,22 @@ +"use strict"; + +var sqlite3 = require('sqlite3').verbose(), + db_env = process.env.DB || 'development', + db; + +function addPercents(variable) { + var percented = "%" + variable + "%"; + return percented; + } + +exports.movie_copiesController = { + copies: function(req, res) { + db = new sqlite3.Database('db/' + db_env + '.db'); + db.all("SELECT * FROM movie_copies", function(err, all_copies) { + db.close(); + return res.status(200).json(all_copies); + }); + + } + +}; // end exports.moviesController diff --git a/controllers/movies.js b/controllers/movies.js new file mode 100644 index 0000000..c9d0e43 --- /dev/null +++ b/controllers/movies.js @@ -0,0 +1,99 @@ +"use strict"; + +var sqlite3 = require('sqlite3').verbose(), + Movie = require('../models/movie'), + db_env = process.env.DB || 'development', + db; + +function addPercents(variable) { + var percented = "%" + variable + "%"; + return percented; +} + +exports.moviesController = { + movies: function(req, res) { + var movie = new Movie(); + + movie.all(function(error, result) { + return res.status(200).json(result); + }); + }, + + movies_by_title: function(req, res) { + var title = req.params.title.toLowerCase(), + movie = new Movie(); + title = addPercents(title); + + movie.find_by("title", title, function(error, result) { + return res.status(200).json(result); + }); + }, + + current_renters_by_title: function(req, res) { + db = new sqlite3.Database('db/' + db_env + '.db'); + var title = req.params.title.toLowerCase(), + order = req.params.order.toLowerCase(); + title = addPercents(title); + + var order_by_statement = ""; + + if(order == "id") { + order_by_statement = "ORDER BY customers.id"; + } + else if(order == "name") { + order_by_statement = "ORDER BY customers.name"; + } + else if(order == "checkout") { + order_by_statement = "ORDER BY rentals.checkout_date"; + } + + db.all("SELECT customers.name FROM rentals \ + INNER JOIN movie_copies ON rentals.movie_copy_id = movie_copies.id \ + INNER JOIN movies ON movie_copies.movie_id = movies.id \ + INNER JOIN customers ON rentals.customer_id = customers.id \ + WHERE movies.title LIKE ? AND return_status = 0 " + order_by_statement + ";", + title, function(err, the_movie) { + db.close(); + return res.status(200).json(the_movie); + }); + }, + + past_renters_by_title: function(req, res) { + db = new sqlite3.Database('db/' + db_env + '.db'); + var title = req.params.title.toLowerCase(), + order = req.params.order.toLowerCase(); + title = addPercents(title); + + var order_by_statement = ""; + + if(order == "id") { + order_by_statement = "ORDER BY customers.id"; + } + else if(order == "name") { + order_by_statement = "ORDER BY customers.name"; + } + else if(order == "checkout") { + order_by_statement = "ORDER BY rentals.checkout_date"; + } + + db.all("SELECT customers.name FROM rentals \ + INNER JOIN movie_copies ON rentals.movie_copy_id = movie_copies.id \ + INNER JOIN movies ON movie_copies.movie_id = movies.id \ + INNER JOIN customers ON rentals.customer_id = customers.id \ + WHERE movies.title LIKE ? AND return_status = 1 " + order_by_statement + ";", + title, function(err, the_movie) { + db.close(); + return res.status(200).json(the_movie); + }); + }, + + movies_by_release: function(req, res) { + var date = req.params.release_date.toLowerCase(), + movie = new Movie(); + date = addPercents(date); + + movie.find_by("release_date", date, function(error, result) { + return res.status(200).json(result); + }); + } +}; // end diff --git a/controllers/rentals.js b/controllers/rentals.js new file mode 100644 index 0000000..735bb21 --- /dev/null +++ b/controllers/rentals.js @@ -0,0 +1,130 @@ +"use strict"; + +var sqlite3 = require('sqlite3').verbose(), + db_env = process.env.DB || 'development', + db; + +function addPercents(variable) { + var percented = "%" + variable + "%"; + return percented; + } + +function findCopy(movie_title, callback) { + db = new sqlite3.Database('db/' + db_env + '.db'); + + db.get("SELECT movie_copies.id FROM movie_copies \ + INNER JOIN movies ON movie_copies.movie_id = movies.id \ + WHERE movies.title LIKE ? AND movie_copies.is_available = 1;", + movie_title, function(err, result) { + if (err) { + db.close(); + return ("NO COPIES AVAILABLE ", err); + } + else { + db.close(); + callback(result); + } + }); +} + +function findRental(movie_title, customer, callback) { + db = new sqlite3.Database('db/' + db_env + '.db'); + db.get("SELECT movie_copies.id FROM rentals \ + INNER JOIN movie_copies ON movie_copies.id = rentals.movie_copy_id \ + INNER JOIN movies ON movies.id = movie_copies.movie_id \ + WHERE movies.title LIKE ? AND rentals.customer_id = ? AND rentals.return_status = 0;", + movie_title, customer, function(err, result) { + if (err) { + db.close(); + console.log("ERROR: ", err); + } + else { + db.close(); + callback(result); + } + }); +} + +exports.rentalsController = { + rentals: function(req, res) { + db = new sqlite3.Database('db/' + db_env + '.db'); + db.all("SELECT * FROM rentals", function(err, all_rentals) { + db.close(); + return res.status(200).json(all_rentals); + }); + }, + + overdue_rentals: function(req, res) { + var current_date = new Date(); // get current date + current_date = current_date.toISOString(); // change format to YYY-MM-DD and time + var formatted_current_date = current_date.split("T")[0]; // cut off time + + db = new sqlite3.Database('db/' + db_env + '.db'); + db.all("SELECT customers.name, movies.title, movie_copies.id FROM rentals \ + INNER JOIN movie_copies ON rentals.movie_copy_id = movie_copies.id \ + INNER JOIN movies ON movie_copies.movie_id = movies.id \ + INNER JOIN customers ON rentals.customer_id = customers.id \ + WHERE rentals.return_status = 0 AND rentals.return_date < ?;", formatted_current_date, function(err, all_rentals) { + db.close(); + return res.status(200).json(all_rentals); + }); + }, + + create_rental: function(req, res) { + var customer = req.params.customer_id, + movie = req.params.movie_title; + movie = addPercents(movie); + var checkout = new Date(); + var due = new Date(checkout); + due.setDate(checkout.getDate()+7); + checkout = checkout.toISOString().split("T")[0]; + due = due.toISOString().split("T")[0]; + + findCopy(movie, function(movie_copy) { + if(movie_copy === undefined) { + return res.status(204).send({status: 204, message: "NO COPIES AVAILABLE"}); + } + + db = new sqlite3.Database('db/' + db_env + '.db'); + db.serialize(function() { + db.run("INSERT INTO rentals(customer_id, movie_copy_id, checkout_date, return_date, return_status, cost) \ + VALUES(" + customer + ", " + movie_copy.id + ", '" + checkout + "', '" + due + "', 0, 5); \ + COMMIT;"); // end first db.run + db.run("UPDATE customers SET account_credit = account_credit - 5 WHERE id = ?;", customer); + db.run("UPDATE movie_copies SET is_available = 0 WHERE id = ?;", movie_copy.id); + db.get("SELECT * FROM rentals WHERE customer_id = ? ORDER BY checkout_date DESC;", customer, function(err, result) { + if (err) { + console.log("ERROR: ", err); + } + return res.status(200).json(result); + }); + }); + db.close(); + }); + }, + + return_rental: function(req, res) { + var customer = req.params.customer_id, + movie = req.params.movie_title; + movie = addPercents(movie); + findRental(movie, customer, function(rental_copy) { + if(rental_copy === undefined) { + return res.status(204).send({status: 204, message: "NO RENTAL FOUND"}); + } + + db = new sqlite3.Database('db/' + db_env + '.db'); + db.serialize(function() { + + db.run("UPDATE rentals SET return_status = 1 WHERE customer_id = " + customer + ";"); + db.run("UPDATE movie_copies SET is_available = 1 WHERE id = " + rental_copy.id + ";"); + db.get("SELECT * FROM rentals WHERE customer_id = ? ORDER BY checkout_date DESC", customer, function(err, result) { + if (err) { + console.log("ERROR: ", err); + } + return res.status(200).json(result); + }); + }); + db.close(); + }); + } +}; diff --git a/controllers/zomg.js b/controllers/zomg.js new file mode 100644 index 0000000..1d10be1 --- /dev/null +++ b/controllers/zomg.js @@ -0,0 +1,8 @@ +"use strict"; + +exports.zomgController = { + + zomg: function(req, res) { + return res.status(200).json({ zomg: "it worked!" }); + } +} diff --git a/database.js b/database.js new file mode 100644 index 0000000..be0d3bd --- /dev/null +++ b/database.js @@ -0,0 +1,81 @@ +"use strict"; + +var sqlite3 = require('sqlite3').verbose(), + db_env = process.env.DB || 'development'; + +module.exports = { + all: function(callback) { + var db = new sqlite3.Database('db/' + db_env + '.db'); + var statement = "SELECT * FROM " + this.table_name; + + db.all(statement, function(err, res) { + if (callback) callback(err, res); + db.close(); + }); + }, + + find_by: function(column, value, callback) { + var db = new sqlite3.Database('db/' + db_env + '.db'); + var statement = "SELECT * FROM " + this.table_name + " WHERE " + column + " LIKE ?"; + + db.all(statement, value, function(err, res) { + if (callback) callback(err, res); + db.close(); + }); + }, + + find_by_with_limit: function(column, value, per_page, page, callback) { + var db = new sqlite3.Database('db/' + db_env + '.db'); + var statement = "SELECT * FROM " + this.table_name + " WHERE " + column + " LIKE ? ORDER BY " + column + " LIMIT " + per_page + " OFFSET " + page; + + db.all(statement, value, function(err, res) { + if (callback) callback(err, res); + db.close(); + }); + }, + + + + // create: function(data, callback) { + // var db = new sqlite3.Database('db/' + db_env + '.db'); + // var keys = Object.keys(data); + // var key_pairs = []; + // var values = []; + + // for (var i = 0; i < keys.length; i++) { + // values.push(data[keys[i]]); + // key_pairs.push('?'); + // } + + // var statement = "INSERT INTO "+ this.table_name + " (" + keys.join(',') + ") " + + // "VALUES(" + key_pairs.join(',') + ")" + + // db.run(statement, values, function(err) { + // callback(err, { inserted_id: this.lastID, changed: this.changes }); + // db.close(); + // }); + // }, + + // save: function(data, callback) { + // if (data.id) { + // var db = new sqlite3.Database('db/' + db_env + '.db'); + // var keys = Object.keys(data); + // var key_pairs = []; + // var values = []; + + // for (var i = 0; i < keys.length; i++) { + // values.push(data[keys[i]]); + // key_pairs.push(keys[i] + "=? "); + // } + + // var statement = "UPDATE " + this.table_name + " SET " + key_pairs.join(',') + "WHERE id=" + data.id; + + // db.run(statement, values, function(err) { + // callback(err, { inserted_id: this.lastID, changed: this.changes }); + // db.close(); + // }); + // } else { + // callback({err: "Missing Key", message: "Can't save without an id; use `create`"}); + // } + // } +} diff --git a/formatted_customers.json b/formatted_customers.json new file mode 100644 index 0000000..f2e07d9 --- /dev/null +++ b/formatted_customers.json @@ -0,0 +1 @@ +[{"id":"1","name":"Shelley Rocha","registered_at":"2015-04-29","address":"Ap #292-5216 Ipsum Rd.","city":"Hillsboro","state":"OR","postal_code":"24309","phone":"(322) 510-8695","account_credit":13.15},{"id":"2","name":"Curran Stout","registered_at":"2014-04-17","address":"Ap #658-1540 Erat Rd.","city":"San Francisco","state":"California","postal_code":"94267","phone":"(908) 949-6758","account_credit":35.66},{"id":"3","name":"Roanna Robinson","registered_at":"2014-11-28","address":"Ap #561-4214 Eget St.","city":"Harrisburg","state":"PA","postal_code":"15867","phone":"(323) 336-1841","account_credit":50.39},{"id":"4","name":"Carolyn Chandler","registered_at":"2014-07-04","address":"133-8707 Arcu. Avenue","city":"Fort Wayne","state":"IN","postal_code":"73684","phone":"(234) 837-2886","account_credit":21.79},{"id":"5","name":"Aquila Riddle","registered_at":"2015-08-27","address":"Ap #187-9582 Primis St.","city":"Tacoma","state":"WA","postal_code":"73251","phone":"(925) 161-2223","account_credit":17.82},{"id":"6","name":"Phyllis Russell","registered_at":"2014-04-03","address":"746-8511 Ipsum Ave","city":"Boise","state":"Idaho","postal_code":"76759","phone":"(961) 964-5158","account_credit":88.67},{"id":"7","name":"Rajah Riggs","registered_at":"2014-01-29","address":"Ap #881-3920 Malesuada Avenue","city":"Norman","state":"OK","postal_code":"36134","phone":"(540) 515-2339","account_credit":30.14},{"id":"8","name":"Amanda Curtis","registered_at":"2014-11-18","address":"Ap #773-125 Nunc St.","city":"Iowa City","state":"Iowa","postal_code":"18538","phone":"(253) 271-5290","account_credit":47.22},{"id":"9","name":"Jacqueline Perry","registered_at":"2015-07-23","address":"Ap #288-7228 Dis Rd.","city":"Anchorage","state":"AK","postal_code":"99789","phone":"(479) 207-8414","account_credit":96.28},{"id":"10","name":"Quinlan Rich","registered_at":"2015-07-10","address":"Ap #727-9607 Nibh Avenue","city":"Hilo","state":"HI","postal_code":"63747","phone":"(521) 124-5753","account_credit":68.41},{"id":"11","name":"Ciara Summers","registered_at":"2015-07-10","address":"Ap #412-1462 Molestie St.","city":"Grand Rapids","state":"Michigan","postal_code":"44906","phone":"(473) 496-4835","account_credit":56.88},{"id":"12","name":"Alfreda Hines","registered_at":"2015-08-20","address":"P.O. Box 754, 627 Erat Avenue","city":"Anchorage","state":"Alaska","postal_code":"99915","phone":"(921) 910-1283","account_credit":55.59},{"id":"13","name":"Eugenia Roberson","registered_at":"2014-02-23","address":"Ap #781-1953 Suspendisse Road","city":"Jackson","state":"MS","postal_code":"67415","phone":"(900) 501-6947","account_credit":22.71},{"id":"14","name":"Ferris Robles","registered_at":"2015-03-16","address":"P.O. Box 344, 4911 Semper Rd.","city":"Independence","state":"Missouri","postal_code":"46428","phone":"(569) 834-1872","account_credit":4.91},{"id":"15","name":"Sopoline Fisher","registered_at":"2014-05-19","address":"543-8042 Porttitor Avenue","city":"Knoxville","state":"TN","postal_code":"23142","phone":"(603) 919-2974","account_credit":53.63},{"id":"16","name":"Vivien Justice","registered_at":"2014-09-22","address":"790-3681 Lobortis Rd.","city":"Sterling Heights","state":"MI","postal_code":"54505","phone":"(563) 349-0325","account_credit":83.76},{"id":"17","name":"Ginger Heath","registered_at":"2015-02-27","address":"Ap #395-9452 Quisque St.","city":"Billings","state":"Montana","postal_code":"25054","phone":"(572) 140-2058","account_credit":89.69},{"id":"18","name":"Kieran Calhoun","registered_at":"2014-08-24","address":"Ap #854-9111 Nunc, Road","city":"Savannah","state":"GA","postal_code":"47373","phone":"(909) 486-8575","account_credit":61.8},{"id":"19","name":"Winter Stephenson","registered_at":"2015-08-07","address":"P.O. Box 887, 4257 Lorem Rd.","city":"Salt Lake City","state":"Utah","postal_code":"63684","phone":"(466) 617-0803","account_credit":1.41},{"id":"20","name":"Mallory Weaver","registered_at":"2014-07-13","address":"7297 Tortor, Avenue","city":"Houston","state":"Texas","postal_code":"89807","phone":"(727) 342-1336","account_credit":65.24},{"id":"21","name":"Audra Vance","registered_at":"2014-11-01","address":"P.O. Box 906, 9067 A Street","city":"Columbus","state":"Ohio","postal_code":"43007","phone":"(832) 502-4114","account_credit":76.71},{"id":"22","name":"Aladdin Fowler","registered_at":"2015-05-25","address":"Ap #548-6390 Ornare Av.","city":"Aurora","state":"IL","postal_code":"92483","phone":"(201) 728-7318","account_credit":90.12},{"id":"23","name":"Dominique Battle","registered_at":"2014-01-25","address":"P.O. Box 753, 5236 At Rd.","city":"Gaithersburg","state":"MD","postal_code":"49893","phone":"(385) 326-1715","account_credit":88.51},{"id":"24","name":"Kimberly Savage","registered_at":"2014-09-20","address":"Ap #888-6281 Aliquam Av.","city":"Virginia Beach","state":"VA","postal_code":"50164","phone":"(285) 195-0154","account_credit":41.86},{"id":"25","name":"Branden Craig","registered_at":"2014-09-23","address":"768-8145 Elit Road","city":"Hattiesburg","state":"MS","postal_code":"99128","phone":"(972) 938-4626","account_credit":92.64},{"id":"26","name":"Hammett Beach","registered_at":"2014-08-23","address":"206-6384 Morbi Road","city":"Butte","state":"MT","postal_code":"54656","phone":"(833) 249-0504","account_credit":60.65},{"id":"27","name":"Dai Meadows","registered_at":"2014-08-17","address":"1083 Enim, St.","city":"Sioux City","state":"IA","postal_code":"63549","phone":"(204) 993-4985","account_credit":87.9},{"id":"28","name":"Grady Chang","registered_at":"2015-05-18","address":"8819 Nam St.","city":"Sacramento","state":"CA","postal_code":"90337","phone":"(306) 153-3636","account_credit":75.94},{"id":"29","name":"Cody Woodard","registered_at":"2014-06-24","address":"P.O. Box 990, 1927 Quis Ave","city":"Gaithersburg","state":"Maryland","postal_code":"68459","phone":"(606) 363-0837","account_credit":97.75},{"id":"30","name":"Ulysses Whitfield","registered_at":"2014-11-21","address":"906-7966 Adipiscing Street","city":"Laramie","state":"Wyoming","postal_code":"95684","phone":"(371) 627-1105","account_credit":93.18},{"id":"31","name":"Olympia Dyer","registered_at":"2014-06-25","address":"152-525 Odio St.","city":"Sandy","state":"Utah","postal_code":"25061","phone":"(124) 172-2031","account_credit":55.17},{"id":"32","name":"Kristen Snyder","registered_at":"2015-02-14","address":"P.O. Box 610, 7930 Vivamus Av.","city":"West Jordan","state":"UT","postal_code":"50965","phone":"(977) 906-9656","account_credit":61.89},{"id":"33","name":"Mira Stokes","registered_at":"2014-04-01","address":"Ap #630-1483 Luctus Rd.","city":"Columbia","state":"MD","postal_code":"66923","phone":"(473) 271-3425","account_credit":95.92},{"id":"34","name":"Ulla Skinner","registered_at":"2014-07-21","address":"318-7550 Placerat St.","city":"Colorado Springs","state":"Colorado","postal_code":"91540","phone":"(718) 770-3057","account_credit":41.64},{"id":"35","name":"Amelia Brock","registered_at":"2015-05-21","address":"499-4567 Metus Av.","city":"Jackson","state":"Mississippi","postal_code":"19969","phone":"(313) 638-8556","account_credit":29.1},{"id":"36","name":"Dexter Frank","registered_at":"2015-08-02","address":"P.O. Box 413, 407 Senectus St.","city":"Wyoming","state":"Wyoming","postal_code":"71026","phone":"(753) 112-2298","account_credit":83.79},{"id":"37","name":"Yoshi Willis","registered_at":"2014-03-02","address":"9648 Magna Rd.","city":"Sioux City","state":"Iowa","postal_code":"57062","phone":"(592) 873-3593","account_credit":56.74},{"id":"38","name":"Marah Ballard","registered_at":"2015-01-24","address":"6452 Integer Av.","city":"Juneau","state":"AK","postal_code":"99933","phone":"(482) 351-7984","account_credit":68.42},{"id":"39","name":"Eugenia Cherry","registered_at":"2015-07-15","address":"P.O. Box 749, 4159 Aliquam Rd.","city":"Stamford","state":"Connecticut","postal_code":"29863","phone":"(318) 627-4305","account_credit":75.71},{"id":"40","name":"Porter Mclean","registered_at":"2014-01-27","address":"Ap #450-3061 Turpis St.","city":"New Orleans","state":"Louisiana","postal_code":"70087","phone":"(764) 362-7391","account_credit":44.94},{"id":"41","name":"Philip Patton","registered_at":"2014-06-24","address":"P.O. Box 210, 857 Non, Street","city":"Aurora","state":"Illinois","postal_code":"88138","phone":"(188) 787-8862","account_credit":17.53},{"id":"42","name":"Charlotte Turner","registered_at":"2014-05-27","address":"8543 Cubilia Avenue","city":"Bridgeport","state":"CT","postal_code":"26116","phone":"(629) 711-4885","account_credit":73.15},{"id":"43","name":"Camden Aguirre","registered_at":"2014-10-17","address":"P.O. Box 100, 8703 Libero Ave","city":"Nampa","state":"ID","postal_code":"13381","phone":"(277) 267-0388","account_credit":76.4},{"id":"44","name":"Samuel Santana","registered_at":"2015-03-22","address":"1605 Quis St.","city":"Bellevue","state":"Washington","postal_code":"67560","phone":"(678) 717-4843","account_credit":71.72},{"id":"45","name":"Sheila Olsen","registered_at":"2015-02-19","address":"2058 Tristique Av.","city":"Eugene","state":"Oregon","postal_code":"63846","phone":"(574) 608-9527","account_credit":49.09},{"id":"46","name":"Acton Gilliam","registered_at":"2015-02-27","address":"Ap #508-8214 Senectus Av.","city":"Portland","state":"Oregon","postal_code":"62594","phone":"(903) 973-1984","account_credit":48.64},{"id":"47","name":"Carla Fox","registered_at":"2015-01-17","address":"4092 Duis St.","city":"Biloxi","state":"MS","postal_code":"67389","phone":"(438) 573-5618","account_credit":25.7},{"id":"48","name":"Ursa Harrell","registered_at":"2014-11-22","address":"8070 Nec Rd.","city":"Henderson","state":"NV","postal_code":"87045","phone":"(951) 243-9155","account_credit":41.1},{"id":"49","name":"Stephanie Foley","registered_at":"2014-05-20","address":"6806 Volutpat. Street","city":"Sioux City","state":"IA","postal_code":"48520","phone":"(562) 674-5565","account_credit":2.94},{"id":"50","name":"Isabelle Riley","registered_at":"2014-07-25","address":"365-2351 Metus. St.","city":"San Antonio","state":"Texas","postal_code":"42972","phone":"(592) 610-6185","account_credit":38.76},{"id":"51","name":"Olivia Guy","registered_at":"2014-05-14","address":"8409 Libero Rd.","city":"San Francisco","state":"California","postal_code":"90775","phone":"(373) 139-6846","account_credit":44.63},{"id":"52","name":"Kenneth Rowland","registered_at":"2014-06-14","address":"Ap #862-3617 Cursus Avenue","city":"Chattanooga","state":"TN","postal_code":"54509","phone":"(650) 912-9706","account_credit":88.74},{"id":"53","name":"Zorita Buchanan","registered_at":"2014-07-13","address":"3413 Lectus Avenue","city":"Richmond","state":"Virginia","postal_code":"49611","phone":"(109) 544-5498","account_credit":73.2},{"id":"54","name":"Galvin Daniels","registered_at":"2014-01-08","address":"506-7584 Adipiscing Street","city":"Birmingham","state":"Alabama","postal_code":"36049","phone":"(863) 203-7464","account_credit":99.37},{"id":"55","name":"Hamilton Hunter","registered_at":"2014-06-17","address":"Ap #353-2344 Sodales Street","city":"Honolulu","state":"Hawaii","postal_code":"18980","phone":"(531) 196-0181","account_credit":47.93},{"id":"56","name":"Dieter Frye","registered_at":"2014-09-01","address":"P.O. Box 572, 4852 Nec, Rd.","city":"Los Angeles","state":"California","postal_code":"93004","phone":"(843) 603-1249","account_credit":0.19},{"id":"57","name":"Anne Mclean","registered_at":"2014-10-05","address":"Ap #642-6158 Convallis St.","city":"Lawton","state":"OK","postal_code":"34188","phone":"(703) 764-9176","account_credit":52.81},{"id":"58","name":"Gretchen Gray","registered_at":"2014-07-25","address":"4557 Arcu Ave","city":"Virginia Beach","state":"VA","postal_code":"15836","phone":"(629) 782-2033","account_credit":29.02},{"id":"59","name":"Theodore Ingram","registered_at":"2014-04-13","address":"P.O. Box 362, 9274 Nibh. St.","city":"Tucson","state":"AZ","postal_code":"86869","phone":"(227) 651-0229","account_credit":55.08},{"id":"60","name":"Otto Leblanc","registered_at":"2015-04-12","address":"P.O. Box 210, 6616 Ipsum Ave","city":"Detroit","state":"Michigan","postal_code":"27552","phone":"(974) 976-6329","account_credit":38.44},{"id":"61","name":"Pearl Blackwell","registered_at":"2015-05-05","address":"797-3332 Nam St.","city":"Sterling Heights","state":"Michigan","postal_code":"51429","phone":"(960) 110-2636","account_credit":64.18},{"id":"62","name":"Caesar Stanley","registered_at":"2015-07-31","address":"Ap #295-1886 Lorem St.","city":"Saint Paul","state":"MN","postal_code":"10029","phone":"(841) 157-1769","account_credit":49.14},{"id":"63","name":"Rebekah Conrad","registered_at":"2015-07-01","address":"112-656 Duis Ave","city":"Davenport","state":"IA","postal_code":"76937","phone":"(691) 802-4154","account_credit":67.99},{"id":"64","name":"Robert Harmon","registered_at":"2015-04-09","address":"333-2529 Duis Road","city":"Kaneohe","state":"Hawaii","postal_code":"58444","phone":"(165) 718-1361","account_credit":27.08},{"id":"65","name":"Maris Puckett","registered_at":"2014-05-23","address":"Ap #422-3446 A Road","city":"Gaithersburg","state":"MD","postal_code":"39939","phone":"(952) 204-5010","account_credit":96.49},{"id":"66","name":"Lara Ellison","registered_at":"2014-09-30","address":"660-756 Arcu. St.","city":"Cedar Rapids","state":"Iowa","postal_code":"96377","phone":"(928) 139-4812","account_credit":60.97},{"id":"67","name":"Dai Baldwin","registered_at":"2015-04-03","address":"5574 Varius Ave","city":"South Bend","state":"Indiana","postal_code":"27284","phone":"(642) 404-1198","account_credit":6.51},{"id":"68","name":"Mira Lyons","registered_at":"2014-02-18","address":"P.O. Box 176, 9193 Enim Road","city":"Topeka","state":"KS","postal_code":"30606","phone":"(355) 309-3105","account_credit":66.25},{"id":"69","name":"Laurel Diaz","registered_at":"2014-02-27","address":"501 Eu St.","city":"Fayetteville","state":"Arkansas","postal_code":"72463","phone":"(382) 883-9819","account_credit":50.13},{"id":"70","name":"Breanna Kim","registered_at":"2015-02-06","address":"752-6391 Quis Ave","city":"Little Rock","state":"AR","postal_code":"71072","phone":"(182) 564-6516","account_credit":58.31},{"id":"71","name":"Hayes Levine","registered_at":"2015-05-14","address":"646-9160 Ultrices St.","city":"Grand Island","state":"NE","postal_code":"89643","phone":"(624) 302-2419","account_credit":77.3},{"id":"72","name":"Taylor Spence","registered_at":"2014-07-09","address":"7109 In St.","city":"Portland","state":"Oregon","postal_code":"91240","phone":"(993) 724-6663","account_credit":24.11},{"id":"73","name":"Camille Walton","registered_at":"2015-03-19","address":"718-6981 Libero Road","city":"Boise","state":"Idaho","postal_code":"72346","phone":"(332) 249-7842","account_credit":21.88},{"id":"74","name":"Yardley Mckenzie","registered_at":"2015-06-22","address":"8911 Justo Rd.","city":"Huntsville","state":"AL","postal_code":"36334","phone":"(731) 311-1108","account_credit":95.41},{"id":"75","name":"Rachel Munoz","registered_at":"2014-05-04","address":"8466 Aliquam Ave","city":"Madison","state":"Wisconsin","postal_code":"56718","phone":"(651) 904-9661","account_credit":80.08},{"id":"76","name":"Bevis Jimenez","registered_at":"2014-05-11","address":"Ap #222-2891 Magnis St.","city":"Columbus","state":"Georgia","postal_code":"35209","phone":"(526) 934-4237","account_credit":25.63},{"id":"77","name":"Malik Barber","registered_at":"2015-02-14","address":"Ap #364-1684 Quam. St.","city":"Waterbury","state":"Connecticut","postal_code":"75696","phone":"(575) 915-7794","account_credit":92.24},{"id":"78","name":"Nell Bishop","registered_at":"2015-05-02","address":"7623 Torquent Street","city":"Minneapolis","state":"MN","postal_code":"27078","phone":"(819) 107-0728","account_credit":9.35},{"id":"79","name":"Leigh Anderson","registered_at":"2014-07-22","address":"Ap #149-1154 Cras Rd.","city":"Montgomery","state":"Alabama","postal_code":"36037","phone":"(176) 966-1214","account_credit":86.77},{"id":"80","name":"Chava Kirby","registered_at":"2014-06-25","address":"570-6835 Ac Ave","city":"Allentown","state":"PA","postal_code":"84384","phone":"(795) 896-0405","account_credit":5.45},{"id":"81","name":"Inez Gamble","registered_at":"2015-02-10","address":"471-8542 Et Ave","city":"Virginia Beach","state":"VA","postal_code":"23365","phone":"(288) 595-2741","account_credit":32.92},{"id":"82","name":"Jacob Villarreal","registered_at":"2015-03-16","address":"3480 Amet, Street","city":"Grand Island","state":"NE","postal_code":"53804","phone":"(676) 686-4808","account_credit":76.99},{"id":"83","name":"Velma Mcfadden","registered_at":"2014-08-14","address":"164-557 At St.","city":"Juneau","state":"AK","postal_code":"99806","phone":"(520) 661-1866","account_credit":4.31},{"id":"84","name":"Berk Carroll","registered_at":"2014-06-09","address":"1640 Blandit. Rd.","city":"Frankfort","state":"Kentucky","postal_code":"88958","phone":"(937) 237-7054","account_credit":55.15},{"id":"85","name":"Serina Collins","registered_at":"2015-08-12","address":"6117 Lorem, Avenue","city":"South Bend","state":"Indiana","postal_code":"39108","phone":"(216) 221-5456","account_credit":55.52},{"id":"86","name":"Larissa Soto","registered_at":"2015-03-04","address":"597-5077 Purus St.","city":"Tampa","state":"Florida","postal_code":"19281","phone":"(693) 833-8618","account_credit":98.77},{"id":"87","name":"Erin Mckenzie","registered_at":"2015-04-17","address":"P.O. Box 822, 6254 Etiam Rd.","city":"Madison","state":"WI","postal_code":"94031","phone":"(646) 241-7827","account_credit":78.13},{"id":"88","name":"Katell Lewis","registered_at":"2014-06-06","address":"P.O. Box 794, 8686 Libero St.","city":"San Antonio","state":"TX","postal_code":"90724","phone":"(307) 332-5251","account_credit":1.13},{"id":"89","name":"Yeo Humphrey","registered_at":"2014-07-14","address":"121 Porta Ave","city":"Bear","state":"DE","postal_code":"61381","phone":"(770) 120-6205","account_credit":42.14},{"id":"90","name":"Brynne Stuart","registered_at":"2015-06-16","address":"778-6903 Urna Rd.","city":"Jonesboro","state":"AR","postal_code":"72921","phone":"(949) 800-1135","account_credit":84.26},{"id":"91","name":"Brynne Fuentes","registered_at":"2015-05-19","address":"8106 Habitant Av.","city":"Minneapolis","state":"Minnesota","postal_code":"23965","phone":"(535) 656-2981","account_credit":76.08},{"id":"92","name":"Giacomo Strong","registered_at":"2014-12-13","address":"1121 Vivamus Av.","city":"Madison","state":"Wisconsin","postal_code":"18820","phone":"(938) 428-0683","account_credit":62.64},{"id":"93","name":"Ruth Nelson","registered_at":"2014-12-07","address":"P.O. Box 324, 7958 Rutrum Avenue","city":"Lewiston","state":"Maine","postal_code":"35597","phone":"(821) 465-3951","account_credit":22.11},{"id":"94","name":"Alexander Brennan","registered_at":"2015-02-20","address":"P.O. Box 778, 9617 Sapien, Avenue","city":"San Antonio","state":"Texas","postal_code":"74395","phone":"(452) 676-2428","account_credit":94.6},{"id":"95","name":"Eagan Lynch","registered_at":"2014-04-08","address":"Ap #209-5829 Gravida St.","city":"Dallas","state":"Texas","postal_code":"45352","phone":"(874) 234-9834","account_credit":4.26},{"id":"96","name":"Charity Buckley","registered_at":"2015-07-13","address":"Ap #822-4239 Aliquam Rd.","city":"San Antonio","state":"Texas","postal_code":"48690","phone":"(580) 120-8164","account_credit":5.49},{"id":"97","name":"Aspen Le","registered_at":"2014-05-20","address":"Ap #373-9486 A Ave","city":"Essex","state":"Vermont","postal_code":"53433","phone":"(179) 428-6526","account_credit":27.07},{"id":"98","name":"Jana Foster","registered_at":"2014-05-31","address":"521-938 Odio. Rd.","city":"Clarksville","state":"TN","postal_code":"41791","phone":"(828) 240-0643","account_credit":88.61},{"id":"99","name":"Basia Contreras","registered_at":"2014-10-11","address":"P.O. Box 325, 3668 Donec St.","city":"Independence","state":"Missouri","postal_code":"44890","phone":"(847) 205-9126","account_credit":74.62},{"id":"100","name":"Barbara Jacobson","registered_at":"2014-11-03","address":"489-4471 Commodo Avenue","city":"Duluth","state":"Minnesota","postal_code":"59354","phone":"(823) 257-9965","account_credit":12.41},{"id":"101","name":"Ria Little","registered_at":"2015-04-02","address":"P.O. Box 423, 7795 Aenean St.","city":"Athens","state":"GA","postal_code":"88306","phone":"(496) 600-9071","account_credit":0.84},{"id":"102","name":"Jael Hoover","registered_at":"2015-04-12","address":"P.O. Box 817, 1753 Ac Rd.","city":"Seattle","state":"Washington","postal_code":"17749","phone":"(953) 751-2907","account_credit":95.32},{"id":"103","name":"Howard Meyer","registered_at":"2014-12-15","address":"2813 Pede. Rd.","city":"Colchester","state":"Vermont","postal_code":"11479","phone":"(735) 816-9513","account_credit":43.79},{"id":"104","name":"Brian Yates","registered_at":"2014-03-10","address":"2961 Integer Ave","city":"Kearney","state":"NE","postal_code":"49867","phone":"(292) 138-3176","account_credit":9.51},{"id":"105","name":"Amber Nicholson","registered_at":"2014-02-13","address":"1666 Urna Ave","city":"Knoxville","state":"Tennessee","postal_code":"57653","phone":"(154) 850-1394","account_credit":84.42},{"id":"106","name":"Silas Barton","registered_at":"2015-02-06","address":"Ap #441-4537 Vitae Road","city":"Gulfport","state":"MS","postal_code":"72241","phone":"(817) 405-3562","account_credit":5.98},{"id":"107","name":"Justina Weber","registered_at":"2015-07-26","address":"9004 Dui Rd.","city":"Cleveland","state":"OH","postal_code":"35970","phone":"(653) 921-2923","account_credit":35.95},{"id":"108","name":"Raven Cote","registered_at":"2015-04-29","address":"P.O. Box 944, 5312 Donec Street","city":"Savannah","state":"GA","postal_code":"56882","phone":"(212) 164-3612","account_credit":54.27},{"id":"109","name":"Ahmed Massey","registered_at":"2014-09-13","address":"675-7569 Neque. Rd.","city":"Metairie","state":"Louisiana","postal_code":"74601","phone":"(798) 424-7871","account_credit":31.59},{"id":"110","name":"Thomas Mcbride","registered_at":"2014-12-28","address":"Ap #911-5536 Proin Road","city":"Little Rock","state":"AR","postal_code":"71114","phone":"(557) 571-0355","account_credit":62.9},{"id":"111","name":"April Humphrey","registered_at":"2014-02-04","address":"P.O. Box 153, 5975 Molestie Ave","city":"Olathe","state":"Kansas","postal_code":"84858","phone":"(795) 813-1088","account_credit":18.44},{"id":"112","name":"Sierra Rosa","registered_at":"2014-05-15","address":"Ap #753-7173 Purus St.","city":"Knoxville","state":"Tennessee","postal_code":"86369","phone":"(433) 397-7483","account_credit":12.75},{"id":"113","name":"Sara Gay","registered_at":"2015-02-27","address":"6311 Felis, Rd.","city":"Knoxville","state":"Tennessee","postal_code":"60949","phone":"(239) 940-6734","account_credit":44.11},{"id":"114","name":"Alexis Ward","registered_at":"2015-04-28","address":"888 Feugiat Rd.","city":"Miami","state":"Florida","postal_code":"65771","phone":"(189) 124-7774","account_credit":84.48},{"id":"115","name":"Justina Dixon","registered_at":"2015-07-17","address":"P.O. Box 279, 5229 Mattis Street","city":"Green Bay","state":"Wisconsin","postal_code":"28463","phone":"(488) 172-0802","account_credit":48.41},{"id":"116","name":"Scott Wise","registered_at":"2014-12-20","address":"P.O. Box 237, 9683 Elit, Road","city":"Dover","state":"DE","postal_code":"80375","phone":"(744) 266-9954","account_credit":11.75},{"id":"117","name":"Cameran Terry","registered_at":"2015-02-20","address":"133-7353 Eu Avenue","city":"Rutland","state":"VT","postal_code":"13964","phone":"(232) 667-7186","account_credit":74.33},{"id":"118","name":"Cooper Suarez","registered_at":"2014-11-10","address":"P.O. Box 632, 4595 Nec Rd.","city":"Montpelier","state":"Vermont","postal_code":"21186","phone":"(792) 641-6482","account_credit":20.07},{"id":"119","name":"Karleigh Ingram","registered_at":"2014-09-22","address":"3480 Nulla Road","city":"San Diego","state":"CA","postal_code":"92064","phone":"(819) 878-4232","account_credit":7.66},{"id":"120","name":"Fiona Santana","registered_at":"2014-12-12","address":"P.O. Box 950, 8105 Gravida. Ave","city":"Independence","state":"MO","postal_code":"64612","phone":"(364) 302-8535","account_credit":47.27},{"id":"121","name":"Cheryl Shelton","registered_at":"2014-07-07","address":"145-5466 Nec Rd.","city":"Anchorage","state":"AK","postal_code":"99971","phone":"(666) 421-3975","account_credit":27.65},{"id":"122","name":"Raya Burgess","registered_at":"2015-05-01","address":"9695 Lacus. Avenue","city":"Owensboro","state":"KY","postal_code":"72801","phone":"(118) 782-5871","account_credit":31.05},{"id":"123","name":"Hakeem Stokes","registered_at":"2015-07-31","address":"975-799 Sit Street","city":"Helena","state":"MT","postal_code":"67768","phone":"(285) 662-1132","account_credit":60.43},{"id":"124","name":"Macon Crosby","registered_at":"2014-04-27","address":"4714 Aliquet. Road","city":"Hartford","state":"Connecticut","postal_code":"56565","phone":"(347) 348-8116","account_credit":66.21},{"id":"125","name":"Brittany Harris","registered_at":"2014-03-19","address":"183-9407 A Road","city":"Bozeman","state":"MT","postal_code":"56384","phone":"(489) 746-9013","account_credit":8.05},{"id":"126","name":"Galena Ford","registered_at":"2015-09-04","address":"8088 Eget Road","city":"Gary","state":"IN","postal_code":"25479","phone":"(538) 724-6020","account_credit":82.15},{"id":"127","name":"Nehru Smith","registered_at":"2015-01-04","address":"317 Arcu. Avenue","city":"Austin","state":"TX","postal_code":"83794","phone":"(127) 566-2347","account_credit":60.36},{"id":"128","name":"Craig Higgins","registered_at":"2015-07-13","address":"Ap #592-2711 Non, Ave","city":"Cambridge","state":"Massachusetts","postal_code":"50503","phone":"(123) 194-5906","account_credit":46.3},{"id":"129","name":"Ria Goodwin","registered_at":"2015-01-29","address":"860-5993 Vivamus Road","city":"Augusta","state":"Maine","postal_code":"25217","phone":"(251) 172-1155","account_credit":88},{"id":"130","name":"Griffith Key","registered_at":"2014-02-10","address":"152-5468 Ac Road","city":"Nampa","state":"Idaho","postal_code":"51195","phone":"(624) 463-6111","account_credit":71.11},{"id":"131","name":"Kasper Fischer","registered_at":"2015-05-14","address":"6153 Sed Road","city":"Independence","state":"Missouri","postal_code":"91560","phone":"(319) 149-4379","account_credit":40.96},{"id":"132","name":"Stuart Goodwin","registered_at":"2015-09-01","address":"P.O. Box 689, 2792 Quis, St.","city":"Augusta","state":"Georgia","postal_code":"71303","phone":"(538) 387-3287","account_credit":88.19},{"id":"133","name":"Dawn Carlson","registered_at":"2015-04-18","address":"5366 Id Street","city":"Aurora","state":"Illinois","postal_code":"79365","phone":"(994) 905-9769","account_credit":78.41},{"id":"134","name":"Natalie Schroeder","registered_at":"2015-06-24","address":"392-2462 Luctus Road","city":"Boston","state":"Massachusetts","postal_code":"99200","phone":"(891) 649-2871","account_credit":95.95},{"id":"135","name":"Justine Goodwin","registered_at":"2014-10-21","address":"Ap #406-6268 Morbi Av.","city":"Columbus","state":"Ohio","postal_code":"85028","phone":"(155) 830-0119","account_credit":71.42},{"id":"136","name":"Wilma Velez","registered_at":"2014-04-06","address":"4400 Aliquam Rd.","city":"Virginia Beach","state":"Virginia","postal_code":"36713","phone":"(279) 153-9870","account_credit":7.6},{"id":"137","name":"Chiquita Burks","registered_at":"2014-02-19","address":"Ap #979-8936 Egestas. St.","city":"Dover","state":"DE","postal_code":"48328","phone":"(264) 440-3911","account_credit":84.17},{"id":"138","name":"Christian Mclaughlin","registered_at":"2015-04-19","address":"765 Tristique Avenue","city":"Des Moines","state":"Iowa","postal_code":"35850","phone":"(962) 941-9645","account_credit":8.08},{"id":"139","name":"Aurelia Giles","registered_at":"2014-12-08","address":"P.O. Box 745, 1594 Ante. Rd.","city":"Wyoming","state":"WY","postal_code":"51587","phone":"(724) 380-8095","account_credit":62.69},{"id":"140","name":"Josephine Browning","registered_at":"2014-12-12","address":"459-9737 Donec Rd.","city":"Davenport","state":"Iowa","postal_code":"21255","phone":"(436) 817-2557","account_credit":21.97},{"id":"141","name":"Kasper Morton","registered_at":"2014-10-22","address":"3938 Cursus. Street","city":"Philadelphia","state":"Pennsylvania","postal_code":"56267","phone":"(191) 710-5107","account_credit":65.15},{"id":"142","name":"Renee Higgins","registered_at":"2014-06-08","address":"P.O. Box 523, 9963 Arcu. Road","city":"Newport News","state":"VA","postal_code":"25802","phone":"(933) 431-7021","account_credit":67.97},{"id":"143","name":"Brody Coleman","registered_at":"2014-02-22","address":"Ap #269-5956 Proin Rd.","city":"Rutland","state":"Vermont","postal_code":"72539","phone":"(482) 790-5904","account_credit":68.91},{"id":"144","name":"Jennifer Greer","registered_at":"2015-07-12","address":"923-7286 Dui. Rd.","city":"Virginia Beach","state":"Virginia","postal_code":"97476","phone":"(547) 641-6594","account_credit":71.19},{"id":"145","name":"Gannon Abbott","registered_at":"2014-09-24","address":"Ap #754-9349 Nec Road","city":"Rochester","state":"Minnesota","postal_code":"22689","phone":"(363) 394-9351","account_credit":27.97},{"id":"146","name":"Adrian Nguyen","registered_at":"2014-04-28","address":"956-1243 Libero Av.","city":"Fort Smith","state":"Arkansas","postal_code":"71182","phone":"(109) 817-4149","account_credit":92.54},{"id":"147","name":"Carter Morris","registered_at":"2014-09-09","address":"P.O. Box 304, 5040 Malesuada Rd.","city":"Kaneohe","state":"Hawaii","postal_code":"26814","phone":"(450) 765-0057","account_credit":81.11},{"id":"148","name":"Basia Ratliff","registered_at":"2014-01-02","address":"Ap #907-6837 Diam Ave","city":"Knoxville","state":"Tennessee","postal_code":"80562","phone":"(734) 180-1477","account_credit":67.26},{"id":"149","name":"Genevieve Nieves","registered_at":"2014-05-13","address":"Ap #864-1195 Sit Street","city":"Wichita","state":"KS","postal_code":"17313","phone":"(660) 218-7246","account_credit":38.71},{"id":"150","name":"Axel Morton","registered_at":"2014-05-02","address":"459-6656 Ultricies Av.","city":"West Jordan","state":"Utah","postal_code":"99969","phone":"(929) 552-4291","account_credit":48.18},{"id":"151","name":"Montana Wynn","registered_at":"2014-08-03","address":"Ap #236-806 Sem, Av.","city":"Springfield","state":"Missouri","postal_code":"41345","phone":"(697) 916-8606","account_credit":33.85},{"id":"152","name":"Allistair Bradley","registered_at":"2015-05-22","address":"P.O. Box 384, 7086 At Avenue","city":"Juneau","state":"Alaska","postal_code":"99972","phone":"(627) 610-6902","account_credit":68.34},{"id":"153","name":"Jacob Foley","registered_at":"2014-03-18","address":"785-7522 Duis St.","city":"Little Rock","state":"AR","postal_code":"72550","phone":"(310) 834-5542","account_credit":52.03},{"id":"154","name":"Noelani Burton","registered_at":"2014-03-15","address":"428-8345 Enim. St.","city":"Burlington","state":"VT","postal_code":"83655","phone":"(461) 724-9721","account_credit":81.67},{"id":"155","name":"Abigail Lara","registered_at":"2015-08-12","address":"P.O. Box 388, 1190 Donec St.","city":"Shreveport","state":"Louisiana","postal_code":"41243","phone":"(235) 178-3417","account_credit":88.56},{"id":"156","name":"Driscoll Shepard","registered_at":"2014-04-12","address":"Ap #375-2818 Ac St.","city":"Minneapolis","state":"MN","postal_code":"30682","phone":"(433) 449-9825","account_credit":13.79},{"id":"157","name":"Elaine Carney","registered_at":"2014-05-22","address":"P.O. Box 495, 1408 Laoreet, Rd.","city":"Miami","state":"Florida","postal_code":"78257","phone":"(213) 954-9127","account_credit":82.97},{"id":"158","name":"Jonas Galloway","registered_at":"2015-01-25","address":"Ap #190-5298 Ornare, Rd.","city":"Bloomington","state":"MN","postal_code":"86280","phone":"(113) 365-9867","account_credit":56.1},{"id":"159","name":"Abel Oneil","registered_at":"2014-04-19","address":"P.O. Box 129, 2669 Arcu. Avenue","city":"Colorado Springs","state":"CO","postal_code":"52301","phone":"(857) 528-9424","account_credit":89.69},{"id":"160","name":"Maia Brock","registered_at":"2015-06-13","address":"Ap #354-4024 Cubilia Road","city":"Essex","state":"Vermont","postal_code":"67149","phone":"(392) 863-4448","account_credit":74.13},{"id":"161","name":"Alden Cabrera","registered_at":"2014-02-18","address":"P.O. Box 904, 6349 Tortor Ave","city":"Rutland","state":"Vermont","postal_code":"96595","phone":"(535) 175-9355","account_credit":31.45},{"id":"162","name":"Kerry Steele","registered_at":"2014-07-25","address":"441-2182 Turpis Av.","city":"Anchorage","state":"AK","postal_code":"99922","phone":"(178) 897-7367","account_credit":63.9},{"id":"163","name":"Charissa Browning","registered_at":"2015-03-11","address":"873-2320 Et, Street","city":"Jacksonville","state":"Florida","postal_code":"14335","phone":"(840) 128-0317","account_credit":5.28},{"id":"164","name":"Darius Mendez","registered_at":"2014-07-26","address":"P.O. Box 505, 730 Nonummy Road","city":"Casper","state":"WY","postal_code":"51369","phone":"(335) 359-1497","account_credit":63.72},{"id":"165","name":"Megan Gates","registered_at":"2015-03-09","address":"955-5227 Nunc Av.","city":"Reading","state":"PA","postal_code":"81062","phone":"(443) 918-1564","account_credit":2.86},{"id":"166","name":"Paloma Horne","registered_at":"2014-04-21","address":"7419 Quis, St.","city":"North Las Vegas","state":"NV","postal_code":"43059","phone":"(293) 295-3992","account_credit":55.75},{"id":"167","name":"Alec Schneider","registered_at":"2014-09-05","address":"958 Arcu. Street","city":"Newport News","state":"Virginia","postal_code":"90428","phone":"(378) 745-6478","account_credit":94.93},{"id":"168","name":"Keegan Porter","registered_at":"2014-07-15","address":"2383 Fusce St.","city":"Omaha","state":"Nebraska","postal_code":"36496","phone":"(235) 797-5965","account_credit":83.54},{"id":"169","name":"Dorian Brooks","registered_at":"2015-01-08","address":"863 Nisi Av.","city":"Cedar Rapids","state":"Iowa","postal_code":"21997","phone":"(543) 949-5105","account_credit":85.74},{"id":"170","name":"Xyla Oneal","registered_at":"2015-06-17","address":"625-1521 Adipiscing Avenue","city":"Tuscaloosa","state":"Alabama","postal_code":"35503","phone":"(586) 384-9718","account_credit":92.1},{"id":"171","name":"Urielle Brock","registered_at":"2015-05-11","address":"P.O. Box 248, 1197 Adipiscing St.","city":"Newport News","state":"VA","postal_code":"96073","phone":"(374) 344-9156","account_credit":97.75},{"id":"172","name":"Hadley Snow","registered_at":"2014-02-04","address":"Ap #795-8212 Erat. Av.","city":"Fort Collins","state":"Colorado","postal_code":"41558","phone":"(290) 857-4604","account_credit":35.67},{"id":"173","name":"Jenna Conley","registered_at":"2015-06-24","address":"9806 In Rd.","city":"South Bend","state":"Indiana","postal_code":"27537","phone":"(965) 898-4616","account_credit":90.28},{"id":"174","name":"Hermione Combs","registered_at":"2014-02-13","address":"Ap #571-9079 Lobortis Street","city":"Cambridge","state":"MA","postal_code":"77897","phone":"(711) 537-2715","account_credit":14.77},{"id":"175","name":"Scarlet Bridges","registered_at":"2014-10-07","address":"P.O. Box 684, 1366 Cras St.","city":"Atlanta","state":"GA","postal_code":"85419","phone":"(303) 449-3873","account_credit":4.05},{"id":"176","name":"Christopher Santana","registered_at":"2014-03-28","address":"Ap #401-3610 Porta St.","city":"Bridgeport","state":"Connecticut","postal_code":"31517","phone":"(916) 183-3805","account_credit":71.54},{"id":"177","name":"Linus Benjamin","registered_at":"2015-01-24","address":"Ap #480-9547 Morbi Rd.","city":"Olathe","state":"Kansas","postal_code":"85407","phone":"(970) 802-1283","account_credit":62.99},{"id":"178","name":"Abbot Mcclain","registered_at":"2015-02-25","address":"P.O. Box 159, 5873 Auctor Rd.","city":"Carson City","state":"NV","postal_code":"64780","phone":"(808) 310-5874","account_credit":13.81},{"id":"179","name":"Griffin Silva","registered_at":"2014-10-14","address":"6307 Nulla Avenue","city":"Juneau","state":"AK","postal_code":"99838","phone":"(679) 747-9281","account_credit":47.03},{"id":"180","name":"Chloe Shepard","registered_at":"2014-08-04","address":"P.O. Box 198, 4549 Pellentesque Rd.","city":"Kapolei","state":"HI","postal_code":"41472","phone":"(130) 324-9334","account_credit":24.78},{"id":"181","name":"Ori Russo","registered_at":"2015-03-11","address":"Ap #795-182 Commodo Av.","city":"Lewiston","state":"ME","postal_code":"88250","phone":"(309) 634-1120","account_credit":46.52},{"id":"182","name":"Shad Campbell","registered_at":"2014-10-20","address":"8296 Eu, Road","city":"Chesapeake","state":"VA","postal_code":"59563","phone":"(109) 812-1478","account_credit":34.45},{"id":"183","name":"Nomlanga Pugh","registered_at":"2014-10-11","address":"Ap #948-7387 Euismod Street","city":"Fort Worth","state":"TX","postal_code":"54834","phone":"(603) 124-9087","account_credit":59.54},{"id":"184","name":"Damian Nixon","registered_at":"2014-09-11","address":"386-6885 Velit. Rd.","city":"Minneapolis","state":"MN","postal_code":"34740","phone":"(996) 763-0457","account_credit":53.6},{"id":"185","name":"Azalia Bennett","registered_at":"2014-01-25","address":"498-9892 Vehicula Ave","city":"Portland","state":"ME","postal_code":"12829","phone":"(233) 218-0724","account_credit":94.45},{"id":"186","name":"Tyler Sweet","registered_at":"2014-03-14","address":"Ap #558-5634 Semper Rd.","city":"Paradise","state":"Nevada","postal_code":"10272","phone":"(401) 227-4119","account_credit":58.12},{"id":"187","name":"Kyla Sears","registered_at":"2014-11-01","address":"P.O. Box 522, 2866 Lectus St.","city":"Casper","state":"Wyoming","postal_code":"77130","phone":"(926) 988-6271","account_credit":40.39},{"id":"188","name":"Kiona Wagner","registered_at":"2014-07-02","address":"173 Tristique Road","city":"Aurora","state":"Colorado","postal_code":"20338","phone":"(976) 299-0296","account_credit":95.61},{"id":"189","name":"Celeste Hernandez","registered_at":"2014-06-13","address":"4405 Cursus Av.","city":"Ketchikan","state":"AK","postal_code":"99982","phone":"(637) 226-8697","account_credit":87.37},{"id":"190","name":"Stuart Stevenson","registered_at":"2015-06-28","address":"P.O. Box 244, 3411 Urna Road","city":"Paradise","state":"Nevada","postal_code":"54968","phone":"(884) 752-9559","account_credit":99.54},{"id":"191","name":"Yael Potter","registered_at":"2014-12-25","address":"P.O. Box 802, 4280 Tellus. Av.","city":"Augusta","state":"GA","postal_code":"94978","phone":"(838) 450-7880","account_credit":89.08},{"id":"192","name":"Sarah Price","registered_at":"2014-11-20","address":"P.O. Box 695, 4792 Sagittis. Street","city":"Jefferson City","state":"Missouri","postal_code":"68837","phone":"(694) 232-5802","account_credit":65.06},{"id":"193","name":"Keane Schroeder","registered_at":"2015-03-27","address":"3750 Ultrices. St.","city":"Wichita","state":"KS","postal_code":"32895","phone":"(771) 777-3480","account_credit":55.08},{"id":"194","name":"Gloria Cabrera","registered_at":"2014-02-17","address":"Ap #918-2687 Arcu Avenue","city":"Reading","state":"PA","postal_code":"20080","phone":"(872) 752-3660","account_credit":37.86},{"id":"195","name":"Audra Beck","registered_at":"2015-01-24","address":"311-6634 Et St.","city":"Bellevue","state":"WA","postal_code":"81533","phone":"(473) 949-2594","account_credit":30.33},{"id":"196","name":"Dolan Newton","registered_at":"2014-01-15","address":"P.O. Box 588, 3911 Proin Road","city":"Auburn","state":"ME","postal_code":"19968","phone":"(214) 790-1643","account_credit":58.03},{"id":"197","name":"Jolie Ashley","registered_at":"2014-05-25","address":"901-461 Pharetra St.","city":"Jefferson City","state":"MO","postal_code":"76963","phone":"(157) 795-0499","account_credit":50.3},{"id":"198","name":"Judith Ewing","registered_at":"2014-04-06","address":"P.O. Box 666, 7743 Mauris Road","city":"Knoxville","state":"TN","postal_code":"47671","phone":"(200) 706-8284","account_credit":13.91},{"id":"199","name":"Scarlett Rojas","registered_at":"2015-02-06","address":"P.O. Box 161, 9318 Non, Ave","city":"Boise","state":"Idaho","postal_code":"81837","phone":"(669) 244-0896","account_credit":27.69},{"id":"200","name":"Kane Johnston","registered_at":"2015-05-22","address":"P.O. Box 940, 1583 At, Street","city":"Austin","state":"Texas","postal_code":"94282","phone":"(270) 219-2853","account_credit":62.7}] \ No newline at end of file diff --git a/models/customer.js b/models/customer.js new file mode 100644 index 0000000..d1b1dd4 --- /dev/null +++ b/models/customer.js @@ -0,0 +1,9 @@ +"use strict"; + +function Customer() { + this.table_name = "customers"; +} + +Customer.prototype = require('../database'); + +module.exports = Customer diff --git a/models/movie.js b/models/movie.js new file mode 100644 index 0000000..83043d5 --- /dev/null +++ b/models/movie.js @@ -0,0 +1,9 @@ +"use strict"; + +function Movie() { + this.table_name = "movies"; +} + +Movie.prototype = require('../database'); + +module.exports = Movie diff --git a/package.json b/package.json new file mode 100644 index 0000000..88bd5cf --- /dev/null +++ b/package.json @@ -0,0 +1,25 @@ +{ + "name": "C3Projects--VideoStoreAPI", + "version": "0.0.0", + "private": true, + "scripts": { + "start": "nodemon ./bin/www", + "test": "clear; DB=test mocha --recursive", + "db:schema": "node ./utils/schema", + "db:seed": "node ./utils/seeds", + "db:reset": "npm run db:schema; npm run db:seed", + "db:scrub": "node ./utils/json_scrubber" + }, + "dependencies": { + "body-parser": "~1.13.2", + "cookie-parser": "~1.3.5", + "debug": "~2.2.0", + "express": "^4.13.3", + "jade": "~1.11.0", + "mocha": "^2.3.3", + "morgan": "~1.6.1", + "serve-favicon": "~2.3.0", + "sqlite3": "^3.1.0", + "supertest": "^1.1.0" + } +} diff --git a/public/stylesheets/style.css b/public/stylesheets/style.css new file mode 100644 index 0000000..9453385 --- /dev/null +++ b/public/stylesheets/style.css @@ -0,0 +1,8 @@ +body { + padding: 50px; + font: 14px "Lucida Grande", Helvetica, Arial, sans-serif; +} + +a { + color: #00B7FF; +} diff --git a/routes/customers.js b/routes/customers.js new file mode 100644 index 0000000..10825e5 --- /dev/null +++ b/routes/customers.js @@ -0,0 +1,30 @@ +var express = require('express'); +var router = express.Router(); +var customer_exports = require('../controllers/customers'); + +// GET CUSTOMER ROUTES +router.get('/', function(req, res, next) { + return customer_exports.customersController.customers(req, res); +}); + +router.get('/:id/current_movies', function(req, res, next) { + return customer_exports.customersController.customers_current_movies(req, res); +}); + +router.get('/:id/past_movies', function(req, res, next) { + return customer_exports.customersController.customers_past_movies(req, res); +}); + +router.get('/names/:name/per_page:per_page/pg:pg', function(req, res, next) { + return customer_exports.customersController.customers_by_name(req, res); +}); + +router.get('/date/:date/per_page:per_page/pg:pg', function(req, res, next) { + return customer_exports.customersController.customers_by_register_date(req, res); +}); + +router.get('/zipcode/:zipcode/per_page:per_page/pg:pg', function(req, res, next) { + return customer_exports.customersController.customers_by_postal_code(req, res); +}); + +module.exports = router; diff --git a/routes/movie_copies.js b/routes/movie_copies.js new file mode 100644 index 0000000..a60fb0a --- /dev/null +++ b/routes/movie_copies.js @@ -0,0 +1,11 @@ +var express = require('express'); +var router = express.Router(); +var movie_copies_exports = require('../controllers/movie_copies'); + +/* GET MOVIE ROUTES */ +router.get('/', function(req, res, next) { + // res.render('movies', { title: 'Express' }); + return movie_copies_exports.movie_copiesController.copies(req, res); +}); + +module.exports = router; diff --git a/routes/movies.js b/routes/movies.js new file mode 100644 index 0000000..24e549a --- /dev/null +++ b/routes/movies.js @@ -0,0 +1,26 @@ +var express = require('express'); +var router = express.Router(); +var movies_exports = require('../controllers/movies'); + +/* GET MOVIE ROUTES */ +router.get('/', function(req, res, next) { + return movies_exports.moviesController.movies(req, res); +}); + +router.get('/:title', function(req, res, next) { + return movies_exports.moviesController.movies_by_title(req, res); +}); + +router.get('/:title/current_renters/:order', function(req, res, next) { + return movies_exports.moviesController.current_renters_by_title(req, res); +}); + +router.get('/:title/past_renters/:order', function(req, res, next) { + return movies_exports.moviesController.past_renters_by_title(req, res); +}); + +router.get('/release/:release_date', function(req, res, next) { + return movies_exports.moviesController.movies_by_release(req, res); +}); + +module.exports = router; diff --git a/routes/rentals.js b/routes/rentals.js new file mode 100644 index 0000000..c8dc419 --- /dev/null +++ b/routes/rentals.js @@ -0,0 +1,22 @@ +var express = require('express'); +var router = express.Router(); +var rentals_exports = require('../controllers/rentals'); + +/* GET RENTAL ROUTES */ +router.get('/', function(req, res, next) { + return rentals_exports.rentalsController.rentals(req, res); +}); + +router.get('/overdue', function(req, res, next) { + return rentals_exports.rentalsController.overdue_rentals(req, res); +}); + +router.post('/:customer_id/:movie_title', function(req, res, next) { + return rentals_exports.rentalsController.create_rental(req, res); +}); + +router.patch('/return/:customer_id/:movie_title', function(req, res, next) { + return rentals_exports.rentalsController.return_rental(req, res); +}); + +module.exports = router; diff --git a/routes/zomg.js b/routes/zomg.js new file mode 100644 index 0000000..6687c0c --- /dev/null +++ b/routes/zomg.js @@ -0,0 +1,11 @@ +var express = require('express'); +var router = express.Router(); +var zomg_exports = require('../controllers/zomg'); + +/* GET */ +router.get('/', function(req, res, next) { + // res.send('respond with a resource'); + return zomg_exports.zomgController.zomg(req, res); +}); + +module.exports = router; diff --git a/test/controllers/customers.js b/test/controllers/customers.js new file mode 100644 index 0000000..aacc578 --- /dev/null +++ b/test/controllers/customers.js @@ -0,0 +1,194 @@ +var request = require('supertest'), +assert = require('assert'), +app = require('../../app'), +sqlite3 = require('sqlite3').verbose(), +agent = request.agent(app); + +describe("customers controller", function() { + + beforeEach(function(done) { + db_cleaner = new sqlite3.Database('db/test.db'); + db_cleaner.serialize(function() { + db_cleaner.exec( + "BEGIN; \ + DELETE FROM customers; \ + INSERT INTO customers(name, registered_at, address, city, state, postal_code, phone, account_credit) \ + VALUES('Shelley Rocha', '2015-09-21', '123 Nope St', 'Seattle', 'WA', '98104', '(000) 000-000', '100'), \ + ('Billy Rocha', '2015-09-21', '123 Nope St', 'Seattle', 'WA', '98104', '(000) 000-000', '100'); \ + COMMIT;", function(err) { + // db_cleaner.close(); + // done(); + } + ); + + db_cleaner.exec( + "BEGIN; \ + DELETE FROM movies; \ + INSERT INTO movies(title, overview, release_date, inventory) \ + VALUES('Jaws', 'Shark!', '2015-09-22', 1), \ + ('Maws', 'Worm!', '2015-09-22', 1); \ + COMMIT;", function(err) { + // db_cleaner.close(); + // done(); + } + ); + + db_cleaner.exec( + "BEGIN; \ + DELETE FROM movie_copies; \ + INSERT INTO movie_copies(movie_id, is_available) \ + VALUES(1, 0), \ + (2, 0); \ + COMMIT;", function(err) { + // db_cleaner.close(); + // done(); + } + ); + + db_cleaner.exec( + "BEGIN; \ + DELETE FROM rentals; \ + INSERT INTO rentals(customer_id, movie_copy_id, checkout_date, return_date, return_status, cost) \ + VALUES(1, 1, '2015-09-22', '2015-10-01', 0, 5), \ + (1, 2, '2015-09-12', '2015-09-21', 1, 5), \ + (2, 2, '2015-09-22', '2015-10-01', 0, 5), \ + (2, 1, '2015-09-12', '2015-09-21', 1, 5); \ + COMMIT;", function(err) { + db_cleaner.close(); + done(); + } + ); + + }); // end serialize + + }); + +describe("GET '/'", function() { + it("knows about the route", function(done) { + agent.get('/customers').set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200, function(err, res) { + assert.equal(err, undefined); + // console.log(err); + done(); + }); + }); + + it("returns an array of customer objects", function(done) { + agent.get('/customers').set('Accept', 'application/json') + .expect(200, function(err, result) { + assert.equal(result.body.length, 2); + + var keys = ['id', 'name', 'registered_at', 'address', 'city', 'state', 'postal_code', 'phone', 'account_credit']; + assert.deepEqual(Object.keys(result.body[0]), keys); + done(); + }); + }); // returns array objects + + it("knows about the route", function(done) { + agent.get('/customers/names/Shelley/per_page1/pg0').set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200, function(err, res) { + assert.equal(err, undefined); + // console.log(err); + done(); + }); + }); + + it("returns the customer object", function(done) { + agent.get('/customers/names/Shelley/per_page1/pg0').set('Accept', 'application/json') + .expect(200, function(err, result) { + assert.equal(result.body.length, 1); + + var keys = ['id', 'name', 'registered_at', 'address', 'city', 'state', 'postal_code', 'phone', 'account_credit']; + assert.deepEqual(Object.keys(result.body[0]), keys); + done(); + }); + }); // returns array objects + + it("knows about the route", function(done) { + agent.get('/customers/date/2015/per_page1/pg0').set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200, function(err, res) { + assert.equal(err, undefined); + // console.log(err); + done(); + }); + }); + + it("returns the customer object", function(done) { + agent.get('/customers/date/2015/per_page1/pg0').set('Accept', 'application/json') + .expect(200, function(err, result) { + assert.equal(result.body.length, 1); + + var keys = ['id', 'name', 'registered_at', 'address', 'city', 'state', 'postal_code', 'phone', 'account_credit']; + assert.deepEqual(Object.keys(result.body[0]), keys); + done(); + }); + }); // returns array objects + + it("knows about the route", function(done) { + agent.get('/customers/zipcode/98104/per_page1/pg0').set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200, function(err, res) { + assert.equal(err, undefined); + // console.log(err); + done(); + }); + }); + + it("returns the customer object", function(done) { + agent.get('/customers/zipcode/98104/per_page1/pg0').set('Accept', 'application/json') + .expect(200, function(err, result) { + assert.equal(result.body.length, 1); + + var keys = ['id', 'name', 'registered_at', 'address', 'city', 'state', 'postal_code', 'phone', 'account_credit']; + assert.deepEqual(Object.keys(result.body[0]), keys); + done(); + }); + }); // returns array objects + + it("knows about the route", function(done) { + agent.get('/customers/1/current_movies').set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200, function(err, res) { + assert.equal(err, undefined); + // console.log(err); + done(); + }); + }); + + it("returns the customer object", function(done) { + agent.get('/customers/1/current_movies').set('Accept', 'application/json') + .expect(200, function(err, result) { + assert.equal(result.body.length, 1); + + var keys = ['title']; + assert.deepEqual(Object.keys(result.body[0]), keys); + done(); + }); + }); // returns array objects + + it("knows about the route", function(done) { + agent.get('/customers/1/past_movies').set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200, function(err, res) { + assert.equal(err, undefined); + // console.log(err); + done(); + }); + }); + + it("returns the customer object", function(done) { + agent.get('/customers/1/past_movies').set('Accept', 'application/json') + .expect(200, function(err, result) { + assert.equal(result.body.length, 1); + + var keys = ['title', 'return_date']; + assert.deepEqual(Object.keys(result.body[0]), keys); + done(); + }); + }); // returns array objects + + }); // describe get +}); // describe customers diff --git a/test/controllers/movies.js b/test/controllers/movies.js new file mode 100644 index 0000000..d80397e --- /dev/null +++ b/test/controllers/movies.js @@ -0,0 +1,173 @@ +var request = require('supertest'), +assert = require('assert'), +app = require('../../app'), +sqlite3 = require('sqlite3').verbose(), +agent = request.agent(app); + +describe("movies controller", function() { + + beforeEach(function(done) { + db_cleaner = new sqlite3.Database('db/test.db'); + db_cleaner.serialize(function() { + db_cleaner.exec( + "BEGIN; \ + DELETE FROM movies; \ + INSERT INTO movies(title, overview, release_date, inventory) \ + VALUES('Jaws', 'Shark!', '2015-09-22', 10), \ + ('Maws', 'Worm!', '2015-09-22', 11); \ + COMMIT;", function(err) { + + } + ); + + db_cleaner.exec( + "BEGIN; \ + DELETE FROM customers; \ + INSERT INTO customers(name, registered_at, address, city, state, postal_code, phone, account_credit) \ + VALUES('Shelley Rocha', '2015-09-21', '123 Nope St', 'Seattle', 'WA', '98104', '(000) 000-000', '100'), \ + ('Billy Rocha', '2015-09-21', '123 Nope St', 'Seattle', 'WA', '98104', '(000) 000-000', '100'); \ + COMMIT;", function(err) { + + } + ); + + db_cleaner.exec( + "BEGIN; \ + DELETE FROM movie_copies; \ + INSERT INTO movie_copies(movie_id, is_available) \ + VALUES(1, 0), \ + (1, 1), \ + (2, 0), \ + (2, 0); \ + COMMIT;", function(err) { + + } + ); + + db_cleaner.exec( + "BEGIN; \ + DELETE FROM rentals; \ + INSERT INTO rentals(customer_id, movie_copy_id, checkout_date, return_date, return_status, cost) \ + VALUES(1, 1, '2015-09-22', '2015-10-01', 0, 5), \ + (1, 2, '2015-09-12', '2015-09-21', 1, 5), \ + (2, 1, '2015-09-22', '2015-10-01', 0, 5), \ + (2, 3, '2015-09-22', '2015-10-01', 0, 5), \ + (2, 1, '2015-09-12', '2015-09-21', 1, 5); \ + COMMIT;", function(err) { + db_cleaner.close(); + done(); + } + ); + }); // end serialize + }); + +describe("GET '/'", function() { + it("knows about the route", function(done) { + agent.get('/movies').set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200, function(err, res) { + assert.equal(err, undefined); + // console.log(err); + done(); + }); + }); + + it("returns an array of movie objects", function(done) { + agent.get('/movies').set('Accept', 'application/json') + .expect(200, function(err, result) { + assert.equal(result.body.length, 2); + + var keys = ['id', 'title', 'overview', 'release_date', 'inventory']; + assert.deepEqual(Object.keys(result.body[0]), keys); + done(); + }); + }); // returns array objects + + it("knows about the route", function(done) { + agent.get('/movies/Jaws').set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200, function(err, res) { + assert.equal(err, undefined); + // console.log(err); + done(); + }); + }); + + it("returns the movie object", function(done) { + agent.get('/movies/Jaws').set('Accept', 'application/json') + .expect(200, function(err, result) { + assert.equal(result.body.length, 1); + + var keys = ['id', 'title', 'overview', 'release_date', 'inventory']; + assert.deepEqual(Object.keys(result.body[0]), keys); + done(); + }); + }); // returns array objects + + it("knows about the route", function(done) { + agent.get('/movies/Jaws/current_renters/id').set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200, function(err, res) { + assert.equal(err, undefined); + // console.log(err); + done(); + }); + }); + + it("returns the movie object", function(done) { + agent.get('/movies/Jaws/current_renters/id').set('Accept', 'application/json') + .expect(200, function(err, result) { + assert.equal(result.body.length, 2); + + var keys = ['name']; + assert.deepEqual(Object.keys(result.body[0]), keys); + done(); + }); + }); // returns array objects + + it("knows about the route", function(done) { + agent.get('/movies/Jaws/past_renters/id').set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200, function(err, res) { + assert.equal(err, undefined); + // console.log(err); + done(); + }); + }); + + it("returns the movie object", function(done) { + agent.get('/movies/Jaws/past_renters/id').set('Accept', 'application/json') + .expect(200, function(err, result) { + assert.equal(result.body.length, 2); + + var keys = ['name']; + assert.deepEqual(Object.keys(result.body[0]), keys); + done(); + }); + }); // returns array objects + + + it("knows about the route", function(done) { + agent.get('/movies/release/2015').set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200, function(err, res) { + assert.equal(err, undefined); + // console.log(err); + done(); + }); + }); + + it("returns the movie object", function(done) { + agent.get('/movies/release/2015').set('Accept', 'application/json') + .expect(200, function(err, result) { + assert.equal(result.body.length, 2); + + var keys = ['id', 'title', 'overview', 'release_date', 'inventory']; + assert.deepEqual(Object.keys(result.body[0]), keys); + done(); + }); + }); // returns array objects + + + }); // describe get +}); // describe movies diff --git a/test/controllers/rentals.js b/test/controllers/rentals.js new file mode 100644 index 0000000..40fdcfd --- /dev/null +++ b/test/controllers/rentals.js @@ -0,0 +1,131 @@ +var request = require('supertest'), +assert = require('assert'), +app = require('../../app'), +sqlite3 = require('sqlite3').verbose(), +agent = request.agent(app); + +describe("customers controller", function() { + + beforeEach(function(done) { + db_cleaner = new sqlite3.Database('db/test.db'); + db_cleaner.serialize(function() { + db_cleaner.exec( + "BEGIN; \ + DELETE FROM customers; \ + INSERT INTO customers(name, registered_at, address, city, state, postal_code, phone, account_credit) \ + VALUES('Shelley Rocha', '2015-09-21', '123 Nope St', 'Seattle', 'WA', '98104', '(000) 000-000', '100'), \ + ('Billy Rocha', '2015-09-21', '123 Nope St', 'Seattle', 'WA', '98104', '(000) 000-000', '100'); \ + COMMIT;", function(err) { + + } + ); + + db_cleaner.exec( + "BEGIN; \ + DELETE FROM movies; \ + INSERT INTO movies(title, overview, release_date, inventory) \ + VALUES('Jaws', 'Shark!', '2015-09-22', 1), \ + ('Maws', 'Worm!', '2015-09-22', 1); \ + COMMIT;", function(err) { + + } + ); + + db_cleaner.exec( + "BEGIN; \ + DELETE FROM movie_copies; \ + INSERT INTO movie_copies(movie_id, is_available) \ + VALUES(1, 0), \ + (2, 1); \ + COMMIT;", function(err) { + + } + ); + + db_cleaner.exec( + "BEGIN; \ + DELETE FROM rentals; \ + INSERT INTO rentals(customer_id, movie_copy_id, checkout_date, return_date, return_status, cost) \ + VALUES(1, 1, '2014-09-22', '2014-10-01', 0, 5), \ + (2, 1, '2015-09-12', '2015-09-21', 1, 5); \ + COMMIT;", function(err) { + db_cleaner.close(); + done(); + } + ); + + }); // end serialize + }); // end beforeEach function + +describe("GET '/'", function() { + it("knows about the route", function(done) { + agent.get('/rentals/overdue').set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200, function(err, res) { + assert.equal(err, undefined); + + done(); + }); + }); + + it("returns an array of movie objects", function(done) { + agent.get('/rentals/overdue').set('Accept', 'application/json') + .expect(200, function(err, result) { + assert.equal(result.body.length, 1); + + var keys = ['name', 'title', 'id']; + assert.deepEqual(Object.keys(result.body[0]), keys); + + done(); + }); + }); // returns array objects + }); // end describe 'get' block + +describe("POST '/'", function() { + it("knows about the route", function(done) { + agent.post('/rentals/1/Maws').set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200, function(err, res) { + assert.equal(err, undefined); + + done(); + }); + }); + + it("returns the rental object", function(done) { + agent.post('/rentals/1/Maws').set('Content-Type', 'application/json') + .expect(200, function(err, res) { + assert.equal(res.body.movie_copy_id, 2); + + var keys = ['id', 'movie_copy_id', 'customer_id', 'checkout_date', 'return_date', 'return_status', 'cost']; + assert.deepEqual(Object.keys(res.body), keys); + + done(); + }); + }); // returns array objects + }); // end post block + +describe("PATCH '/'", function() { + it("knows about the route", function(done) { + agent.patch('/rentals/return/1/Jaws').set('Accept', 'application/json') + .expect('Content-Type', /application\/json/) + .expect(200, function(err, res) { + assert.equal(err, undefined); + + done(); + }); + }); + + it("returns the rental object", function(done) { + agent.patch('/rentals/return/1/Jaws').set('Accept', 'application/json') + .expect(200, function(err, res) { + assert.equal(res.body.return_status, 1); + + var keys = ['id', 'movie_copy_id', 'customer_id', 'checkout_date', 'return_date', 'return_status', 'cost']; + assert.deepEqual(Object.keys(res.body), keys); + + done(); + }); + }); // returns array objects + }); // end patch block +}); // end describe 'rentals controller' block diff --git a/test/models/customers.js b/test/models/customers.js new file mode 100644 index 0000000..1467f5d --- /dev/null +++ b/test/models/customers.js @@ -0,0 +1,106 @@ +var assert = require('assert'), + Customer = require('../../models/customer'), + sqlite3 = require('sqlite3').verbose(); + +describe("Customer", function() { + var customer, db_cleaner; + + beforeEach(function(done) { + customer = new Customer(); + + db_cleaner = new sqlite3.Database('db/test.db'); + db_cleaner.serialize(function() { + db_cleaner.exec( + "BEGIN; \ + DELETE FROM customers; \ + INSERT INTO customers(name, registered_at, address, city, state, postal_code, phone, account_credit) \ + VALUES('Jan', '2015-09-01', '123 Nope St', 'Seattle', 'WA', '98104', '(206) 555-5555)', 100), \ + ('Stan', '2015-09-02', '123 Nope St', 'Seattle', 'WA', '98104', '(206) 555-5555)', 100), \ + ('Dan', '2015-09-03', '123 Nope St', 'Seattle', 'WA', '98104', '(206) 555-5555)', 100), \ + ('Mann', '2015-09-04', '123 Nope St', 'Seattle', 'WA', '98104', '(206) 555-5555)', 100), \ + ('Bam', '2014-09-05', '123 Nope St', 'Seattle', 'WA', '98104', '(206) 555-5555)', 100); \ + COMMIT;" + , function(err) { + db_cleaner.close(); + done(); + } + ); + }); + }); + + it("can be instantiated", function() { + assert(customer instanceof Customer); + }); + + describe("instance methods", function() { + it("can find all customers", function(done) { + customer.all(function(err, res) { + assert.equal(err, undefined); + assert(res instanceof Array); + assert.equal(res.length, 5); + + assert.equal(res[0].name, 'Jan'); + assert.equal(res[1].name, 'Stan'); + + done(); + }); + }); + + it("can find some of the customers", function(done) { + customer.find_by_with_limit("name", "%n%", 100, 0, function(error, result) { + assert.equal(error, undefined); + assert(result instanceof Array); + assert.equal(result.length, 4); + + var expected_names = ['Dan', 'Jan', 'Mann', 'Stan'], + actual_names = []; + + for(var index in result) { + actual_names.push(result[index].name); + } + + assert.deepEqual(expected_names, actual_names); + done(); + }); + }); + + it("can put some of the customers in date order", function(done) { + customer.find_by_with_limit("registered_at", "%4%", 100, 0, function(error, result) { + assert.equal(error, undefined); + assert(result instanceof Array); + assert.equal(result.length, 2); + + var expected_names = ['Bam', 'Mann'], + actual_names = []; + + for(var index in result) { + actual_names.push(result[index].name); + } + + assert.deepEqual(expected_names, actual_names); + + done(); + }); + }); + + it("can find a customer by id", function(done){ + customer.find_by("id", 1, function(err, res) { + assert.equal(err, undefined); + assert(res instanceof Array); + assert.equal(res.length, 1); + assert.equal(res[0].id, 1); + done(); + }); + }); + + it("can find a customer by name", function(done) { + customer.find_by("name", "%Bam%", function(err, res) { + assert.equal(err, undefined); + assert(res instanceof Array); + assert.equal(res.length, 1); + assert.equal(res[0].name, 'Bam'); + done(); + }); + }); + }); +}); diff --git a/utils/json_scrubber.js b/utils/json_scrubber.js new file mode 100644 index 0000000..05817f5 --- /dev/null +++ b/utils/json_scrubber.js @@ -0,0 +1,24 @@ +"use strict"; + +var fs = require('fs'); +var customers = require('../customers'); +var json_array = []; + +for(var i = 0; i < customers.length; i++) { + var customer = customers[i]; + var date = new Date(customer.registered_at); + var formatted_date = date.toISOString(); + + customer.registered_at = formatted_date.split("T")[0]; + + json_array.push(customer); +} + +var stringy_json_array = JSON.stringify(json_array); + +fs.writeFile('formatted_customers.json', stringy_json_array, function(err) { + if (err) { + console.log(err); + return; + } +}); diff --git a/utils/schema.js b/utils/schema.js new file mode 100644 index 0000000..ee2868c --- /dev/null +++ b/utils/schema.js @@ -0,0 +1,98 @@ +"use strict"; + +var sqlite3 = require('sqlite3').verbose(), + db_env = process.env.DB || 'development', + db = new sqlite3.Database('db/' + db_env + '.db'); + +var movie_fields = [ + ['title', 'text'], + ['overview', 'text'], + ['release_date', 'text'], + ['inventory', 'integer'], +]; + +var movie_copies_fields = [ + ['is_available', 'integer'] +]; + +var customer_fields = [ + ['name', 'text'], + ['registered_at', 'text'], + ['address', 'text'], + ['city', 'text'], + ['state', 'text'], + ['postal_code', 'text'], + ['phone', 'text'], + ['account_credit', 'integer'] +]; + +var rental_fields = [ + ['checkout_date', 'text'], + ['return_date', 'text'], + ['return_status', 'integer'], + ['cost', 'integer'] +]; + // sqlite does no have a separate boolean class, using values 0 and 1 for true and false + +db.serialize(function() { + // drop existing tables + db.run("DROP TABLE IF EXISTS movies;"); + db.run("DROP TABLE IF EXISTS movie_copies;"); + db.run("DROP TABLE IF EXISTS customers;"); + db.run("DROP TABLE IF EXISTS rentals;"); + + // create fresh versions of those tables + db.run("CREATE TABLE movies (id INTEGER PRIMARY KEY);"); + db.run("CREATE TABLE customers (id INTEGER PRIMARY KEY);"); + + // movie copies table with foreign keys + db.run("CREATE TABLE movie_copies( \ + id INTEGER PRIMARY KEY, \ + movie_id INTEGER, \ + FOREIGN KEY (movie_id) REFERENCES movies(id) \ + );"); + + db.run("CREATE TABLE rentals( \ + id INTEGER PRIMARY KEY, \ + movie_copy_id INTEGER, \ + customer_id INTEGER, \ + FOREIGN KEY (movie_copy_id) REFERENCES movie_copies(id), \ + FOREIGN KEY (customer_id) REFERENCES customers(id) \ + );"); + + // add columns that I need to those tables + for(var i = 0; i < movie_fields.length; i++) { + var name = movie_fields[i][0], + type = movie_fields[i][1]; + + // ALTER TABLE movies ADD COLUMN title text; + db.run("ALTER TABLE movies ADD COLUMN " + name + " " + type + ";"); + } + + for(var i = 0; i < movie_copies_fields.length; i++) { + var name = movie_copies_fields[i][0], + type = movie_copies_fields[i][1]; + + // ALTER TABLE movies ADD COLUMN title text; + db.run("ALTER TABLE movie_copies ADD COLUMN " + name + " " + type + ";"); + } + + for(var i = 0; i < customer_fields.length; i++) { + var name = customer_fields[i][0], + type = customer_fields[i][1]; + + // ALTER TABLE customers ADD COLUMN title text; + db.run("ALTER TABLE customers ADD COLUMN " + name + " " + type + ";"); + } + + for(var i = 0; i < rental_fields.length; i++) { + var name = rental_fields[i][0], + type = rental_fields[i][1]; + + // ALTER TABLE customers ADD COLUMN title text; + db.run("ALTER TABLE rentals ADD COLUMN " + name + " " + type + ";"); + } + +}); + +db.close(); diff --git a/utils/seeds.js b/utils/seeds.js new file mode 100644 index 0000000..3dbf921 --- /dev/null +++ b/utils/seeds.js @@ -0,0 +1,74 @@ +"use strict"; + +var sqlite3 = require('sqlite3').verbose(), + db_env = process.env.DB || 'development', + db = new sqlite3.Database('db/' + db_env + '.db'); + +var movies = require('../movies'); +var movie_statement = db.prepare( + "INSERT INTO movies(title, overview, inventory, release_date) \ + VALUES (?, ?, ?, ?);" +); + +var movie_copies_statement = db.prepare( + "INSERT INTO movie_copies(movie_id, is_available) \ + VALUES (?, ?);" +); + +var customers = require('../formatted_customers'); +var customers_statement = db.prepare( + "INSERT INTO customers(name, registered_at, address, city, state, postal_code, phone, account_credit) \ + VALUES (?, ?, ?, ?, ?, ?, ?, ?);" +); + +db.serialize(function() { + // loop them movies + for (var i = 0; i < movies.length; i++) { + var movie = movies[i]; + + // insert each into db + movie_statement.run( + movie.title, + movie.overview, + movie.inventory, + movie.release_date + ); + } + movie_statement.finalize(); + + // loop them movies FOR COPIES + for (var i = 0; i < movies.length; i++) { + var movie = movies[i]; // this needs to be from db, not json seed + var num_copies = movie.inventory; + + for (var j = 0; j < num_copies; j++) { + movie_copies_statement.run( + i+1, + 1 + ); + } + } + movie_copies_statement.finalize(); + + for (var i = 0; i < customers.length; i++) { + var customer = customers[i]; + + // insert each into db + customers_statement.run( + customer.name, + customer.registered_at, + customer.address, + customer.city, + customer.state, + customer.postal_code, + customer.phone, + customer.account_credit + ); + } + + customers_statement.finalize(); +}); + + + +db.close(); diff --git a/views/error.jade b/views/error.jade new file mode 100644 index 0000000..51ec12c --- /dev/null +++ b/views/error.jade @@ -0,0 +1,6 @@ +extends layout + +block content + h1= message + h2= error.status + pre #{error.stack} diff --git a/views/index.jade b/views/index.jade new file mode 100644 index 0000000..3d63b9a --- /dev/null +++ b/views/index.jade @@ -0,0 +1,5 @@ +extends layout + +block content + h1= title + p Welcome to #{title} diff --git a/views/layout.jade b/views/layout.jade new file mode 100644 index 0000000..15af079 --- /dev/null +++ b/views/layout.jade @@ -0,0 +1,7 @@ +doctype html +html + head + title= title + link(rel='stylesheet', href='/stylesheets/style.css') + body + block content