From ee5e375402b546e48684cb433e14420c22d181ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= Date: Fri, 13 May 2016 09:44:44 +0200 Subject: [PATCH 1/6] Added mongoose, seed data and fetch + display data --- app.js | 3 ++ models/product.js | 11 +++++ package.json | 1 + routes/index.js | 10 ++++- seed/product-seeder.js | 52 +++++++++++++++++++++++ views/shop/index.hbs | 95 +++++++----------------------------------- 6 files changed, 92 insertions(+), 80 deletions(-) create mode 100644 models/product.js create mode 100644 seed/product-seeder.js diff --git a/app.js b/app.js index bceda91..5f1424a 100644 --- a/app.js +++ b/app.js @@ -5,11 +5,14 @@ var logger = require('morgan'); var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var expressHbs = require('express-handlebars'); +var mongoose = require('mongoose'); var routes = require('./routes/index'); var app = express(); +mongoose.connect('localhost:27017/shopping'); + // view engine setup app.engine('.hbs', expressHbs({defaultLayout: 'layout', extname: '.hbs'})); app.set('view engine', '.hbs'); diff --git a/models/product.js b/models/product.js new file mode 100644 index 0000000..deffcbd --- /dev/null +++ b/models/product.js @@ -0,0 +1,11 @@ +var mongoose = require('mongoose'); +var Schema = mongoose.Schema; + +var schema = new Schema({ + imagePath: {type: String, required: true}, + title: {type: String, required: true}, + description: {type: String, required: true}, + price: {type: Number, required: true} +}); + +module.exports = mongoose.model('Product', schema); \ No newline at end of file diff --git a/package.json b/package.json index 5a7e7d4..1adad53 100644 --- a/package.json +++ b/package.json @@ -12,6 +12,7 @@ "express": "~4.13.1", "express-handlebars": "^3.0.0", "hbs": "~3.1.0", + "mongoose": "^4.4.16", "morgan": "~1.6.1", "serve-favicon": "~2.3.0" } diff --git a/routes/index.js b/routes/index.js index c43dcf8..37b363e 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,9 +1,17 @@ var express = require('express'); var router = express.Router(); +var Product = require('../models/product'); /* GET home page. */ router.get('/', function(req, res, next) { - res.render('shop/index', { title: 'Shopping Cart' }); + Product.find(function(err, docs) { + var productChunks = []; + var chunkSize = 3; + for (var i = 0; i < docs.length; i += chunkSize) { + productChunks.push(docs.slice(i, i + chunkSize)); + } + res.render('shop/index', { title: 'Shopping Cart', products: productChunks }); + }); }); module.exports = router; diff --git a/seed/product-seeder.js b/seed/product-seeder.js new file mode 100644 index 0000000..cba96a2 --- /dev/null +++ b/seed/product-seeder.js @@ -0,0 +1,52 @@ +var Product = require('../models/product'); + +var mongoose = require('mongoose'); + +mongoose.connect('localhost:27017/shopping'); + +var products = [ + new Product({ + imagePath: 'https://upload.wikimedia.org/wikipedia/en/5/5e/Gothiccover.png', + title: 'Gothic Video Game', + description: 'Awesome Game!!!!', + price: 10 + }), + new Product({ + imagePath: 'http://eu.blizzard.com/static/_images/games/wow/wallpapers/wall2/wall2-1440x900.jpg', + title: 'World of Warcraft Video Game', + description: 'Also awesome? But of course it was better in vanilla ...', + price: 20 + }), + new Product({ + imagePath: 'https://support.activision.com/servlet/servlet.FileDownload?file=00PU000000Rq6tz', + title: 'Call of Duty Video Game', + description: 'Meh ... nah, it\'s okay I guess', + price: 40 + }), + new Product({ + imagePath: 'https://pmcdeadline2.files.wordpress.com/2014/02/minecraft__140227211000.jpg', + title: 'Minecraft Video Game', + description: 'Now that is super awesome!', + price: 15 + }), + new Product({ + imagePath: 'https://d1r7xvmnymv7kg.cloudfront.net/sites_products/darksouls3/assets/img/DARKSOUL_facebook_mini.jpg', + title: 'Dark Souls 3 Video Game', + description: 'I died!', + price: 50 + }) +]; + +var done = 0; +for (var i = 0; i < products.length; i++) { + products[i].save(function(err, result) { + done++; + if (done === products.length) { + exit(); + } + }); +} + +function exit() { + mongoose.disconnect(); +} \ No newline at end of file diff --git a/views/shop/index.hbs b/views/shop/index.hbs index 8530f63..8651c4f 100644 --- a/views/shop/index.hbs +++ b/views/shop/index.hbs @@ -1,82 +1,19 @@ -
-
-
- ... -
-

Thumbnail label

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit. A consequuntur debitis delectus deserunt dolore dolorem eum expedita, inventore nobis omnis perferendis possimus quas repellat soluta, sunt voluptate, voluptates! Assumenda, nulla?

-
-
$12
- Button +{{# each products }} +
+ {{# each this }} +
+
+ ... +
+

{{ this.title }}

+

{{ this.description }}

+
+
${{ this.price }}
+ Add to Shopping Cart +
+
-
+ {{/each}}
-
-
- ... -
-

Thumbnail label

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit. A consequuntur debitis delectus deserunt dolore dolorem eum expedita, inventore nobis omnis perferendis possimus quas repellat soluta, sunt voluptate, voluptates! Assumenda, nulla?

-
-
$12
- Button -
-
-
-
-
-
- ... -
-

Thumbnail label

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit. A consequuntur debitis delectus deserunt dolore dolorem eum expedita, inventore nobis omnis perferendis possimus quas repellat soluta, sunt voluptate, voluptates! Assumenda, nulla?

-
-
$12
- Button -
-
-
-
-
-
-
-
- ... -
-

Thumbnail label

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit. A consequuntur debitis delectus deserunt dolore dolorem eum expedita, inventore nobis omnis perferendis possimus quas repellat soluta, sunt voluptate, voluptates! Assumenda, nulla?

-
-
$12
- Button -
-
-
-
-
-
- ... -
-

Thumbnail label

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit. A consequuntur debitis delectus deserunt dolore dolorem eum expedita, inventore nobis omnis perferendis possimus quas repellat soluta, sunt voluptate, voluptates! Assumenda, nulla?

-
-
$12
- Button -
-
-
-
-
-
- ... -
-

Thumbnail label

-

Lorem ipsum dolor sit amet, consectetur adipisicing elit. A consequuntur debitis delectus deserunt dolore dolorem eum expedita, inventore nobis omnis perferendis possimus quas repellat soluta, sunt voluptate, voluptates! Assumenda, nulla?

-
-
$12
- Button -
-
-
-
-
\ No newline at end of file +{{/each}} \ No newline at end of file From 823a1893747b999fcd225b87dd8c2bddfe18f48b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= Date: Thu, 26 May 2016 12:56:01 +0200 Subject: [PATCH 2/6] Addes user signup, signin, route protection, validation --- app.js | 17 +++++++++ config/passport.js | 76 +++++++++++++++++++++++++++++++++++++++ models/user.js | 18 ++++++++++ package.json | 7 ++++ routes/index.js | 19 +++++----- routes/user.js | 58 ++++++++++++++++++++++++++++++ views/partials/header.hbs | 14 +++++--- views/user/profile.hbs | 1 + views/user/signin.hbs | 24 +++++++++++++ views/user/signup.hbs | 24 +++++++++++++ 10 files changed, 245 insertions(+), 13 deletions(-) create mode 100644 config/passport.js create mode 100644 models/user.js create mode 100644 routes/user.js create mode 100644 views/user/profile.hbs create mode 100644 views/user/signin.hbs create mode 100644 views/user/signup.hbs diff --git a/app.js b/app.js index 5f1424a..26001a0 100644 --- a/app.js +++ b/app.js @@ -6,12 +6,18 @@ var cookieParser = require('cookie-parser'); var bodyParser = require('body-parser'); var expressHbs = require('express-handlebars'); var mongoose = require('mongoose'); +var session = require('express-session'); +var passport = require('passport'); +var flash = require('connect-flash'); +var validator = require('express-validator'); var routes = require('./routes/index'); +var userRoutes = require('./routes/user'); var app = express(); mongoose.connect('localhost:27017/shopping'); +require('./config/passport'); // view engine setup app.engine('.hbs', expressHbs({defaultLayout: 'layout', extname: '.hbs'})); @@ -22,9 +28,20 @@ app.set('view engine', '.hbs'); app.use(logger('dev')); app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); +app.use(validator()); app.use(cookieParser()); +app.use(session({secret: 'mysupersecret', resave: false, saveUninitialized: false})); +app.use(flash()); +app.use(passport.initialize()); +app.use(passport.session()); app.use(express.static(path.join(__dirname, 'public'))); +app.use(function(req, res, next) { + res.locals.login = req.isAuthenticated(); + next(); +}); + +app.use('/user', userRoutes); app.use('/', routes); // catch 404 and forward to error handler diff --git a/config/passport.js b/config/passport.js new file mode 100644 index 0000000..cdd74be --- /dev/null +++ b/config/passport.js @@ -0,0 +1,76 @@ +var passport = require('passport'); +var User = require('../models/user'); +var LocalStrategy = require('passport-local').Strategy; + +passport.serializeUser(function (user, done) { + done(null, user.id); +}); + +passport.deserializeUser(function (id, done) { + User.findById(id, function (err, user) { + done(err, user); + }); +}); + +passport.use('local.signup', new LocalStrategy({ + usernameField: 'email', + passwordField: 'password', + passReqToCallback: true +}, function (req, email, password, done) { + req.checkBody('email', 'Invalid email').notEmpty().isEmail(); + req.checkBody('password', 'Invalid password').notEmpty().isLength({min:4}); + var errors = req.validationErrors(); + if (errors) { + var messages = []; + errors.forEach(function(error) { + messages.push(error.msg); + }); + return done(null, false, req.flash('error', messages)); + } + User.findOne({'email': email}, function (err, user) { + if (err) { + return done(err); + } + if (user) { + return done(null, false, {message: 'Email is already in use.'}); + } + var newUser = new User(); + newUser.email = email; + newUser.password = newUser.encryptPassword(password); + newUser.save(function(err, result) { + if (err) { + return done(err); + } + return done(null, newUser); + }); + }); +})); + +passport.use('local.signin', new LocalStrategy({ + usernameField: 'email', + passwordField: 'password', + passReqToCallback: true +}, function(req, email, password, done) { + req.checkBody('email', 'Invalid email').notEmpty().isEmail(); + req.checkBody('password', 'Invalid password').notEmpty(); + var errors = req.validationErrors(); + if (errors) { + var messages = []; + errors.forEach(function(error) { + messages.push(error.msg); + }); + return done(null, false, req.flash('error', messages)); + } + User.findOne({'email': email}, function (err, user) { + if (err) { + return done(err); + } + if (!user) { + return done(null, false, {message: 'No user found.'}); + } + if (!user.validPassword(password)) { + return done(null, false, {message: 'Wrong password.'}); + } + return done(null, user); + }); +})); \ No newline at end of file diff --git a/models/user.js b/models/user.js new file mode 100644 index 0000000..c03c84b --- /dev/null +++ b/models/user.js @@ -0,0 +1,18 @@ +var mongoose = require('mongoose'); +var Schema = mongoose.Schema; +var bcrypt = require('bcrypt-nodejs'); + +var userSchema = new Schema({ + email: {type: String, required: true}, + password: {type: String, required: true} +}); + +userSchema.methods.encryptPassword = function(password) { + return bcrypt.hashSync(password, bcrypt.genSaltSync(5), null); +}; + +userSchema.methods.validPassword = function(password) { + return bcrypt.compareSync(password, this.password); +}; + +module.exports = mongoose.model('User', userSchema); \ No newline at end of file diff --git a/package.json b/package.json index 1adad53..43b3488 100644 --- a/package.json +++ b/package.json @@ -6,14 +6,21 @@ "start": "node ./bin/www" }, "dependencies": { + "bcrypt-nodejs": "0.0.3", "body-parser": "~1.13.2", + "connect-flash": "^0.1.1", "cookie-parser": "~1.3.5", + "csurf": "^1.8.3", "debug": "~2.2.0", "express": "~4.13.1", "express-handlebars": "^3.0.0", + "express-session": "^1.13.0", + "express-validator": "^2.20.5", "hbs": "~3.1.0", "mongoose": "^4.4.16", "morgan": "~1.6.1", + "passport": "^0.3.2", + "passport-local": "^1.0.0", "serve-favicon": "~2.3.0" } } diff --git a/routes/index.js b/routes/index.js index 37b363e..5d2242c 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,17 +1,18 @@ var express = require('express'); var router = express.Router(); + var Product = require('../models/product'); /* GET home page. */ -router.get('/', function(req, res, next) { - Product.find(function(err, docs) { - var productChunks = []; - var chunkSize = 3; - for (var i = 0; i < docs.length; i += chunkSize) { - productChunks.push(docs.slice(i, i + chunkSize)); - } - res.render('shop/index', { title: 'Shopping Cart', products: productChunks }); - }); +router.get('/', function (req, res, next) { + Product.find(function (err, docs) { + var productChunks = []; + var chunkSize = 3; + for (var i = 0; i < docs.length; i += chunkSize) { + productChunks.push(docs.slice(i, i + chunkSize)); + } + res.render('shop/index', {title: 'Shopping Cart', products: productChunks}); + }); }); module.exports = router; diff --git a/routes/user.js b/routes/user.js new file mode 100644 index 0000000..d00dbe1 --- /dev/null +++ b/routes/user.js @@ -0,0 +1,58 @@ +var express = require('express'); +var router = express.Router(); +var csrf = require('csurf'); +var passport = require('passport'); + +var csrfProtection = csrf(); +router.use(csrfProtection); + +router.get('/profile', isLoggedIn, function (req, res, next) { + res.render('user/profile'); +}); + +router.get('/logout', isLoggedIn, function (req, res, next) { + req.logout(); + res.redirect('/'); +}); + +router.use('/', notLoggedIn, function(req, res, next) { + next(); +}); + +router.get('/signup', function (req, res, next) { + var messages = req.flash('error'); + res.render('user/signup', {csrfToken: req.csrfToken(), messages: messages, hasErrors: messages.length > 0}); +}); + +router.post('/signup', passport.authenticate('local.signup', { + successRedirect: '/user/profile', + failureRedirect: '/user/signup', + failureFlash: true +})); + +router.get('/signin', function (req, res, next) { + var messages = req.flash('error'); + res.render('user/signin', {csrfToken: req.csrfToken(), messages: messages, hasErrors: messages.length > 0}); +}); + +router.post('/signin', passport.authenticate('local.signin', { + successRedirect: '/user/profile', + failureRedirect: '/user/signin', + failureFlash: true +})); + +module.exports = router; + +function isLoggedIn(req, res, next) { + if (req.isAuthenticated()) { + return next(); + } + res.redirect('/'); +} + +function notLoggedIn(req, res, next) { + if (!req.isAuthenticated()) { + return next(); + } + res.redirect('/'); +} \ No newline at end of file diff --git a/views/partials/header.hbs b/views/partials/header.hbs index b32f408..1879ad9 100644 --- a/views/partials/header.hbs +++ b/views/partials/header.hbs @@ -8,7 +8,7 @@ - Brand + Brand
@@ -18,9 +18,15 @@ diff --git a/views/user/profile.hbs b/views/user/profile.hbs new file mode 100644 index 0000000..9749f97 --- /dev/null +++ b/views/user/profile.hbs @@ -0,0 +1 @@ +

User Profile

\ No newline at end of file diff --git a/views/user/signin.hbs b/views/user/signin.hbs new file mode 100644 index 0000000..b2175a4 --- /dev/null +++ b/views/user/signin.hbs @@ -0,0 +1,24 @@ +
+
+

Sign In

+ {{#if hasErrors}} +
+ {{# each messages }} +

{{this}}

+ {{/each}} +
+ {{/if}} +
+
+ + +
+
+ + +
+ + +
+
+
\ No newline at end of file diff --git a/views/user/signup.hbs b/views/user/signup.hbs new file mode 100644 index 0000000..34a5639 --- /dev/null +++ b/views/user/signup.hbs @@ -0,0 +1,24 @@ +
+
+

Sign Up

+ {{#if hasErrors}} +
+ {{# each messages }} +

{{this}}

+ {{/each}} +
+ {{/if}} +
+
+ + +
+
+ + +
+ + +
+
+
\ No newline at end of file From 97ec0b4133828f9d8ecaf2c2347056751c261b5b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= Date: Tue, 14 Jun 2016 10:20:26 +0200 Subject: [PATCH 3/6] Added cart/ session functionality --- app.js | 10 ++++++++- models/cart.js | 24 ++++++++++++++++++++++ package.json | 1 + routes/index.js | 24 ++++++++++++++++++++++ views/partials/header.hbs | 7 ++++++- views/shop/index.hbs | 2 +- views/shop/shopping-cart.hbs | 40 ++++++++++++++++++++++++++++++++++++ 7 files changed, 105 insertions(+), 3 deletions(-) create mode 100644 models/cart.js create mode 100644 views/shop/shopping-cart.hbs diff --git a/app.js b/app.js index 26001a0..1fb054e 100644 --- a/app.js +++ b/app.js @@ -10,6 +10,7 @@ var session = require('express-session'); var passport = require('passport'); var flash = require('connect-flash'); var validator = require('express-validator'); +var MongoStore = require('connect-mongo')(session); var routes = require('./routes/index'); var userRoutes = require('./routes/user'); @@ -30,7 +31,13 @@ app.use(bodyParser.json()); app.use(bodyParser.urlencoded({ extended: false })); app.use(validator()); app.use(cookieParser()); -app.use(session({secret: 'mysupersecret', resave: false, saveUninitialized: false})); +app.use(session({ + secret: 'mysupersecret', + resave: false, + saveUninitialized: false, + store: new MongoStore({ mongooseConnection: mongoose.connection }), + cookie: { maxAge: 180 * 60 * 1000 } +})); app.use(flash()); app.use(passport.initialize()); app.use(passport.session()); @@ -38,6 +45,7 @@ app.use(express.static(path.join(__dirname, 'public'))); app.use(function(req, res, next) { res.locals.login = req.isAuthenticated(); + res.locals.session = req.session; next(); }); diff --git a/models/cart.js b/models/cart.js new file mode 100644 index 0000000..79d850f --- /dev/null +++ b/models/cart.js @@ -0,0 +1,24 @@ +module.exports = function Cart(oldCart) { + this.items = oldCart.items || {}; + this.totalQty = oldCart.totalQty || 0; + this.totalPrice = oldCart.totalPrice || 0; + + this.add = function(item, id) { + var storedItem = this.items[id]; + if (!storedItem) { + storedItem = this.items[id] = {item: item, qty: 0, price: 0}; + } + storedItem.qty++; + storedItem.price = storedItem.item.price * storedItem.qty; + this.totalQty++; + this.totalPrice += storedItem.item.price; + }; + + this.generateArray = function() { + var arr = []; + for (var id in this.items) { + arr.push(this.items[id]); + } + return arr; + }; +}; \ No newline at end of file diff --git a/package.json b/package.json index 43b3488..1f61461 100644 --- a/package.json +++ b/package.json @@ -9,6 +9,7 @@ "bcrypt-nodejs": "0.0.3", "body-parser": "~1.13.2", "connect-flash": "^0.1.1", + "connect-mongo": "^1.2.0", "cookie-parser": "~1.3.5", "csurf": "^1.8.3", "debug": "~2.2.0", diff --git a/routes/index.js b/routes/index.js index 5d2242c..05b2c5a 100644 --- a/routes/index.js +++ b/routes/index.js @@ -1,5 +1,6 @@ var express = require('express'); var router = express.Router(); +var Cart = require('../models/cart'); var Product = require('../models/product'); @@ -15,4 +16,27 @@ router.get('/', function (req, res, next) { }); }); +router.get('/add-to-cart/:id', function(req, res, next) { + var productId = req.params.id; + var cart = new Cart(req.session.cart ? req.session.cart : {}); + + Product.findById(productId, function(err, product) { + if (err) { + return res.redirect('/'); + } + cart.add(product, product.id); + req.session.cart = cart; + console.log(req.session.cart); + res.redirect('/'); + }); +}); + +router.get('/shopping-cart', function(req, res, next) { + if (!req.session.cart) { + return res.render('shop/shopping-cart', {products: null}); + } + var cart = new Cart(req.session.cart); + res.render('shop/shopping-cart', {products: cart.generateArray(), totalPrice: cart.totalPrice}); +}); + module.exports = router; diff --git a/views/partials/header.hbs b/views/partials/header.hbs index 1879ad9..ea19abc 100644 --- a/views/partials/header.hbs +++ b/views/partials/header.hbs @@ -14,7 +14,12 @@
diff --git a/views/shop/shopping-cart.hbs b/views/shop/shopping-cart.hbs new file mode 100644 index 0000000..41abe82 --- /dev/null +++ b/views/shop/shopping-cart.hbs @@ -0,0 +1,40 @@ +{{# if products }} +
+
+
    + {{# each products }} +
  • + {{ this.qty }} + {{ this.item.title }} + ${{ this.price }} +
    + + +
    +
  • + {{/each}} +
+
+
+
+
+ Total: {{ totalPrice }} +
+
+
+
+
+ +
+
+{{ else }} +
+
+

No Items in Cart

+
+
+{{/if}} + From 834cc255ede7e7b0adef4c3b1bbc29e8cf2eecfb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= Date: Mon, 4 Jul 2016 17:12:59 +0200 Subject: [PATCH 4/6] Added Stripe functionality (anonymous) --- package.json | 3 +- public/javascripts/checkout.js | 38 ++++++++++++++++++++ routes/index.js | 38 +++++++++++++++++++- views/layouts/layout.hbs | 2 +- views/shop/checkout.hbs | 64 ++++++++++++++++++++++++++++++++++ views/shop/index.hbs | 8 +++++ views/shop/shopping-cart.hbs | 2 +- 7 files changed, 151 insertions(+), 4 deletions(-) create mode 100644 public/javascripts/checkout.js create mode 100644 views/shop/checkout.hbs diff --git a/package.json b/package.json index 1f61461..0cde66e 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "morgan": "~1.6.1", "passport": "^0.3.2", "passport-local": "^1.0.0", - "serve-favicon": "~2.3.0" + "serve-favicon": "~2.3.0", + "stripe": "^4.7.0" } } diff --git a/public/javascripts/checkout.js b/public/javascripts/checkout.js new file mode 100644 index 0000000..3a0d750 --- /dev/null +++ b/public/javascripts/checkout.js @@ -0,0 +1,38 @@ +Stripe.setPublishableKey('pk_test_Z8Rr8NHqJmYA48M2FGGmpL3N'); + +var $form = $('#checkout-form'); + +$form.submit(function (event) { + $('#charge-error').addClass('hidden'); + $form.find('button').prop('disabled', true); + Stripe.card.createToken({ + number: $('#card-number').val(), + cvc: $('#card-cvc').val(), + exp_month: $('#card-expiry-month').val(), + exp_year: $('#card-expiry-year').val(), + name: $('#card-name').val() + }, stripeResponseHandler); + return false; +}); + +function stripeResponseHandler(status, response) { + if (response.error) { // Problem! + + // Show the errors on the form + $('#charge-error').text(response.error.message); + $('#charge-error').removeClass('hidden'); + $form.find('button').prop('disabled', false); // Re-enable submission + + } else { // Token was created! + + // Get the token ID: + var token = response.id; + + // Insert the token into the form so it gets submitted to the server: + $form.append($('').val(token)); + + // Submit the form: + $form.get(0).submit(); + + } +} \ No newline at end of file diff --git a/routes/index.js b/routes/index.js index 05b2c5a..2533777 100644 --- a/routes/index.js +++ b/routes/index.js @@ -6,13 +6,14 @@ var Product = require('../models/product'); /* GET home page. */ router.get('/', function (req, res, next) { + var successMsg = req.flash('success')[0]; Product.find(function (err, docs) { var productChunks = []; var chunkSize = 3; for (var i = 0; i < docs.length; i += chunkSize) { productChunks.push(docs.slice(i, i + chunkSize)); } - res.render('shop/index', {title: 'Shopping Cart', products: productChunks}); + res.render('shop/index', {title: 'Shopping Cart', products: productChunks, successMsg: successMsg, noMessages: !successMsg}); }); }); @@ -39,4 +40,39 @@ router.get('/shopping-cart', function(req, res, next) { res.render('shop/shopping-cart', {products: cart.generateArray(), totalPrice: cart.totalPrice}); }); +router.get('/checkout', function(req, res, next) { + if (!req.session.cart) { + return res.redirect('/shopping-cart'); + } + var cart = new Cart(req.session.cart); + var errMsg = req.flash('error')[0]; + res.render('shop/checkout', {total: cart.totalPrice, errMsg: errMsg, noError: !errMsg}); +}); + +router.post('/checkout', function(req, res, next) { + if (!req.session.cart) { + return res.redirect('/shopping-cart'); + } + var cart = new Cart(req.session.cart); + + var stripe = require("stripe")( + "sk_test_pweF4VBTjmx6qKT3WgvKUkO4" + ); + + stripe.charges.create({ + amount: cart.totalPrice * 100, + currency: "usd", + source: req.body.stripeToken, // obtained with Stripe.js + description: "Test Charge" + }, function(err, charge) { + if (err) { + req.flash('error', err.message); + return res.redirect('/checkout'); + } + req.flash('success', 'Successfully bought product!'); + req.session.cart = null; + res.redirect('/'); + }); +}); + module.exports = router; diff --git a/views/layouts/layout.hbs b/views/layouts/layout.hbs index 65b8cfb..f80db38 100644 --- a/views/layouts/layout.hbs +++ b/views/layouts/layout.hbs @@ -9,10 +9,10 @@ {{> header }} +
{{{body}}}
- diff --git a/views/shop/checkout.hbs b/views/shop/checkout.hbs new file mode 100644 index 0000000..7020ae6 --- /dev/null +++ b/views/shop/checkout.hbs @@ -0,0 +1,64 @@ +
+
+

Checkout

+

Your Total: ${{total}}

+
+ {{errMsg}} +
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+
+ + +
+
+
+
+
+
+ + +
+
+
+ +
+
+
+ + + \ No newline at end of file diff --git a/views/shop/index.hbs b/views/shop/index.hbs index 4dadc9b..07bfabb 100644 --- a/views/shop/index.hbs +++ b/views/shop/index.hbs @@ -1,3 +1,11 @@ +
+
+
+ {{ successMsg }} +
+
+
+ {{# each products }}
{{# each this }} diff --git a/views/shop/shopping-cart.hbs b/views/shop/shopping-cart.hbs index 41abe82..64bf06d 100644 --- a/views/shop/shopping-cart.hbs +++ b/views/shop/shopping-cart.hbs @@ -27,7 +27,7 @@
- + Checkout
{{ else }} From 67988f9a76725df15278f3dd9c7f9a4f1dc0ed17 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= Date: Thu, 14 Jul 2016 15:48:30 +0200 Subject: [PATCH 5/6] finished order storage --- models/order.js | 12 +++++++++ npm-debug.log | 45 ++++++++++++++++++++++++++++++++++ public/javascripts/checkout.js | 2 +- routes/index.js | 30 ++++++++++++++++++----- routes/user.js | 41 +++++++++++++++++++++++++------ views/shop/checkout.hbs | 4 +-- views/user/profile.hbs | 25 ++++++++++++++++++- views/user/signin.hbs | 1 + 8 files changed, 143 insertions(+), 17 deletions(-) create mode 100644 models/order.js create mode 100644 npm-debug.log diff --git a/models/order.js b/models/order.js new file mode 100644 index 0000000..0db45ec --- /dev/null +++ b/models/order.js @@ -0,0 +1,12 @@ +var mongoose = require('mongoose'); +var Schema = mongoose.Schema; + +var schema = new Schema({ + user: {type: Schema.Types.ObjectId, ref: 'User'}, + cart: {type: Object, required: true}, + address: {type: String, required: true}, + name: {type: String, required: true}, + paymentId: {type: String, required: true} +}); + +module.exports = mongoose.model('Order', schema); \ No newline at end of file diff --git a/npm-debug.log b/npm-debug.log new file mode 100644 index 0000000..d2ae859 --- /dev/null +++ b/npm-debug.log @@ -0,0 +1,45 @@ +0 info it worked if it ends with ok +1 verbose cli [ '/usr/local/bin/node', '/usr/local/bin/npm', 'start' ] +2 info using npm@3.9.0 +3 info using node@v5.6.0 +4 verbose run-script [ 'prestart', 'start', 'poststart' ] +5 info lifecycle shopping-cart@0.0.0~prestart: shopping-cart@0.0.0 +6 silly lifecycle shopping-cart@0.0.0~prestart: no script for prestart, continuing +7 info lifecycle shopping-cart@0.0.0~start: shopping-cart@0.0.0 +8 verbose lifecycle shopping-cart@0.0.0~start: unsafe-perm in lifecycle true +9 verbose lifecycle shopping-cart@0.0.0~start: PATH: /usr/local/lib/node_modules/npm/bin/node-gyp-bin:/Users/maximilianschwarzmuller/development/node/tutorials/youtube/shopping-cart/node_modules/.bin:/usr/local/bin:/users/maximilianschwarzmuller/.composer/vendor/laravel:/Applications/anaconda/bin:/usr/local/mysql/bin:/usr/local/bin:/usr/bin:/bin:/usr/sbin:/sbin:/usr/local/git/bin +10 verbose lifecycle shopping-cart@0.0.0~start: CWD: /Users/maximilianschwarzmuller/development/node/tutorials/youtube/shopping-cart +11 silly lifecycle shopping-cart@0.0.0~start: Args: [ '-c', 'node ./bin/www' ] +12 silly lifecycle shopping-cart@0.0.0~start: Returned: code: 1 signal: null +13 info lifecycle shopping-cart@0.0.0~start: Failed to exec start script +14 verbose stack Error: shopping-cart@0.0.0 start: `node ./bin/www` +14 verbose stack Exit status 1 +14 verbose stack at EventEmitter. (/usr/local/lib/node_modules/npm/lib/utils/lifecycle.js:245:16) +14 verbose stack at emitTwo (events.js:100:13) +14 verbose stack at EventEmitter.emit (events.js:185:7) +14 verbose stack at ChildProcess. (/usr/local/lib/node_modules/npm/lib/utils/spawn.js:24:14) +14 verbose stack at emitTwo (events.js:100:13) +14 verbose stack at ChildProcess.emit (events.js:185:7) +14 verbose stack at maybeClose (internal/child_process.js:827:16) +14 verbose stack at Process.ChildProcess._handle.onexit (internal/child_process.js:211:5) +15 verbose pkgid shopping-cart@0.0.0 +16 verbose cwd /Users/maximilianschwarzmuller/development/node/tutorials/youtube/shopping-cart +17 error Darwin 15.5.0 +18 error argv "/usr/local/bin/node" "/usr/local/bin/npm" "start" +19 error node v5.6.0 +20 error npm v3.9.0 +21 error code ELIFECYCLE +22 error shopping-cart@0.0.0 start: `node ./bin/www` +22 error Exit status 1 +23 error Failed at the shopping-cart@0.0.0 start script 'node ./bin/www'. +23 error Make sure you have the latest version of node.js and npm installed. +23 error If you do, this is most likely a problem with the shopping-cart package, +23 error not with npm itself. +23 error Tell the author that this fails on your system: +23 error node ./bin/www +23 error You can get information on how to open an issue for this project with: +23 error npm bugs shopping-cart +23 error Or if that isn't available, you can get their info via: +23 error npm owner ls shopping-cart +23 error There is likely additional logging output above. +24 verbose exit [ 1, true ] diff --git a/public/javascripts/checkout.js b/public/javascripts/checkout.js index 3a0d750..04bc0d7 100644 --- a/public/javascripts/checkout.js +++ b/public/javascripts/checkout.js @@ -1,4 +1,4 @@ -Stripe.setPublishableKey('pk_test_Z8Rr8NHqJmYA48M2FGGmpL3N'); +Stripe.setPublishableKey('pk_test_m6ZWLYyvkUAqJzr1fvr1uRj2'); var $form = $('#checkout-form'); diff --git a/routes/index.js b/routes/index.js index 2533777..c70f7ed 100644 --- a/routes/index.js +++ b/routes/index.js @@ -3,6 +3,7 @@ var router = express.Router(); var Cart = require('../models/cart'); var Product = require('../models/product'); +var Order = require('../models/order'); /* GET home page. */ router.get('/', function (req, res, next) { @@ -40,7 +41,7 @@ router.get('/shopping-cart', function(req, res, next) { res.render('shop/shopping-cart', {products: cart.generateArray(), totalPrice: cart.totalPrice}); }); -router.get('/checkout', function(req, res, next) { +router.get('/checkout', isLoggedIn, function(req, res, next) { if (!req.session.cart) { return res.redirect('/shopping-cart'); } @@ -49,14 +50,14 @@ router.get('/checkout', function(req, res, next) { res.render('shop/checkout', {total: cart.totalPrice, errMsg: errMsg, noError: !errMsg}); }); -router.post('/checkout', function(req, res, next) { +router.post('/checkout', isLoggedIn, function(req, res, next) { if (!req.session.cart) { return res.redirect('/shopping-cart'); } var cart = new Cart(req.session.cart); var stripe = require("stripe")( - "sk_test_pweF4VBTjmx6qKT3WgvKUkO4" + "sk_test_fwmVPdJfpkmwlQRedXec5IxR" ); stripe.charges.create({ @@ -69,10 +70,27 @@ router.post('/checkout', function(req, res, next) { req.flash('error', err.message); return res.redirect('/checkout'); } - req.flash('success', 'Successfully bought product!'); - req.session.cart = null; - res.redirect('/'); + var order = new Order({ + user: req.user, + cart: cart, + address: req.body.address, + name: req.body.name, + paymentId: charge.id + }); + order.save(function(err, result) { + req.flash('success', 'Successfully bought product!'); + req.session.cart = null; + res.redirect('/'); + }); }); }); module.exports = router; + +function isLoggedIn(req, res, next) { + if (req.isAuthenticated()) { + return next(); + } + req.session.oldUrl = req.url; + res.redirect('/user/signin'); +} diff --git a/routes/user.js b/routes/user.js index d00dbe1..7197830 100644 --- a/routes/user.js +++ b/routes/user.js @@ -3,11 +3,24 @@ var router = express.Router(); var csrf = require('csurf'); var passport = require('passport'); +var Order = require('../models/order'); +var Cart = require('../models/cart'); + var csrfProtection = csrf(); router.use(csrfProtection); router.get('/profile', isLoggedIn, function (req, res, next) { - res.render('user/profile'); + Order.find({user: req.user}, function(err, orders) { + if (err) { + return res.write('Error!'); + } + var cart; + orders.forEach(function(order) { + cart = new Cart(order.cart); + order.items = cart.generateArray(); + }); + res.render('user/profile', { orders: orders }); + }); }); router.get('/logout', isLoggedIn, function (req, res, next) { @@ -15,8 +28,8 @@ router.get('/logout', isLoggedIn, function (req, res, next) { res.redirect('/'); }); -router.use('/', notLoggedIn, function(req, res, next) { - next(); +router.use('/', notLoggedIn, function (req, res, next) { + next(); }); router.get('/signup', function (req, res, next) { @@ -25,10 +38,17 @@ router.get('/signup', function (req, res, next) { }); router.post('/signup', passport.authenticate('local.signup', { - successRedirect: '/user/profile', failureRedirect: '/user/signup', failureFlash: true -})); +}), function (req, res, next) { + if (req.session.oldUrl) { + var oldUrl = req.session.oldUrl; + req.session.oldUrl = null; + res.redirect(oldUrl); + } else { + res.redirect('/user/profile'); + } +}); router.get('/signin', function (req, res, next) { var messages = req.flash('error'); @@ -36,10 +56,17 @@ router.get('/signin', function (req, res, next) { }); router.post('/signin', passport.authenticate('local.signin', { - successRedirect: '/user/profile', failureRedirect: '/user/signin', failureFlash: true -})); +}), function (req, res, next) { + if (req.session.oldUrl) { + var oldUrl = req.session.oldUrl; + req.session.oldUrl = null; + res.redirect(oldUrl); + } else { + res.redirect('/user/profile'); + } +}); module.exports = router; diff --git a/views/shop/checkout.hbs b/views/shop/checkout.hbs index 7020ae6..85ea16f 100644 --- a/views/shop/checkout.hbs +++ b/views/shop/checkout.hbs @@ -10,13 +10,13 @@
- +
- +

diff --git a/views/user/profile.hbs b/views/user/profile.hbs index 9749f97..8b04224 100644 --- a/views/user/profile.hbs +++ b/views/user/profile.hbs @@ -1 +1,24 @@ -

User Profile

\ No newline at end of file +
+
+

User Profile

+
+

My Orders

+ {{# each orders }} +
+
+
    + {{# each this.items }} +
  • + {{ this.price }} + {{ this.item.title }} | {{ this.qty }} Units +
  • + {{/each}} +
+
+ +
+ {{/each}} +
+
\ No newline at end of file diff --git a/views/user/signin.hbs b/views/user/signin.hbs index b2175a4..de8f009 100644 --- a/views/user/signin.hbs +++ b/views/user/signin.hbs @@ -20,5 +20,6 @@ +

Don't have an account? Sign up instead!

\ No newline at end of file From 513241c37af72df6bca5e3bae85bc6f6ba9d6e42 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Maximilian=20Schwarzm=C3=BCller?= Date: Tue, 26 Jul 2016 10:51:00 +0200 Subject: [PATCH 6/6] finishing touches, cart management --- models/cart.js | 17 +++++++++++++++++ routes/index.js | 18 ++++++++++++++++++ views/shop/shopping-cart.hbs | 4 ++-- 3 files changed, 37 insertions(+), 2 deletions(-) diff --git a/models/cart.js b/models/cart.js index 79d850f..79a82ba 100644 --- a/models/cart.js +++ b/models/cart.js @@ -13,6 +13,23 @@ module.exports = function Cart(oldCart) { this.totalQty++; this.totalPrice += storedItem.item.price; }; + + this.reduceByOne = function(id) { + this.items[id].qty--; + this.items[id].price -= this.items[id].item.price; + this.totalQty--; + this.totalPrice -= this.items[id].item.price; + + if (this.items[id].qty <= 0) { + delete this.items[id]; + } + }; + + this.removeItem = function(id) { + this.totalQty -= this.items[id].qty; + this.totalPrice -= this.items[id].price; + delete this.items[id]; + }; this.generateArray = function() { var arr = []; diff --git a/routes/index.js b/routes/index.js index c70f7ed..bb6d153 100644 --- a/routes/index.js +++ b/routes/index.js @@ -33,6 +33,24 @@ router.get('/add-to-cart/:id', function(req, res, next) { }); }); +router.get('/reduce/:id', function(req, res, next) { + var productId = req.params.id; + var cart = new Cart(req.session.cart ? req.session.cart : {}); + + cart.reduceByOne(productId); + req.session.cart = cart; + res.redirect('/shopping-cart'); +}); + +router.get('/remove/:id', function(req, res, next) { + var productId = req.params.id; + var cart = new Cart(req.session.cart ? req.session.cart : {}); + + cart.removeItem(productId); + req.session.cart = cart; + res.redirect('/shopping-cart'); +}); + router.get('/shopping-cart', function(req, res, next) { if (!req.session.cart) { return res.render('shop/shopping-cart', {products: null}); diff --git a/views/shop/shopping-cart.hbs b/views/shop/shopping-cart.hbs index 64bf06d..a9ede84 100644 --- a/views/shop/shopping-cart.hbs +++ b/views/shop/shopping-cart.hbs @@ -10,8 +10,8 @@