diff --git a/.gitignore b/.gitignore index 646ac519e..8c71495a7 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,4 @@ .DS_Store node_modules/ +*.log +coverage/ diff --git a/app.js b/app.js index f0579b1dc..8fd6d9460 100644 --- a/app.js +++ b/app.js @@ -4,14 +4,20 @@ var favicon = require('serve-favicon'); var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); +var massive = require('massive'); -var routes = require('./routes/index'); +var app = module.exports = express(); -var app = express(); +// database setup +var connectionString = "postgres://localhost/videoStore"; +// the node module that is used to access the database +var db = massive.connectSync({connectionString: connectionString}); +// this gives me a way to grab the database from the code, like in the model +app.set('db', db); // view engine setup app.set('views', path.join(__dirname, 'views')); -app.set('view engine', 'jade'); +app.set('view engine', 'ejs'); // uncomment after placing your favicon in /public //app.use(favicon(path.join(__dirname, 'public', 'favicon.ico'))); @@ -21,8 +27,18 @@ app.use(bodyParser.urlencoded({ extended: false })); app.use(cookieParser()); app.use(express.static(path.join(__dirname, 'public'))); +var routes = require('./routes/index'); app.use('/', routes); +var moviesRoutes = require('./routes/movies'); +app.use('/movies', moviesRoutes); + +var customersRoutes = require('./routes/customers'); +app.use('/customers', customersRoutes); + +var rentalsRoutes = require('./routes/rentals'); +app.use('/rentals', rentalsRoutes); + // catch 404 and forward to error handler app.use(function(req, res, next) { var err = new Error('Not Found'); diff --git a/controllers/customers_controller.js b/controllers/customers_controller.js new file mode 100644 index 000000000..5cbd68da5 --- /dev/null +++ b/controllers/customers_controller.js @@ -0,0 +1,97 @@ +var Customers = require("../models/customers"); +var Rentals = require("../models/rentals"); +var History = require("../models/history"); + +var CustomersController = { + index: function(req, res, next) { + Customers.all(function(error, customers) { // this is a callback + if(error) { + var err = new Error("Error retrieving customers list:\n" + error.message); + err.status = 500; + next(err); + } else { + var obj = {}; + if (customers.length === 0) { + obj["status"] = 204; + } else { + obj["status"] = 200; + } + } + obj["customers"] = customers; + res.json(obj); + }); + }, + + // the word phone breaks the code: + sort: function(req, res, next) { + if(req.params.column === "name" || req.params.column === "registered_at" || req.params.column === "postal_code") { + + //Send in an ORDER clause and a LIMIT with OFFSET + var options = { + limit : req.query.n, + order : req.params.column, + offset: req.query.p + }; + // products ordered in descending fashion + // takes 2 parameters + // options and the callback fucntion + Customers.sort_by(options, function(error, customers) { // this will return and array of customers + if(error) { + var err = new Error("No such customer"); + err.status = 404; + next(err); + } else { + var obj = {}; + if (customers.length === 0) { + obj["status"] = 204; + } else { + obj["status"] = 200; + } + obj["customers"] = customers; + res.json(obj); + } + }); + } else { + var obj = {}; + obj["status"] = 400; + obj["message"] = "invalid request"; + res.json(obj); + } + }, + // this is a property whose value is a function (this is now a method) + // this is the response from node + current: function(req, res, next) { + //model has a find method that takes in 2 arguments (id, callback function) + Rentals.find_by_customer(req.params.id, function(error, rentals) { + if(error) { + var err = new Error("No such account"); + err.status = 404; + next(err); + } else { + res.json(rentals); // write out the rentals as json + } + }); + }, + + history: function(req, res, next) { + //model has a find method that takes in 2 arguments (id, callback function) + History.getPastRentalHistory(req.params.id, function(error, history) { + if(error) { + var err = new Error("No history at this store"); + err.status = 404; + next(err); + } else { + var obj = {}; + if (history.length === 0) { + obj["status"] = 204; + } else { + obj["status"] = 200; + } + obj["history"] = history; + res.json(obj); // write out the history as json + } + }); + } +}; + +module.exports = CustomersController; diff --git a/controllers/index_controller.js b/controllers/index_controller.js new file mode 100644 index 000000000..ff1627c70 --- /dev/null +++ b/controllers/index_controller.js @@ -0,0 +1,21 @@ +var doc = require ("../doc.json"); + + +var IndexController = { + locals: { + documentation: doc + }, + + getJSON: function (req, res, next) { + res.json(200, doc); + }, + + getHTML: function(req, res, next) { + // console.log(string_docs); + res.render('docs', IndexController.locals); + } +}; + + + +module.exports = IndexController; diff --git a/controllers/movies_controller.js b/controllers/movies_controller.js new file mode 100644 index 000000000..28812d29a --- /dev/null +++ b/controllers/movies_controller.js @@ -0,0 +1,68 @@ +var Movies = require("../models/movies"); +var Rentals = require('../models/rentals'); +var Customers = require('../models/customers'); + +var MoviesController = { + index: function(req, res, next) { + Movies.all(callback(res, "movies")); + }, + + sort: function(req, res, next) { + var order_by = req.params.column; + var options = { + limit : req.query.n, + offset: req.query.p + }; + + if (order_by === "release_date" || order_by === "release-date") { + options["order"] = "release_date"; + } else if (order_by === "title") { + options["order"] = "title"; + } else { + options["order"] = "id"; + } + + Movies.sort_by(options, callback(res, "movies")); + }, + + current: function(req, res, next) { + var movie = req.params.title; + Movies.find_customers_by_title(movie, callback(res, "customers")); + }, + + history: function(req, res, next) { + var movie = req.params.title; + var column = req.params.column; + var order_by; + + if (column === 'name') { + order_by = 'name'; + } else if (column === 'checkout_date') { + order_by = 'checkout_date'; + } + + Movies.find_customers_by_history(movie, order_by, callback(res, "customers")); + } +}; + +function callback(res, type) { + return function(error, result) { // this is a callback + if(error) { + var err = new Error("Error retrieving results:\n" + error.message); + err.status = 500; + res.render(err); + } else { + var obj = {}; + if (result.length === 0) { + obj["status"] = 204; + } else { + obj["status"] = 200; + } + obj[type] = result; + res.json(obj); + } + } +} + +module.exports = MoviesController; + diff --git a/controllers/rentals_controller.js b/controllers/rentals_controller.js new file mode 100644 index 000000000..5000308a1 --- /dev/null +++ b/controllers/rentals_controller.js @@ -0,0 +1,123 @@ +var Rentals = require('../models/rentals'); +var Movies = require("../models/movies"); +var Customers = require("../models/customers"); +var History = require("../models/history"); + +var RentalsController = { + + find_movie: function(req, res, next) { + var movie = req.params.title; + + Movies.find(movie, function(error, found_movie) { + if(error) { + var err = new Error("No such movie"); + err.status = 404; + next(err); + } else { + obj = {} + obj['status'] = 200; + obj['Movie Info'] = {}; + obj['Movie Info']['Synopsis'] = found_movie.overview; + obj['Movie Info']['Release Date'] = found_movie.release_date; + obj['Movie Info']['Total Inventory'] = found_movie.inventory; + Rentals.available(found_movie.id, function(err, number){ + obj['Movie Info']['Available Inventory'] = number + res.json(obj); + }); + } + }); + }, + + current_customers: function(req, res, next) { + var movie = req.params.title; + + Rentals.find_customers_by_title(movie, function(error, customers) { + if(error) { + var err = new Error("No such movie"); + err.status = 404; + next(err); + } else { + var obj = {}; + if (customers.length === 0) { + obj["status"] = 204; + } else { + obj["status"] = 200; + } + obj["customers"] = customers; + res.json(obj); + } + }) + }, + + checkout: function(req, res, next) { + var customer_id = req.params.id; + var movie = req.params.title; + + + Rentals.mark_as_checkout (movie, customer_id, function(error, rental_array, customer_updated) { + if(error) { + var err = new Error(error.message); + err.status = 404; + next(err); + } else { + var rental_id = rental_array[0].id + History.create_record(rental_id, customer_id, function(error, history_result) { + if(error) { + var err = new Error(error.message); + err.status = 404; + next(err); + } else { + obj = {} + obj["Status"] = 200; + obj["Message"] = "Checkout has been processed succesfully" + obj["Return day"] = history_result["return_date"] + obj["Customer's Name"] = customer_updated[0]["name"] + obj["Customer's Credit"] = customer_updated[0]["account_credit"] + + res.json(obj); + } + }); + } + }); + }, + + // include customer name, movie title, check-out date, and return date + overdue: function(req, res, next) { + Rentals.get_overdue(function(error, customers) { + if(error) { + var err = new Error(error.message); + err.status = 500; + next(err); + } else { + obj = {}; + if (customers.length === 0) { + obj["status"] = 204; + } else { + obj["status"] = 200; + } + obj["customers"] = customers + res.json(obj); + } + }) + }, + + return_a_rental: function(req, res, next) { + var customer_id = req.params.id; + var movie = req.params.title; + Rentals.return_rental(customer_id, movie, function(error, updated_rental) { + if(error) { + var err = new Error(error.message); + err.status = 404; + next(err); + } else { + obj = {} + obj["Status"] = 200; + obj["Message"] = "Return has been processed succesfully" + obj["Rental info"] = updated_rental[0] + res.json(obj); + } + }) + } +}; + +module.exports = RentalsController; diff --git a/db/seeds/history.json b/db/seeds/history.json new file mode 100644 index 000000000..c33d3d1d0 --- /dev/null +++ b/db/seeds/history.json @@ -0,0 +1,74 @@ +[ +{ +"rental_id": 33, +"customer_id": 2, +"checkout_date": "4/16/2016", +"return_date": "4/26/2016" +}, +{ +"rental_id": 9, +"customer_id": 2, +"checkout_date": "6/17/2016", +"return_date": "6/27/2016" +}, +{ +"rental_id": 15, +"customer_id": 2, +"checkout_date": "6/18/2016", +"return_date": "6/28/2016" +}, +{ +"rental_id": 2, +"customer_id": 4, +"checkout_date": "2/19/2016", +"return_date": "2/29/2016" +}, +{ +"rental_id": 10, +"customer_id": 4, +"checkout_date": "6/10/2016", +"return_date": "6/20/2016" +}, +{ +"rental_id": 21, +"customer_id": 4, +"checkout_date": "6/6/2016", +"return_date": "6/16/2016" +}, +{ +"rental_id": 2, +"customer_id": 1, +"checkout_date": "6/16/2016", +"return_date": "6/26/2016" +}, +{ +"rental_id": 3, +"customer_id": 3, +"checkout_date": "7/10/2016", +"return_date": "7/20/2016" +}, +{ +"rental_id": 12, +"customer_id": 2, +"checkout_date": "3/13/2016", +"return_date": "4/23/2016" +}, +{ +"rental_id": 4, +"customer_id": 1, +"checkout_date": "6/16/2016", +"return_date": "6/26/2016" +}, +{ +"rental_id": 6, +"customer_id": 11, +"checkout_date": "6/16/2016", +"return_date": "6/26/2016" +}, +{ +"rental_id": 5, +"customer_id": 2, +"checkout_date": "6/12/2016", +"return_date": "6/22/2016" +} +] diff --git a/db/setup/schema.sql b/db/setup/schema.sql new file mode 100644 index 000000000..5b0c54e78 --- /dev/null +++ b/db/setup/schema.sql @@ -0,0 +1,47 @@ +DROP TABLE IF EXISTS movies; +CREATE TABLE movies( + id serial PRIMARY KEY, + title text, + overview text, + release_date text, + inventory integer +); + +CREATE INDEX movies_title ON movies (title); +CREATE INDEX movies_release_date ON movies (release_date); + +DROP TABLE IF EXISTS customers; +CREATE TABLE customers( + id serial PRIMARY KEY, + name text, + registered_at text, + address text, + city text, + state text, + postal_code text, + phone text, + account_credit decimal +); + + +DROP TABLE IF EXISTS rentals; +CREATE TABLE rentals( + id serial PRIMARY KEY, + movie_id integer, + customer_id integer, + status text +); + +CREATE INDEX rentals_customer_id ON rentals (customer_id); +CREATE INDEX rentals_status ON rentals (status); + +DROP TABLE IF EXISTS history; +CREATE TABLE history( + id serial PRIMARY KEY, + rental_id integer, + customer_id integer, + checkout_date text, + return_date text +); + +CREATE INDEX history_customer_id ON history (customer_id); diff --git a/db/sql/history/overdue.sql b/db/sql/history/overdue.sql new file mode 100644 index 000000000..9b166c5d4 --- /dev/null +++ b/db/sql/history/overdue.sql @@ -0,0 +1,16 @@ +-- customer name +-- movie title +-- history check-out date +-- history return date + +SELECT customers.name, movies.title, history.checkout_date, history.return_date FROM history + INNER JOIN rentals + ON history.rental_id = rentals.id + INNER JOIN movies + ON rentals.movie_id = movies.id + INNER JOIN customers + ON history.customer_id = customers.id +WHERE return_date < $1 AND rentals.status = 'rented'; + + +-- if the status in the rentals is 'rented' and the return date in the history table is before today \ No newline at end of file diff --git a/db/sql/movies/currentCustomers.sql b/db/sql/movies/currentCustomers.sql new file mode 100644 index 000000000..451b1fab8 --- /dev/null +++ b/db/sql/movies/currentCustomers.sql @@ -0,0 +1,6 @@ +SELECT customers.name, customers.phone, customers.account_credit FROM movies + INNER JOIN rentals + ON movies.id=rentals.movie_id + INNER JOIN customers + ON rentals.customer_id=customers.id +WHERE title = $1; diff --git a/db/sql/movies/historyCustomersByDate.sql b/db/sql/movies/historyCustomersByDate.sql new file mode 100644 index 000000000..c1c537856 --- /dev/null +++ b/db/sql/movies/historyCustomersByDate.sql @@ -0,0 +1,9 @@ +SELECT customers.id, customers.name, customers.phone, customers.account_credit, history.checkout_date FROM movies + INNER JOIN rentals + ON movies.id=rentals.movie_id + INNER JOIN history + ON rentals.id=history.rental_id + INNER JOIN customers + ON history.customer_id=customers.id +WHERE title = $1 +ORDER BY history.checkout_date; diff --git a/db/sql/movies/historyCustomersByName.sql b/db/sql/movies/historyCustomersByName.sql new file mode 100644 index 000000000..e78bfa135 --- /dev/null +++ b/db/sql/movies/historyCustomersByName.sql @@ -0,0 +1,9 @@ +SELECT customers.id, customers.name, customers.phone, customers.account_credit, history.checkout_date FROM movies + INNER JOIN rentals + ON movies.id=rentals.movie_id + INNER JOIN history + ON rentals.id=history.rental_id + INNER JOIN customers + ON history.customer_id=customers.id +WHERE title = $1 +ORDER BY customers.name; diff --git a/db/sql/movies/historyCustomersDefault.sql b/db/sql/movies/historyCustomersDefault.sql new file mode 100644 index 000000000..b0678fcf1 --- /dev/null +++ b/db/sql/movies/historyCustomersDefault.sql @@ -0,0 +1,9 @@ +SELECT customers.id, customers.name, customers.phone, customers.account_credit, history.checkout_date FROM movies + INNER JOIN rentals + ON movies.id=rentals.movie_id + INNER JOIN history + ON rentals.id=history.rental_id + INNER JOIN customers + ON history.customer_id=customers.id +WHERE title = $1 +ORDER BY customers.id; diff --git a/db/sql/rentals/checkout.sql b/db/sql/rentals/checkout.sql new file mode 100644 index 000000000..cf8eaf5fe --- /dev/null +++ b/db/sql/rentals/checkout.sql @@ -0,0 +1,12 @@ +UPDATE rentals AS r +SET customer_id = $1, status='rented' +FROM ( + SELECT rentals.id + FROM rentals + INNER JOIN movies + ON movies.id=rentals.movie_id + WHERE title=$2 AND rentals.status = 'available' + LIMIT 1 +) AS result +WHERE r.id=result.id +RETURNING *; diff --git a/db/sql/rentals/currentCustomers.sql b/db/sql/rentals/currentCustomers.sql new file mode 100644 index 000000000..0b18d4f78 --- /dev/null +++ b/db/sql/rentals/currentCustomers.sql @@ -0,0 +1,6 @@ +SELECT * FROM movies + INNER JOIN rentals + ON movies.id=rentals.movie_id + INNER JOIN customers + ON rentals.customer_id=customers.id +WHERE title = $1 diff --git a/db/sql/rentals/retunMovie.sql b/db/sql/rentals/retunMovie.sql new file mode 100644 index 000000000..fb01b9b13 --- /dev/null +++ b/db/sql/rentals/retunMovie.sql @@ -0,0 +1,12 @@ +UPDATE rentals AS r +SET customer_id = NULL, status='available' +FROM ( + SELECT rentals.id + FROM rentals + INNER JOIN movies + ON movies.id=rentals.movie_id + WHERE title=$2 AND rentals.customer_id = $1 + LIMIT 1 +) AS result +WHERE r.id=result.id +RETURNING *; diff --git a/db/sql/rentals/updatecustomer.sql b/db/sql/rentals/updatecustomer.sql new file mode 100644 index 000000000..3d30f81c0 --- /dev/null +++ b/db/sql/rentals/updatecustomer.sql @@ -0,0 +1,4 @@ +UPDATE customers +SET account_credit= account_credit + $1 +WHERE id=$2 +RETURNING*; diff --git a/doc.json b/doc.json new file mode 100644 index 000000000..6b9e7b03e --- /dev/null +++ b/doc.json @@ -0,0 +1,241 @@ +[ { + "customers": { + "list_all_customers": { + "routeName": "index", + "httpRequest": "GET /customers", + "requiredParameters": "none", + "optionalParameters": "none", + "intendedUse": "This endpoint contains a list of all customers in your database, generated at time of request.", + "dataBreakdown": { + "someDataFound": "returns the found data", + "noDataFound": "returns status 500, []", + "error": "Error" + } + } + }, + + "customers_sorted_by_name": { + "routeName": "index, sort", + "httpRequest": "GET /customers/sort/cloumn", + "requiredParameters": "none", + "optionalParameters": "n=? to limit the number of records returned, p=? for offset", + "intendedUse": "This endpoint returns a list of all customers in your database (generated at time of query), sorted by name. Query string n=? will limit the number of returned records, and p=? will offset your records by that number.", + "dataBreakdown": { + "someDataFound": "returns the found data", + "noDataFound": "Error", + "error": "Error" + } + }, + + "customers_sorted_by_postal_code": { + "routeName": "index, sort", + "httpRequest": "GET /customers/sort/column", + "requiredParameters": "none", + "optionalParameters": "n=? to limit the number of records returned, p=? for offset", + "intendedUse": "This endpoint returns a list of customers in your database (generated at time of query) sorted by zipcode. Query string n=? will limit the number of records returned, and p=? will offset your records by that number.", + "dataBreakdown": { + "someDataFound": "returns the found data", + "noDataFound": "Error", + "error": "Error" + } + }, + + "list_customer_checkouts": { + "routeName": "List Checkouts to Customer", + "httpRequest": "GET /customers/:id/current", + "requiredParameters": "customer id number", + "optionalParameters": "none", + "intendedUse": "Given a customer id, this endpoint returns a list (generated at time of query) of all checkouts a customer currently has.", + "dataBreakdown": { + "someDataFound": "returns the found data", + "noDataFound": "Error", + "error": "Error" + } + }, + + "customer_checkout_history": { + "routeName": "List Customer Checkout History", + "httpRequest": "GET /customers/:id/history", + "requiredParameters": "customer id number", + "optionalParameters": "none", + "intendedUse": "This endpoint returns a list (generated at time of query) of all checkouts a customer has previously made. (These are all films that have been returned.)", + "dataBreakdown": { + "someDataFound": "returns the found data", + "noDataFound": "Error", + "error": "Error" + } + }, + + "movies": { + "list_all_movies": { + "routeName": "List All Movies", + "httpRequest": "GET /movies", + "requiredParameters": "none", + "optionalParameters": "none", + "intendedUse": "This endpoint will return a list of all movies in your database, generated at time of query.", + "dataBreakdown": { + "someDataFound": "returns the found data", + "noDataFound": "Error", + "error": "Error" + } + }, + + "movies_sorted_by_title": { + "routeName": "List Movies, Sorted by Title", + "httpRequest": "GET /movies/sort/title", + "requiredParameters": "none", + "optionalParameters": "n=? to limit the number of records returned, p=? for offset", + "intendedUse": "This endpoint will return a list of all movies in your database, generated at time of query. Query string n=? will allow you to limit how many records are returned, and query string p=? will offset your records by that number.", + "dataBreakdown": { + "someDataFound": "returns the found data", + "noDataFound": "Error", + "error": "Error" + } + }, + + "movies_sorted_by_release": { + "routeName": "List Movies, Sorted by Title", + "httpRequest": "GET /movies/sort/title", + "requiredParameters": "none", + "optionalParameters": "n=? to limit the number of records returned, p=? for offset", + "intendedUse": "This endpoint will return a list of all movies in your database, generated at time of query. Query string n=? will allow you to limit how many records are returned, and query string p=? will offset your records by that number.", + "dataBreakdown": { + "someDataFound": "returns the found data", + "noDataFound": "Error", + "error": "Error" + } + }, + + "movie_details": { + "routeName": "View Details for Title", + "httpRequest": "GET /movies/:title", + "requiredParameters": "film title", + "optionalParameters": "none", + "intendedUse": "This endpoint returns the details for a movie title. It will show title, summary description, release date, number of copies owned, and number of copies in stock. Generated at time of query.", + "dataBreakdown": { + "someDataFound": "returns the found data", + "noDataFound": "Error", + "error": "Error" + } + }, + + "movie_checkouts": { + "routeName": "View Checkouts for Title", + "httpRequest": "GET /movies/:title/current", + "requiredParameters": "film title", + "optionalParameters": "none", + "intendedUse": "This endpoint returns the the current checkout records for the given film. It will return customer name, phone number, and account credit for each checked out copy of the film.", + "dataBreakdown": { + "someDataFound": "returns the found data", + "noDataFound": "Error", + "error": "Error" + } + }, + + "movie_hist_name_sort": { + "routeName": "View Historical Checkouts for Title by Name", + "httpRequest": "GET /movies/:title/history/sort/name", + "requiredParameters": "film title", + "optionalParameters": "none", + "intendedUse": "This endpoint returns the the historical checkout records for the given film. It will return customer name, phone number, and account credit for each checked out copy of the film and is ordered by customer name.", + "dataBreakdown": { + "someDataFound": "returns the found data", + "noDataFound": "Error", + "error": "Error" + } + }, + + "movie_hist_checkout_sort": { + "routeName": "View Historical Checkouts for Title by Date", + "httpRequest": "GET /movies/:title/history/sort/date", + "requiredParameters": "film title", + "optionalParameters": "none", + "intendedUse": "This endpoint returns the the historical checkout records for the given film. It will return customer name, phone number, and account credit for each checked out copy of the film and is ordered by customer name.", + "dataBreakdown": { + "someDataFound": "returns the found data", + "noDataFound": "Error", + "error": "Error" + } + } + }, + + "rentals": { + "rental_checkouts": { + "routeName": "View Current Checkouts for Title", + "httpRequest": "GET /rentals/:title/current", + "requiredParameters": "film title", + "optionalParameters": "none", + "intendedUse": "This endpoint returns the the historical checkout records for the given film. It will return customer name, phone number, and account credit for each checked out copy of the film and is ordered by customer name.", + "dataBreakdown": { + "someDataFound": "returns the found data", + "noDataFound": "Error", + "error": "Error" + } + }, + + "rental_hist_name_sort": { + "routeName": "View Historical Checkouts for Title by Name", + "httpRequest": "GET /rentals/:title/history/sort/name", + "requiredParameters": "film title", + "optionalParameters": "none", + "intendedUse": "This endpoint returns the the historical checkout records for the given film. It will return customer name, phone number, and account credit for each checked out copy of the film and is ordered by customer name.", + "dataBreakdown": { + "someDataFound": "returns the found data", + "noDataFound": "Error", + "error": "Error" + } + }, + + "rental_hist_checkout_sort": { + "routeName": "View Historical Checkouts for Title by Date", + "httpRequest": "GET /rentals/:title/history/sort/date", + "requiredParameters": "film title", + "optionalParameters": "none", + "intendedUse": "This endpoint returns the the historical checkout records for the given film. It will return customer name, phone number, and account credit for each checked out copy of the film and is ordered by customer name.", + "dataBreakdown": { + "someDataFound": "returns the found data", + "noDataFound": "Error", + "error": "Error" + } + }, + + "checkout_film_to_cust": { + "routeName": "View Historical Checkouts for Title by Date", + "httpRequest": "POST /rentals/:title/checkout", + "requiredParameters": "film title in route, customer ID in post body", + "optionalParameters": "none", + "intendedUse": "This endpoint will create a new checkout record tying the customer id and film title together in the rentals database along with date checked out and due date. Current default rental period is 3 days.", + "dataBreakdown": { + "someDataFound": "returns the checkout record", + "noDataFound": "Error", + "error": "Error" + } + }, + + "checkin_film_from_cust": { + "routeName": "View Historical Checkouts for Title by Date", + "httpRequest": "POST /rentals/:title/return", + "requiredParameters": "film title in route, customer ID in post body", + "optionalParameters": "none", + "intendedUse": "This endpoint will update the checkout record tying the customer id and film title together with the date checked in. It will also deduct any fees from customer credit. (This can send customer credit negative, which means they owe money.", + "dataBreakdown": { + "someDataFound": "returns the checkout record", + "noDataFound": "Error", + "error": "Error" + } + }, + + "list_of_overdue_films": { + "routeName": "List All Overdue Films", + "httpRequest": "GET /rentals/overdue", + "requiredParameters": "none", + "optionalParameters": "none", + "intendedUse": "This endpoint will return a list (generated at time of query) of all films currently checked out and overdue.", + "dataBreakdown": { + "someDataFound": "returns the found data", + "noDataFound": "Error", + "error": "Error" + } + } + } +}] diff --git a/models/customers.js b/models/customers.js new file mode 100644 index 000000000..58d3e6f73 --- /dev/null +++ b/models/customers.js @@ -0,0 +1,50 @@ +var app = require("../app"); +var db = app.get("db"); + +// Constructor function +var Customers = function(customer) { + this.id = customer.id; + this.name = customer.name; + this.registered_at = customer.registered_at; + this.address = customer.address; + this.city = customer.city; + this.state = customer.state; + this.postal_code = customer.postal_code; + this.phone = customer.phone; + this.account_credit = customer.account_credit; +}; + +Customers.all = function(callback) { + db.customers.find(function(error, customers) { + if(error || !customers) { + callback(error || new Error("Could not retrieve customers"), undefined); + } else { + callback(null, customers.map(function(customer) { + return new Customers(customer); + })); + } + }); +}; + +Customers.sort_by = function(options, callback) { + db.customers.find({}, options, function(error, customers){ + if(error || !customers) { + callback(error || new Error("Could not retrieve customers"), undefined); + } else { + callback(null, customers.map(function(customer) { + return new Customers(customer); + })); + } + }); +}; + + + + + + + + + + +module.exports = Customers; diff --git a/models/history.js b/models/history.js new file mode 100644 index 000000000..54d496a12 --- /dev/null +++ b/models/history.js @@ -0,0 +1,41 @@ +var app = require("../app"); +var db = app.get("db"); + +// Constructor function +var History = function(history) { + this.id = history.id; + this.rental_id = history.rental_id; + this.customer_id = history.customer_id; + this.checkout_date = history.checkout_date; + this.return_date = history.return_date; +}; + +History.getPastRentalHistory = function(customer_id, callback) { + db.run("SELECT customer_id, checkout_date, return_date FROM history WHERE customer_id=$1 ORDER BY return_date ASC", [customer_id], function(error, history) { + if(error) { + callback(error, undefined); + } else { + callback(null, history); + } + }); +}; + +History.create_record = function(rental_id, customer_id, callback) { + db.history.save({rental_id: rental_id, customer_id: customer_id, checkout_date: new Date().toLocaleDateString(), return_date: make_return_date()}, function(error,inserted){ + if(error) { + callback(error, undefined); + } else { + callback(null, new History(inserted)); + } + }); +} + +function make_return_date() { + var date = new Date(); + var return_days = 10; + date.setDate(date.getDate() + return_days); + return date.toLocaleDateString(); +} + + +module.exports = History; diff --git a/models/movies.js b/models/movies.js new file mode 100644 index 000000000..363ac7c72 --- /dev/null +++ b/models/movies.js @@ -0,0 +1,70 @@ +var app = require("../app"); +var db = app.get("db"); +var Customers = require('../models/customers'); + +// Constructor function +var Movies = function(movie) { + this.id = movie.id; + this.title = movie.title; + this.overview = movie.overview; + this.release_date = movie.release_date; + this.inventory = movie.inventory; +}; + +Movies.all = function(callback) { + db.movies.find(movies_callback(callback)); +}; + +Movies.sort_by = function(options, callback) { + db.movies.find({}, options, movies_callback(callback)); +}; + +Movies.find = function(title, callback) { + db.movies.findOne({title: title}, function(error, movie) { + if(error || !movie) { + callback(error || new Error("Could not retrieve movie"), undefined); + } else { + callback(null, new Movies(movie)); + } + }); +}; + +Movies.find_customers_by_title = function(title, callback) { + db.sql.movies.currentCustomers([title], customer_callback(callback)); +} + +Movies.find_customers_by_history = function(title, order_by, callback) { + if (order_by === 'name') { + db.sql.movies.historyCustomersByName(title, customer_callback(callback)); + } else if (order_by === 'checkout_date') { + db.sql.movies.historyCustomersByDate(title, customer_callback(callback)); + } else { + db.sql.movies.historyCustomersDefault(title, customer_callback(callback)); + } +} + +function customer_callback(passed_callback) { + return function(error, customers) { + if(error || !customers) { + passed_callback(error || new Error("Could not retrieve customers"), undefined); + } else { + passed_callback(null, customers.map(function(customer) { + return new Customers(customer); + })); + } + }; +} + +function movies_callback(passed_callback) { + return function(error, movies) { + if(error || !movies) { + passed_callback(error || new Error("Could not retrieve movies"), undefined); + } else { + passed_callback(null, movies.map(function(movie) { + return new Movies(movie); + })); + } + }; +} + +module.exports = Movies; diff --git a/models/rentals.js b/models/rentals.js new file mode 100644 index 000000000..c8c3cfbeb --- /dev/null +++ b/models/rentals.js @@ -0,0 +1,100 @@ +var app = require("../app"); +var db = app.get("db"); +var Customers = require('../models/customers'); + +// Constructor function +var Rentals = function(rental) { + this.id = rental.id; + this.movie_id = rental.movie_id; + this.customer_id = rental.customer_id; + this.status = rental.status; + +}; +// find the movies by customer id +// an array of customer ids +Rentals.find_by_customer = function(customer_id, callback) { + // key value that matches one of the column names in the rentals TABLE + // value is the specific value that we want to look for in the table + db.rentals.find({customer_id : customer_id}, function(error, rentals) { + if(error || !rentals) { + callback(error || new Error("Could not retrieve your movies"), undefined); + } else { + callback(null, rentals.map(function(rental) { + return new Rentals(rental); + })); + } + }); +}; + +// when movies are returned the customer id will be deleted from the rentals table + +Rentals.available = function(movie_id, callback){ + db.rentals.find({movie_id : movie_id, status: "available"}, function(error, rentals) { + if(error || !rentals) { + callback(error || new Error("Could not retrieve rentals"), undefined); + } else { + callback(null, rentals.length); + } + }); +}; + +//Melissa is using this. dont delete +Rentals.find_customers_by_title = function(title, callback) { + db.sql.rentals.currentCustomers([title], function(error, customers) { + if(error || !customers) { + callback(error || new Error("Could not retrieve customers"), undefined); + } else { + callback(null, customers.map(function(customer) { + return new Customers(customer); + })); + }; + }); +}; + +Rentals.mark_as_checkout = function(movie, customer_id, callback) { + db.sql.rentals.checkout([customer_id, movie], function(error, result){ + if(error) { + callback(error, undefined) + } else { + Rentals.update_custumer_credit(result, customer_id, callback) + }; + }); +}; + +Rentals.update_custumer_credit = function (result, customer_id, callback) { + var bonus = 0.50 + var customer_id = Number(customer_id) + db.sql.rentals.updatecustomer([bonus, customer_id], function (error, customer_updated) { + if (error) { + callback(error, undefined) + } else { + callback(null, result, customer_updated) + }; + }); +}; + +Rentals.get_overdue = function(callback) { + var today = new Date().toLocaleDateString(); + db.sql.history.overdue([today], function(error, customers) { + if(error) { + callback(error, undefined); + } else { + callback(null, customers); + } + }) +}; + +Rentals.return_rental = function(customer_id, movie, callback){ + //reach into the movies table, look by title, get movie_id, go to rentals, see that movie_id and customer_id matches + //change the status to available and delete the costumer-id or set it to cero + + db.sql.rentals.retunMovie([customer_id, movie], function(error, updated_rental) { + if(error || !updated_rental) { + callback(error || new Error("Could not retrieve updated_rental"), undefined); + } else { + callback(null, updated_rental); + } + }); +}; + +module.exports = Rentals diff --git a/package.json b/package.json index d39b26403..e67c6bb4e 100644 --- a/package.json +++ b/package.json @@ -4,19 +4,27 @@ "private": true, "scripts": { "start": "nodemon ./bin/www", - "test": "clear; jasmine-node --verbose spec/" + "test": "clear; ./node_modules/.bin/istanbul cover -x 'spec/**/*' -- ./node_modules/.bin/jasmine-node --captureExceptions --verbose spec/", + "db:drop": "dropdb videoStore", + "db:create": "createdb videoStore", + "db:schema": "node tasks/load_schema.js", + "db:seed": "node tasks/seed.js", + "db:reset": "npm run db:drop; npm run db:create; npm run db:schema; npm run db:seed" }, "dependencies": { "body-parser": "~1.13.2", "cookie-parser": "~1.3.5", "debug": "~2.2.0", + "ejs": "^2.4.2", "express": "~4.13.1", "jade": "~1.11.0", + "massive": "^2.3.0", "morgan": "~1.6.1", "sequelize": "^3.23.3", "serve-favicon": "~2.3.0" }, "devDependencies": { + "istanbul": "^0.4.4", "jasmine-node": "^1.14.5", "nodemon": "^1.9.2", "request": "^2.72.0" diff --git a/routes/customers.js b/routes/customers.js new file mode 100644 index 000000000..69b3ab1b8 --- /dev/null +++ b/routes/customers.js @@ -0,0 +1,16 @@ +var express = require('express'); +var router = express.Router(); +var CustomersController = require('../controllers/customers_controller.js'); + +/* GET customers listing. */ +router.get('/', CustomersController.index); + +/* GET customers sorted by column details. */ +router.get('/sort/:column', CustomersController.sort); + +/* GET customers details. */ +router.get('/:id/current', CustomersController.current); + +router.get('/:id/history', CustomersController.history); + +module.exports = router; diff --git a/routes/index.js b/routes/index.js index 06cfc1137..45de1a885 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,9 +1,19 @@ var express = require('express'); var router = express.Router(); +var IndexController = require('../controllers/index_controller.js'); /* GET home page. */ router.get('/', function(req, res, next) { - res.status(200).json({whatevs: 'whatevs!!!'}) + res.status(200).json({whatevs: 'whatevs!!!'}); }); +router.get('/zomg', function(req, res, next) { + res.status(200).json({it_works: 'it works!'}); +}); + +router.get('/api/docs.json', IndexController.getJSON); + +router.get('/api/docs', IndexController.getHTML); + + module.exports = router; diff --git a/routes/movies.js b/routes/movies.js new file mode 100644 index 000000000..850356f1b --- /dev/null +++ b/routes/movies.js @@ -0,0 +1,70 @@ +var express = require('express'); +var router = express.Router(); +var MoviesController = require('../controllers/movies_controller.js'); + +/* GET movies listing. */ +router.get('/', MoviesController.index); + +/* GET movies sorted by column details. */ +router.get('/sort/:column', MoviesController.sort); + +/* GET customers that currently have the film */ +router.get('/:title/current', MoviesController.current); + +// /movies/Jaws/history/sort/name +/* GET customers that have checkout out the film in the past */ +router.get('/:title/history/sort/:column', MoviesController.history); + +module.exports = router; + + + +// - GET a list of all movies (/movies) +// * display a list to the customers. +// * in JSON +// +// - GET a subset of movies +// * make a dynamic route(s) +// * provide this path: +// 1. release_date: ("/movies/sort/release-date?n=5&p=1") +// 2. title: ("/movies/sort/title?n=5&p=1") +// - Given a sort column, return n movie records, offset by p records (this will be used to create "pages" of movies) +// - Sort columns are +// - title +// - release_date +// +// - Given a movies title... +// (employees are getting the information) +// - GET a list of customers that have currently checked out a copy of the film: +// get request (/movies/Jaws/current) +// connect = link movie to the rental, check status +// if status == active(checked out), +// return customer(s) +// a collection of customers (each): +// - name, +// - phone number, +// - account credit +// +// - GET a list of customers that have checked out a copy in the past route the employee will use: (/movies/Jaws/history/sort/name) +// intermediate table (history) +// keeps all movies rented +// pull out the records that have the movie id, +// then associte the id with the customer id +// then send the collection of customers, +// if +// return customers +// - include each customers +// name, +// phone number, +// account credit +// - ordered by customer name or +// - ordered by check out date +// +// cust_id movie_id +// 2 3 +// 4 3 +// 7 1 +// +// SELECT cust_id +// WHERE movie_id = 3 +// => [2,4] diff --git a/routes/rentals.js b/routes/rentals.js new file mode 100644 index 000000000..a159130d5 --- /dev/null +++ b/routes/rentals.js @@ -0,0 +1,19 @@ +var express = require('express'); +var router = express.Router(); +var RentalsController = require('../controllers/rentals_controller.js'); + +// GET overdue rentals +router.get('/overdue', RentalsController.overdue); + +/* GET rentals listing by title. */ +// Look a movie up by title to see (/rentals/Jaws) +router.get('/:title', RentalsController.find_movie); + +/* GET customers details. */ +router.get('/:title/customers', RentalsController.current_customers); + +router.post('/:title/check-out/:id', RentalsController.checkout); + +router.post('/:title/return/:id', RentalsController.return_a_rental); + +module.exports = router; diff --git a/spec/controllers/customers.spec.js b/spec/controllers/customers.spec.js new file mode 100644 index 000000000..6dbf44c57 --- /dev/null +++ b/spec/controllers/customers.spec.js @@ -0,0 +1,43 @@ +var request = require('request'); +var base_url = "http://localhost:3000/"; + + +describe("Endpoint at /", function () { + var url = function(endpoint) { + return base_url + "customers" + endpoint; + }; + + it('responds with a 200 status code', function (done) { + request.get(url('/'), function(error, response, body) { + expect(JSON.parse(body).status).toEqual(200); + done(); + }); + }); +}); + + describe("Endpoint at /sort", function() { + var url = function(endpoint) { + return base_url + "customers/sort" + endpoint; + }; + + it('responds with a 200 status code for a valid url', function(done) { + request.get(url('/name?n=10&p=2'), function(error, response, body) { + expect(JSON.parse(body).status).toEqual(200); + done(); + }); + }); + + it('responds with a 200 status code for a valid url', function(done) { + request.get(url('/registered_at?n=10&p=2'), function(error, response, body) { + expect(JSON.parse(body).status).toEqual(200); + done(); + }); + }); + + it('responds with a 200 status code for a valid url', function(done) { + request.get(url('/postal_code?n=10&p=2'), function(error, response, body) { + expect(JSON.parse(body).status).toEqual(200); + done(); + }); + }); +}); diff --git a/spec/controllers/index.spec.js b/spec/controllers/index.spec.js index e4151d267..ee9e394a2 100644 --- a/spec/controllers/index.spec.js +++ b/spec/controllers/index.spec.js @@ -1,29 +1,29 @@ -var request = require('request') -var base_url = "http://localhost:3000/" +var request = require('request'); +var base_url = "http://localhost:3000/"; describe("Endpoint at /", function () { it('responds with a 200 status code', function (done) { request.get(base_url, function(error, response, body) { - expect(response.statusCode).toEqual(200) - done() - }) - }) + expect(response.statusCode).toEqual(200); + done(); + }); + }); describe("the returned json data", function() { it('has the right keys', function(done) { request.get(base_url, function(error, response, body) { - var data = JSON.parse(body) - expect(Object.keys(data)).toEqual(['whatevs']) - done() - }) - }) + var data = JSON.parse(body); + expect(Object.keys(data)).toEqual(['whatevs']); + done(); + }); + }); it('has the right values for the keys', function(done) { request.get(base_url, function(error, response, body) { - var data = JSON.parse(body) - expect(data.whatevs).toEqual('whatevs!!!') - done() - }) - }) - }) -}) + var data = JSON.parse(body); + expect(data.whatevs).toEqual('whatevs!!!'); + done(); + }); + }); + }); +}); diff --git a/spec/controllers/movies.spec.js b/spec/controllers/movies.spec.js index ddcaf2f68..07052018c 100644 --- a/spec/controllers/movies.spec.js +++ b/spec/controllers/movies.spec.js @@ -1,5 +1,98 @@ var request = require('request'); +var base_url = "http://localhost:3000/"; -describe("Endpoints under /movies", function() { - -}) +describe('MoviesController', function() { + describe("Endpoint at /", function() { + var url = function(endpoint) { + return base_url + "movies" + endpoint; + }; + + it('responds with a 200 status code', function(done) { + request.get(url('/'), function(error, response, body) { + expect(JSON.parse(body).status).toEqual(200); + done(); + }); + }); + }); + + describe("Endpoint at /sort", function() { + var url = function(endpoint) { + return base_url + "movies/sort" + endpoint; + }; + + it('responds with a 200 status code for a valid url', function(done) { + request.get(url('/title?n=10&p=1'), function(error, response, body) { + expect(JSON.parse(body).status).toEqual(200); + done(); + }); + }); + + it('responds with a 200 status code for released_date', function(done) { + request.get(url('/release_date?n=6&p=1'), function(error, response, body) { + expect(JSON.parse(body).status).toEqual(200); + done(); + }); + }); + + it('responds with a 200 status code for title', function(done) { + request.get(url('/title?n=6&p=1'), function(error, response, body) { + expect(JSON.parse(body).status).toEqual(200); + done(); + }); + }); + + it('responds with a 200 status code for mispelling (by default sorts by id if invalid sort column given)', function(done) { + request.get(url('/released-date?n=10&p=1'), function(error, response, body) { + expect(response.statusCode).toEqual(200); + done(); + }); + }); + }); + + describe("Endpoint at movies/:title/current", function() { + var url = function(endpoint) { + return base_url + "movies" + endpoint; + }; + + it('responds with a 200 status code for a valid movie', function(done) { + request.get(url('/Jaws/current'), function(error, response, body) { + expect(JSON.parse(body).status).toEqual(200); + done(); + }); + }); + + it('responds with a 204 status code for an invalid movie', function(done) { + request.get(url('/asdfasdfasdf/current'), function(error, response, body) { + expect(JSON.parse(body).status).toEqual(204); + done(); + }); + }); + }); + + describe("Endpoint at movies/:title/history/sort/:column", function() { + var url = function(endpoint) { + return base_url + "movies" + endpoint; + }; + + it('responds with a 200 status code for a valid movie', function(done) { + request.get(url('/Jaws/history/sort/name'), function(error, response, body) { + expect(JSON.parse(body).status).toEqual(200); + done(); + }); + }); + + it('responds with a 204 status code for an invalid movie', function(done) { + request.get(url('/asdfasdfasdf/history/sort/name'), function(error, response, body) { + expect(JSON.parse(body).status).toEqual(204); + done(); + }); + }); + + it('responds with a 200 status code for an invalid sort column (sorted by id by default)', function(done) { + request.get(url('/Jaws/history/sort/blahblah'), function(error, response, body) { + expect(JSON.parse(body).status).toEqual(200); + done(); + }); + }); + }); +}); diff --git a/spec/controllers/rentals.spec.js b/spec/controllers/rentals.spec.js new file mode 100644 index 000000000..0d9303f03 --- /dev/null +++ b/spec/controllers/rentals.spec.js @@ -0,0 +1,146 @@ +var request = require('request'); +var base_url = "http://localhost:3000/"; + +describe('RentalsController', function() { + var url = function(endpoint) { + return base_url + "rentals" + endpoint; + }; + + describe("Endpoint at /Jaws", function () { + it('responds with a 200 status code for succesful request', function (done) { + request.get(url('/Jaws'), function(error, response, body) { + var body_parsed = JSON.parse(body) + expect(response.statusCode).toEqual(200) + expect(typeof body).toEqual("string") + expect(typeof body_parsed).toEqual("object") + expect(body_parsed["status"]).toEqual(200) + done(); + }); + }); + + it('response with an object with movie and rental information', function (done) { + request.get(url('/Jaws'), function(error, response, body) { + var body_parsed = JSON.parse(body) + expect(body_parsed["Movie Info"]["Synopsis"]).toEqual(jasmine.any(String)) + expect(body_parsed["Movie Info"]["Release Date"]).toEqual("1975-06-19") + expect(body_parsed["Movie Info"]["Total Inventory"]).toEqual(6) + expect(body_parsed["Movie Info"]["Available Inventory"]).toEqual(4) + done(); + }); + }); + + }); + + describe("Endpoint at /Titanic", function () { + it('responds with a 200 status code for succesful request', function (done) { + request.get(url('/Titanic'), function(error, response, body) { + var body_parsed = JSON.parse(body) + expect(response.statusCode).toEqual(200) + expect(typeof body).toEqual("string") + expect(typeof body_parsed).toEqual("object") + expect(body_parsed["status"]).toEqual(200) + done(); + }); + }); + + it('response with an object with movie and rental information', function (done) { + request.get(url('/Titanic'), function(error, response, body) { + var body_parsed = JSON.parse(body) + expect(body_parsed["Movie Info"]["Synopsis"]).toEqual(jasmine.any(String)) + expect(body_parsed["Movie Info"]["Release Date"]).toEqual("1997-12-19") + expect(body_parsed["Movie Info"]["Total Inventory"]).toEqual(5) + expect(body_parsed["Movie Info"]["Available Inventory"]).toEqual(5) + done(); + }); + }); + // it('responds with a 404 status code for bad request', function (done) { + // request.get(url('/Melissa'), function(error, response, body) { + // // var body_parsed = JSON.parse(body) + // // expect(response.statusCode).toEqual(200) + // // expect(typeof body).toEqual("string") + // // expect(typeof body_parsed).toEqual("object") + // // done(); + // }); + // }); + // + }); + + describe("Endpoint at /Jaws/customers", function () { + + it('responds with a 200 status code for succesful request', function (done) { + request.get(url('/Jaws/customers'), function(error, response, body) { + var body_parsed = JSON.parse(body) + expect(response.statusCode).toEqual(200) + expect(typeof body).toEqual("string") + expect(typeof body_parsed).toEqual("object") + expect(body_parsed["status"]).toEqual(200) + done(); + }); + }); + + it('responds with all customers info, for succesful request', function (done) { + request.get(url('/Jaws/customers'), function(error, response, body) { + var body_parsed = JSON.parse(body) + expect(body_parsed["customers"]).toEqual(jasmine.any(Array)) + expect(typeof body_parsed["customers"][0]["name"]).toEqual("string") + expect(typeof body_parsed["customers"][0]["registered_at"]).toEqual("string") + expect(typeof body_parsed["customers"][0]["address"]).toEqual("string") + expect(typeof body_parsed["customers"][0]["city"]).toEqual("string") + expect(typeof body_parsed["customers"][0]["state"]).toEqual("string") + expect(typeof body_parsed["customers"][0]["postal_code"]).toEqual("string") + expect(typeof body_parsed["customers"][0]["phone"]).toEqual("string") + expect(typeof body_parsed["customers"][0]["account_credit"]).toEqual("string") + done(); + }); + }); + }); + + describe("Endpoint at /Titanic/customers", function () { + + it('responds with a 204 status for not data found', function (done) { + request.get(url('/Titanic/customers'), function(error, response, body) { + var body_parsed = JSON.parse(body) + expect(response.statusCode).toEqual(200) + expect(typeof body).toEqual("string") + expect(typeof body_parsed).toEqual("object") + expect(body_parsed["status"]).toEqual(204) + done(); + }); + }); + + it('responds with a empty array, for not data found', function (done) { + request.get(url('/Titanic/customers'), function(error, response, body) { + var body_parsed = JSON.parse(body) + expect(body_parsed["customers"]).toEqual(jasmine.any(Array)) + expect(body_parsed["customers"]).toEqual([]) + done(); + }); + }); + }); + + // describe("Endpoint at /Notorious/check-out", function () { + // it('responds with a 200 status code, for succesful request', function (done) { + // request.post(url('/Notorious/check-out/100'), function(error, response, body) { + // var body_parsed = JSON.parse(body) + // expect(response.statusCode).toEqual(200) + // expect(typeof body).toEqual("string") + // expect(typeof body_parsed).toEqual("object") + // expect(body_parsed["Status"]).toEqual(200) + // done(); + // }); + // }); + + // it('responds with a Message for succesful request', function (done) { + // request.post(url('/Alien/check-out/100'), function(error, response, body) { + // var body_parsed = JSON.parse(body) + // expect(typeof body_parsed).toEqual("object") + // expect(body_parsed["Message"]).toEqual("Checkout has been processed succesfully") + // expect(typeof body_parsed["Message"]).toEqual("string") + // expect(body_parsed["Customer's Name"]).toEqual("Barbara Jacobson") + // expect(typeof body_parsed["Return day"]).toEqual("string") + // expect(body_parsed["Customer's Credit"]).toEqual("13.41") + // done(); + // }); + // }); + // }); +}); diff --git a/spec/models/customers.spec.js b/spec/models/customers.spec.js new file mode 100644 index 000000000..e7307d7fb --- /dev/null +++ b/spec/models/customers.spec.js @@ -0,0 +1,40 @@ +var app = require("../../app"); +var db = app.get("db"); + +var Customers = require('../../models/customers'); +var Movies = require('../../models/customers'); + +describe('Customers', function () { + afterEach(function () { + db.end(); + }); + + describe('#all', function () { + it('should return an array of customer instances', function (done) { + Customers.all(function (error, customers) { + expect(error).toBeNull + expect(customers).toEqual(jasmine.any(Array)); + expect(customers.length).toEqual(200); + done(); + }); + }); + }); + + describe('#sort_by', function () { + var options_release = { + limit : 5, + order : 'name', + offset: 1 + }; + + + it('should return an array of 5 Customers if limit is set to 5', function (done) { + Movies.sort_by(options_release, function (error, movies) { + expect(error).toBeNull; + expect(movies).toEqual(jasmine.any(Array)); + expect(movies.length).toEqual(5); + done(); + }); + }); + }); +}); diff --git a/spec/models/history.spec.js b/spec/models/history.spec.js new file mode 100644 index 000000000..0135f7897 --- /dev/null +++ b/spec/models/history.spec.js @@ -0,0 +1,38 @@ +var app = require("../../app"); +var db = app.get("db"); + +var History = require('../../models/history'); + +describe('History', function () { + afterEach(function () { + db.end(); + }); + + describe('#create_record', function () { + it('returns a History instance that has all properties defined', function (done) { + History.create_record(1, 1, function(error, result) { + expect(error).toBeNull; + expect(result.id).toNotBe(null); + expect(result.rental_id).toNotBe(null); + expect(result.customer_id).toNotBe(null); + expect(result.checkout_date).toNotBe(null); + expect(result.return_date).toNotBe(null); + db.history.destroy({id: result.id}, function(err, res){done()}); + }); + }); + }); + + describe('#getPastRentalHistory', function() { + it('returns an array of customer id, checkout date, and return date', function(done) { + History.getPastRentalHistory(1, function(error, result) { + expect(error).toBeNull; + expect(result).toEqual(jasmine.any(Array)); + expect(result[0].customer_id).toBe(1); + expect(result[0].checkout_date).toNotBe(null); + expect(result[0].return_date).toNotBe(null); + expect(Object.keys(result[0])).toEqual(['customer_id', 'checkout_date', 'return_date']); + done(); + }) + }) + }) +}); diff --git a/spec/models/movies.spec.js b/spec/models/movies.spec.js new file mode 100644 index 000000000..894974fbf --- /dev/null +++ b/spec/models/movies.spec.js @@ -0,0 +1,134 @@ +var app = require("../../app"); +var db = app.get("db"); + +var Movies = require('../../models/movies'); +var Customers = require('../../models/customers'); + +describe('Movies', function () { + var options_release = { + limit : 5, + order : 'release_date', + offset: 1 + }; + + var options_title = { + limit : 5, + order : 'title', + offset: 1 + }; + + afterEach(function () { + db.end(); + }); + + describe('#all', function () { + it('should return an array of 100 Movie instances', function (done) { + Movies.all(function (error, movies) { + expect(error).toBeNull; + expect(movies).toEqual(jasmine.any(Array)); + expect(movies.length).toEqual(100); + done(); + }); + }); + }); + + describe('#sort_by', function () { + + it('should return an array of 5 Movies if limit is set to 5', function (done) { + Movies.sort_by(options_release, function (error, movies) { + expect(error).toBeNull; + expect(movies).toEqual(jasmine.any(Array)); + expect(movies.length).toEqual(5); + done(); + }); + }); + + it('should return The Phantom of the Opera first when sorted by release_date', function (done) { + Movies.sort_by(options_release, function (error, movies) { + expect(error).toBeNull; + expect(movies[0].title).toEqual("The Phantom of the Opera"); + expect(movies[0].id).toEqual(83); + done(); + }); + }); + + + it('should return 2001: A Space Odyssey first when sorted by title', function (done) { + Movies.sort_by(options_title, function (error, movies) { + expect(error).toBeNull; + expect(movies[0].title).toEqual("2001: A Space Odyssey"); + expect(movies[0].id).toEqual(40); + done(); + }); + }); + }); + + describe('#find', function () { + it('should return a Movie', function (done) { + Movies.find("Jaws", function (error, movie) { + expect(error).toBeNull; + expect(movie.title).toEqual("Jaws"); + expect(movie.release_date).toEqual("1975-06-19"); + expect(movie.inventory).toEqual(6); + expect(movie.overview).toEqual("An insatiable great white shark terrorizes the townspeople of Amity Island, The police chief, an oceanographer and a grizzled shark hunter seek to destroy the bloodthirsty beast."); + done(); + }); + }); + }); + + describe('#find_customers_by_title', function() { + it('should return an Array of Customer instances', function(done) { + Movies.find_customers_by_title("Jaws", function(error, customers) { + expect(error).toBeNull; + expect(customers).toEqual(jasmine.any(Array)); + expect(customers[0]).toEqual(jasmine.any(Customers)); + done(); + }); + }); + + it('should return only name, phone, and account_credit info for each customer', function(done) { + Movies.find_customers_by_title("Jaws", function(error, customers) { + expect(customers[0].name).toNotBe(undefined); + expect(customers[0].phone).toNotBe(undefined); + expect(customers[0].account_credit).toNotBe(undefined); + done(); + }); + }); + }); + + describe('#find_customers_by_history', function() { + it('should return an Array of Customer instances', function(done) { + Movies.find_customers_by_history("Jaws", "name", function(error, customers) { + expect(error).toBeNull; + expect(customers).toEqual(jasmine.any(Array)); + expect(customers[0]).toEqual(jasmine.any(Customers)); + done(); + }); + }); + + it('can order by name', function(done) { + Movies.find_customers_by_history("Psycho", "name", function(error, customers) { + expect(customers[0].id).toEqual(4); + expect(customers[customers.length-1].id).toEqual(1); + done(); + }); + }); + + it('can order by checkout_date', function(done) { + Movies.find_customers_by_history("Psycho", "checkout_date", function(error, customers) { + expect(customers[0].id).toEqual(4); + expect(customers[customers.length-1].id).toEqual(3); + done(); + }); + }); + + it('orders by id by default if an invalid sort column was given', function(done) { + Movies.find_customers_by_history("Psycho", "blahblahblah", function(error, customers) { + expect(customers[0].id).toEqual(1); + expect(customers[customers.length-1].id).toEqual(11); + done(); + }); + }); + }); +}); + diff --git a/spec/models/rentals.spec.js b/spec/models/rentals.spec.js new file mode 100644 index 000000000..f5b0c4bec --- /dev/null +++ b/spec/models/rentals.spec.js @@ -0,0 +1,124 @@ +var app = require("../../app"); +var db = app.get("db"); +var Rentals = require('../../models/rentals') + +describe('Rentals', function () { + + afterEach(function () { + // delete all the accounts I created + db.end() + }) + + describe('#available', function () { + it('should return number of rentals available', function (done) { + var movie_id = 33; + Rentals.available(movie_id, function (error, costumers) { + expect(error).toBeNull + expect(costumers).toEqual(9) + done() + }) + }) + + it('should return number of rentals available', function (done) { + var movie_id = 2; + Rentals.available(movie_id, function (error, result) { + expect(error).toBeNull + expect(result).toEqual(4) + done() + }) + }) + + it('should return number of rentals available', function (done) { + var movie_id = 25; + Rentals.available(movie_id, function (error, result) { + expect(error).toBeNull + expect(result).toEqual(5) + done() + }) + }) + }) + + describe('#find_customers_by_title', function () { + it('should return custumers info with a given title', function (done) { + var title = "Jaws"; + Rentals.find_customers_by_title(title, function (error, costumers) { + expect(error).toBeNull + expect(Array.isArray(costumers)).toEqual(true) + expect(typeof costumers[0]).toEqual('object') + expect(typeof costumers[0].name).toEqual('string') + expect(typeof costumers[0].registered_at).toEqual('string') + expect(typeof costumers[0].address).toEqual('string') + expect(typeof costumers[0].city).toEqual('string') + expect(typeof costumers[0].state).toEqual('string') + expect(typeof costumers[0].postal_code).toEqual('string') + expect(typeof costumers[0].account_credit).toEqual('string') + + done() + }) + }) + + it('should return an empty array if no customers were found', function (done) { + var title = "Titanic"; + Rentals.find_customers_by_title(title, function (error, costumers) { + expect(error).toBeNull + expect(Array.isArray(costumers)).toEqual(true) + expect(costumers).toEqual([]) + done() + }) + }) + + it('should return an empty array if given a bad request', function (done) { + var title = "melissa"; + Rentals.find_customers_by_title(title, function (error, costumers) { + expect(error).toBeNull + expect(Array.isArray(costumers)).toEqual(true) + expect(costumers).toEqual([]) + done() + }) + }) + }) + + describe('#mark_as_checkout', function () { + it('should return number of rentals mark_as_checkout', function (done) { + var movie = 'Raging Bull'; + var costumer_id = 102; + + Rentals.mark_as_checkout(movie, costumer_id, function (error, info) { + expect(error).toBeNull + expect(Array.isArray(info)).toEqual(true) + // expect(typeof info[0].id).toEqual('number') + // expect(info[0][movie_id]).toEqual(51) + // expect(info[0][customer_id).toEqual(102) + // expect(info[0][status).toEqual('rented') + done() + }) + }) + + it('should return number of rentals mark_as_checkout', function (done) { + var movie = "The Great Escape"; + var costumer_id = 42; + Rentals.mark_as_checkout(movie, costumer_id, function (error, info) { + expect(error).toBeNull + expect(Array.isArray(info)).toEqual(true) + done() + }) + }) + }) + + describe('#get_overdue', function () { + it('should return all the of rentals get_overdue', function (done) { + Rentals.get_overdue(function (error, info) { + expect(error).toBeNull + expect(Array.isArray(info)).toEqual(true) + expect(typeof info[0]).toEqual("object") + expect(typeof info[0].name).toEqual("string") + expect(typeof info[0].title).toEqual("string") + expect(typeof info[0].checkout_date).toEqual("string") + expect(typeof info[0].return_date).toEqual("string") + done() + }) + }) + + }) + +}); diff --git a/tasks/load_schema.js b/tasks/load_schema.js new file mode 100644 index 000000000..8becaa659 --- /dev/null +++ b/tasks/load_schema.js @@ -0,0 +1,11 @@ +var app = require("../app"); +var db = app.get("db"); + +db.setup.schema([], function(err, result) { + if (err) { + throw(new Error(err.message)); + } + + console.log("yay schema!"); + process.exit(); +}); diff --git a/tasks/seed.js b/tasks/seed.js new file mode 100644 index 000000000..a453897b7 --- /dev/null +++ b/tasks/seed.js @@ -0,0 +1,31 @@ +var app = require("../app"); +var db = app.get("db"); + +var data_movies = require('../db/seeds/movies.json'); +var data_customers = require('../db/seeds/customers.json'); +var data_history = require('../db/seeds/history.json'); + +for (var record of data_movies) { + db.movies.saveSync(record); +} + +for (var record of data_history) { + db.history.saveSync(record); +}; + +db.movies.find(function(err, res){ + for (var movie of res) { + for (i = 0; i < movie.inventory; i++) { + db.rentals.saveSync({movie_id: movie.id, status: 'available' }); + } + } +}); + +for (var record of data_customers) { + db.customers.saveSync(record); +} + +db.rentals.saveSync({id: [33, 9, 15], status: 'rented', customer_id: 2}) +db.rentals.saveSync({id: [2, 10, 21], status: 'rented', customer_id: 4}) + +process.exit( ); diff --git a/views/docs.ejs b/views/docs.ejs new file mode 100644 index 000000000..15d4a22f8 --- /dev/null +++ b/views/docs.ejs @@ -0,0 +1,13 @@ + + +
+