diff --git a/.gitignore b/.gitignore index 646ac519e..062fe0c62 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ .DS_Store node_modules/ +/coverage diff --git a/api/docs.json b/api/docs.json new file mode 100644 index 000000000..b347ba17e --- /dev/null +++ b/api/docs.json @@ -0,0 +1,135 @@ +{ + "links": { + "customers": "http://localhost:3000/customers", + "customer_show": "http://localhost/customers/:name", + "customer_sort": "http://localhost/customers/sort/:field?n=[limit]p=[offset]", + "customer_current": "http://localhost/customers/:id/current", + "customer_history": "http://localhost/customers/:id/history" + }, + "data": [{ + "type": "customers", + "id": "1", + "attributes": { + "name": "Shelley Rocha", + "registered_at": "Wed, 29 Apr 2015 07:54:14 -0700", + "address": "Ap #292-5216 Ipsum Rd.", + "city": "Hillsboro", + "state": "OR", + "postal_code": "24309", + "phone": "(322) 510-8695", + "account_credit": "13.15" + }, + "type": "customers_show", + "id": "1", + "attributes": { + "name": "Shelley Rocha", + "registered_at": "Wed, 29 Apr 2015 07:54:14 -0700", + "address": "Ap #292-5216 Ipsum Rd.", + "city": "Hillsboro", + "state": "OR", + "postal_code": "24309", + "phone": "(322) 510-8695", + "account_credit": "13.15" + }, + "type": "customers_sort", + "field": "name", + "n": "number", + "p": "offset", + "attributes": { + "name": "Shelley Rocha", + "registered_at": "Wed, 29 Apr 2015 07:54:14 -0700", + "address": "Ap #292-5216 Ipsum Rd.", + "city": "Hillsboro", + "state": "OR", + "postal_code": "24309", + "phone": "(322) 510-8695", + "account_credit": "13.15" + }, + "type": "customers_current", + "id": "1", + "attributes": { + "id": 29, + "title": "The Shining", + "overview": "Jack Torrance accepts a caretaker job at the Overlook Hotel, where he, along with his wife Wendy and their son Danny, must live isolated from the rest of the world for the winter. But they aren't prepared for the madness that lurks within.", + "release_date": "1980-05-22", + "inventory": 3 + }, + "type": "customers_history", + "id": "1", + "attributes": { + "id": 29, + "title": "The Shining", + "overview": "Jack Torrance accepts a caretaker job at the Overlook Hotel, where he, along with his wife Wendy and their son Danny, must live isolated from the rest of the world for the winter. But they aren't prepared for the madness that lurks within.", + "release_date": "1980-05-22", + "inventory": 3 + } + }] +} + +{ + "links": { + "customers": "http://localhost:3000/movies", + "customer_show": "http://localhost/movies/sort/:field?n=[limit]p=offset", + "customer_sort": "http://localhost/movies/:title/current", + "customer_current": "http://localhost/movies/:title/history", + "customer_history": "http://localhost/movies/:id/history" + }, + "data": [{ + "type": "customers", + "id": "1", + "attributes": { + "name": "Shelley Rocha", + "registered_at": "Wed, 29 Apr 2015 07:54:14 -0700", + "address": "Ap #292-5216 Ipsum Rd.", + "city": "Hillsboro", + "state": "OR", + "postal_code": "24309", + "phone": "(322) 510-8695", + "account_credit": "13.15" + }, + "type": "customers_show", + "id": "1", + "attributes": { + "name": "Shelley Rocha", + "registered_at": "Wed, 29 Apr 2015 07:54:14 -0700", + "address": "Ap #292-5216 Ipsum Rd.", + "city": "Hillsboro", + "state": "OR", + "postal_code": "24309", + "phone": "(322) 510-8695", + "account_credit": "13.15" + }, + "type": "customers_sort", + "field": "name", + "n": "number", + "p": "offset", + "attributes": { + "name": "Shelley Rocha", + "registered_at": "Wed, 29 Apr 2015 07:54:14 -0700", + "address": "Ap #292-5216 Ipsum Rd.", + "city": "Hillsboro", + "state": "OR", + "postal_code": "24309", + "phone": "(322) 510-8695", + "account_credit": "13.15" + }, + "type": "customers_current", + "id": "1", + "attributes": { + "id": 29, + "title": "The Shining", + "overview": "Jack Torrance accepts a caretaker job at the Overlook Hotel, where he, along with his wife Wendy and their son Danny, must live isolated from the rest of the world for the winter. But they aren't prepared for the madness that lurks within.", + "release_date": "1980-05-22", + "inventory": 3 + }, + "type": "customers_history", + "id": "1", + "attributes": { + "id": 29, + "title": "The Shining", + "overview": "Jack Torrance accepts a caretaker job at the Overlook Hotel, where he, along with his wife Wendy and their son Danny, must live isolated from the rest of the world for the winter. But they aren't prepared for the madness that lurks within.", + "release_date": "1980-05-22", + "inventory": 3 + } + }] +} diff --git a/app.js b/app.js index f0579b1dc..cf354cb80 100644 --- a/app.js +++ b/app.js @@ -1,34 +1,67 @@ -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 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 routes = require('./routes/index') +var app = express() -var routes = require('./routes/index'); +var massive = require("massive") -var app = express(); +var app = module.exports = express() +var connectionString = "postgres://localhost/video_store" + +// connect to Massive and get the db instance. You can safely use the +// convenience sync method here because its on app load +// you can also use loadSync - it's an alias +var massiveInstance = massive.connectSync({connectionString : connectionString}) + +// Set a reference to the massive instance on Express' app: +app.set('db', massiveInstance) // view engine setup -app.set('views', path.join(__dirname, 'views')); -app.set('view engine', 'jade'); +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(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'))) + +var indexRoutes = require('./routes/index') +// /zomg inside index route +app.use('/', indexRoutes) +app.use('/zomg', indexRoutes) +// customers +// GET +var customerRoutes = require('./routes/customers') +app.use('/customers', customerRoutes) +// POST - check-in and check-out +// needs id + +// rentals +// GET +var rentalsRoutes = require('./routes/rentals') +app.use('/rentals', rentalsRoutes) +// title +// needs customer id, movie title -app.use('/', routes); +// movies +// GET +var moviesRoutes = require('./routes/movies') +app.use('/movies', moviesRoutes) +// needs title // catch 404 and forward to error handler app.use(function(req, res, next) { - var err = new Error('Not Found'); - err.status = 404; - next(err); -}); + var err = new Error('Not Found') + err.status = 404 + next(err) +}) // error handlers @@ -36,23 +69,23 @@ app.use(function(req, res, next) { // will print stacktrace if (app.get('env') === 'development') { app.use(function(err, req, res, next) { - res.status(err.status || 500); + 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.status(err.status || 500) res.render('error', { message: err.message, error: {} - }); -}); + }) +}) -module.exports = app; +module.exports = app diff --git a/controllers/customers.js b/controllers/customers.js new file mode 100644 index 000000000..f3d3e83b6 --- /dev/null +++ b/controllers/customers.js @@ -0,0 +1,70 @@ +var Customer = require('../models/customer') +var Rental = require('../models/rental') + +CustomersController = { + locals: { + title: 'CUSTOMERS CUSTOMERS CUSTOMERS' + }, + + getCustomers: function(req, res) { + Customer.all (function (error, customers) { + if (error) { + var err = new Error("Error retrieving customer list:\n" + error.message); + err.status = 500; + next(err); + } else { + res.json(customers) + } + }) + }, + + getCustomersShow: function (req, res, next) { + Customer.find(req.params.name, function(error, customer) { + if(error) { + var err = new Error("No such customer"); + err.status = 404; + next(err); + } else { + res.json(customer) + } + }) + }, + + getCustomersSort: function (req, res, next) { + Customer.sort(req.params.field, req.query.n, req.query.p, function(error, customer) { + if (error) { + var err = new Error("Error retrieving customer list:\n" + error.message); + err.status = 500; + next(err); + } else { + res.json(customer) + } + }) + }, + + getRentalsCurrent: function (req, res, next) { + Rental.findCurrent(req.params.id, function (error, movie) { + if (error) { + var err = new Error("Error retrieving current rentals:\n" + error.message); + err.status = 500; + next(err); + } else { + res.json(movie) + } + }) + }, + + getRentalsHistory: function (req, res, next) { + Rental.findHistory(req.params.id, function (error, movie) { + if (error) { + var err = new Error("Error retrieving rental history:\n" + error.message); + err.status = 500; + next(err); + } else { + res.json(movie) + } + }) + } +} + +module.exports = CustomersController diff --git a/controllers/index.js b/controllers/index.js new file mode 100644 index 000000000..d86e99224 --- /dev/null +++ b/controllers/index.js @@ -0,0 +1,15 @@ +IndexController = { + locals: { + title: 'INDEX INDEX INDEX' + }, + + getIndex: function(req, res) { + res.status(200).json({whatevs: 'whatevs!!!'}) + }, + + getZomg: function(req, res) { + res.status(200).json({it_works: 'Yeah MF!!!'}) + } +} + +module.exports = IndexController diff --git a/controllers/movies.js b/controllers/movies.js new file mode 100644 index 000000000..836833c62 --- /dev/null +++ b/controllers/movies.js @@ -0,0 +1,70 @@ +var Movie = require('../models/movie') +var Rental = require('../models/rental') + +MoviesController = { + locals: { + title: 'MOVIES MOVIES MOVIES' + }, + + getMovies: function(req, res) { + Movie.all (function (error, movies) { + if (error) { + var err = new Error("Error retrieving movie list:\n" + error.message); + err.status = 500; + next(err); + } else { + res.json(movies) + } + }) + }, + + getMoviesShow: function(req, res, next) { + Movie.find(req.params.title, function(error, movie) { + if(error) { + var err = new Error("No such movies"); + err.status = 404; + next(err); + } else { + res.json(movie) + } + }) + }, + + getMoviesSort: function(req, res) { + Movie.sort(req.params.field, req.query.n, req.query.p, function(error, movie) { + if (error) { + var err = new Error("Error retrieving movie list:\n" + error.message); + err.status = 500; + next(err); + } else { + res.json(movie) + } + }) + }, + + getMoviesCurrent: function (req, res, next) { + Rental.findCurrentMovies(req.params.title, function (error, customer) { + if (error) { + var err = new Error("Error retrieving current customers:\n" + error.message); + err.status = 500; + next(err); + } else { + res.json(customer) + } + }) + }, + + getMoviesHistory: function (req, res, next) { + Rental.findHistoryMovies(req.params.title, function (error, customers) { + if (error) { + var err = new Error("Error retrieving rental history:\n" + error.message); + err.status = 500; + next(err); + } else { + res.json(customers) + } + }) + } +} + +module.exports = MoviesController diff --git a/controllers/rentals.js b/controllers/rentals.js new file mode 100644 index 000000000..81e077cd3 --- /dev/null +++ b/controllers/rentals.js @@ -0,0 +1,81 @@ +var Rental = require('../models/rental') + +RentalsController = { + locals: { + title: 'RENTALS RENTALS RENTALS' + }, + + getRentals: function(req, res, next) { + Rental.all (function (error, movies) { + if (error) { + var err = new Error("Error retrieving rental list:\n" + error.message); + err.status = 500; + next(err); + } else { + res.json(movies) + } + }) + }, + + getRentalsShow: function(req, res, next) { + Rental.find (req.params.title, function(error, rental) { + if (error) { + var err = new Error("No such rentals"); + err.status = 404; + next(err); + } else { + res.json(rental) + } + }) + }, + + getRentalsCustomers:function(req, res, next) { + Rental.find_customers (req.params.title, function(error, customer) { + if (error) { + var err = new Error("No such customers"); + err.status = 404; + next(err); + } else { + res.json(customer) + } + }) + }, + + postCheckout:function(req, res, next) { + Rental.checkout (req.params.title, req.body.customer_id, function(error, movie) { + if (error) { + var err = new Error("Can't rent out"); + err.status = 404; + next(error); + res.json({}) + } else { + res.json({checkout: "Success, you checked out, you fancy!"}) + } + }) + }, + + putCheckin: function(req, res, next) { + Rental.checkin (req.params.title, req.body.customer_id, function(error, movie) { + if (error) { + var err = new Error("Can't return, try again later"); + err.status = 404; + next(err); + } else { + res.json({checkin: "Success, you checked that movie in, yo!"}) + } + }) + }, + + overdue: function (req, res) { + Rental.overdueRental (function (error, rental) { + if (error) { + var err = new Error("Did not work :( "); + err.status = 404; + } else { + res.json(rental) + } + }) + } +} + +module.exports = RentalsController diff --git a/db/current.sql b/db/current.sql new file mode 100644 index 000000000..b7dc47bce --- /dev/null +++ b/db/current.sql @@ -0,0 +1,7 @@ +SELECT rentals.movie_id, rentals.customer_id, customers.id, movies.* +FROM rentals +INNER JOIN customers +ON rentals.customer_id=customers.id +INNER JOIN movies +ON movies.id=rentals.movie_id +WHERE rentals.customer_id=$1 AND rentals.status=true; diff --git a/db/customer_rental_history.sql b/db/customer_rental_history.sql new file mode 100644 index 000000000..bd77a561c --- /dev/null +++ b/db/customer_rental_history.sql @@ -0,0 +1,8 @@ +SELECT movies.id, rentals.movie_id, rentals.customer_id, rentals.status, customers.* +FROM rentals +INNER JOIN movies +ON movies.id=rentals.movie_id +INNER JOIN customers +ON rentals.customer_id=customers.id +WHERE movies.title=$1 +ORDER BY customers.name; diff --git a/db/find_current_rentals.sql b/db/find_current_rentals.sql new file mode 100644 index 000000000..1e9e5fbdc --- /dev/null +++ b/db/find_current_rentals.sql @@ -0,0 +1,8 @@ + +SELECT movies.id, rentals.movie_id, rentals.customer_id, rentals.status, customers.* +FROM rentals +INNER JOIN movies +ON movies.id=rentals.movie_id +INNER JOIN customers +ON rentals.customer_id=customers.id +WHERE movies.title=$1 AND rentals.status=true; diff --git a/db/history.sql b/db/history.sql new file mode 100644 index 000000000..9c4f8671d --- /dev/null +++ b/db/history.sql @@ -0,0 +1,8 @@ +SELECT rentals.movie_id, rentals.customer_id, customers.id, movies.* +FROM rentals +INNER JOIN customers +ON rentals.customer_id=customers.id +INNER JOIN movies +ON movies.id=rentals.movie_id +WHERE rentals.customer_id=$1 +ORDER BY to_date(rentals.checkout_date, 'Dy Mon DD YYYY'); diff --git a/db/overdue_rentals.sql b/db/overdue_rentals.sql new file mode 100644 index 000000000..0055959f6 --- /dev/null +++ b/db/overdue_rentals.sql @@ -0,0 +1,7 @@ +SELECT rentals.status, rentals.customer_id, rentals.return_date, customers.*, rentals.checkout_date, movies.title +FROM rentals +INNER JOIN movies +ON rentals.movie_id=movies.id +INNER JOIN customers +ON customers.id=rentals.customer_id +WHERE rentals.status=true AND to_date(return_date, 'Dy Mon DD YYYY')