implementing basket using redis
bin/www.js (so connection is saved in config now and is globally available)
#!/usr/bin/env node
// Dependencies
const http = require('http');
const config = require('../config');
const App = require('../app');
const mongoose = require('mongoose');
const redis = require('redis');
const log = config.logger;
mongoose.connect(config.mongodb.dsn)
.then(() => {
log.info('Successfully connected to MongoDb');
});
const redisClient = redis.createClient(config.redis.options);
redisClient.on('ready', () => {
log.info('Successfully connected to Redis');
});
config.redis.client = redisClient;
routes/basket/index.js
const express = require('express');
const itemService = require('../../services/itemService');
const basketService = require('../../services/basketService');
const userService = require('../../services/userService');
module.exports = (config) => {
const router = express.Router();
const log = config.logger;
const basket = basketService(config.redis.client);
router.get('/', async (req, res) => {
const basketItems = await basket.getAll(res.locals.currentUser.id);
let items = [];
if (basketItems) {
items = await Promise.all(Object.keys(basketItems).map(async (key) => {
const item = await itemService.getOne(key);
item.quantity = basketItems[key];
return item;
}));
}
return res.render('basket', { items });
});
router.get('/remove/:itemId', async (req, res) => {
if (!res.locals.currentUser) {
req.session.messages.push({
type: 'warning',
text: 'Please log in first',
});
return res.redirect('/shop');
}
try {
await basket.remove(req.params.itemId, res.locals.currentUser.id);
req.session.messages.push({
type: 'success',
text: 'The item was removed from the the basket',
});
} catch (err) {
req.session.messages.push({
type: 'danger',
text: 'There was an error removing the item from the basket',
});
log.fatal(err);
return res.redirect('/basket');
}
return res.redirect('/basket');
});
router.get('/buy', async (req, res, next) => {
return next('Not implemented');
/*
try {
const userId = res.locals.currentUser.id;
const user = res.locals.currentUser;
// Get all basket items for a user
const basketItems = await basket.getAll(userId);
// be defensive
if (!basketItems) {
throw new Error('No items found in basket');
}
// Find the item for each basket entry and add the quantity to it
// Return a new array with items plus quantity as new field
const items = await Promise.all(Object.keys(basketItems).map(async (key) => {
const item = await itemService.getOne(key);
item.quantity = basketItems[key];
return item;
}));
// Run this in a sequelize transaction
await order.inTransaction(async (t) => {
// Create a new order and add all items
await order.create(user, items, t);
// Clear the users basket
await Promise.all(Object.keys(basketItems).map(async (key) => {
await basket.remove(key, userId);
}));
});
req.session.messages.push({
type: 'success',
text: 'Thank you for your business',
});
return res.redirect('/basket');
} catch (err) {
req.session.messages.push({
type: 'danger',
text: 'There was an error finishing your order',
});
log.fatal(err);
return res.redirect('/basket');
}
*/
});
return router;
};
routes/shop/index.js
const express = require('express');
const itemService = require('../../services/itemService');
const basketService = require('../../services/basketService');
module.exports = (config) => {
const router = express.Router();
const log = config.logger;
const basket = basketService(config.redis.client);
router.get('/', async (req, res) => {
const items = await itemService.getAll();
return res.render('shop', { items });
});
router.get('/tobasket/:itemId', async (req, res) => {
if (!res.locals.currentUser) {
req.session.messages.push({
type: 'warning',
text: 'Please log in first',
});
return res.redirect('/shop');
}
try {
await basket.add(req.params.itemId, res.locals.currentUser.id);
req.session.messages.push({
type: 'success',
text: 'The item was added to the basket',
});
} catch (err) {
req.session.messages.push({
type: 'danger',
text: 'There was an error adding the item to the basket',
});
log.fatal(err);
}
return res.redirect('/shop');
});
return router;
};
routes/user/index.js (impersonate for user login)
const express = require('express');
const userService = require('../../../services/userService');
module.exports = (config) => {
const router = express.Router();
const log = config.logger;
// Do you see the keyword 'async' here?
// If this looks unfamiliar, this is a new language feature of JavaScript
// that made it into Node.js in version 8
// Functions that are marked with async can use the await keyword which
// lets you basically use asynchronous functions as if they were synchronous.
router.get('/:userId?', async (req, res, next) => {
try {
const users = await userService.getAll();
let user = null;
// The optional userId param was passed
if (req.params.userId) {
user = await userService.getOne(req.params.userId);
}
return res.render('admin/user', {
users,
user,
});
} catch (err) {
return next(err);
}
});
// Save or update user
router.post('/', async (req, res) => {
const email = req.body.email.trim();
const password = req.body.password.trim();
// Add this here because on update we might want to keep the password as it is
if (!email || (!password && !req.body.userId)) {
req.session.messages.push({
type: 'warning',
text: 'Please enter email address and password!',
});
return res.redirect('/admin/user');
}
try {
// If there was no existing user we now want to create a new user object
if (!req.body.userId) {
await userService.create({ email, password });
} else {
const userData = {
email,
};
// Add this if because password does not need to be changed on updated
if (password) {
userData.password = password;
}
await userService.update(req.body.userId, userData);
}
req.session.messages.push({
type: 'success',
text: `The user was ${req.body.userId ? 'updated' : 'created'} successfully!`,
});
return res.redirect('/admin/user');
} catch (err) {
req.session.messages.push({
type: 'danger',
text: 'There was an error while saving the user!',
});
log.fatal(err);
return res.redirect('/admin/user');
}
});
// Delete user
router.get('/delete/:userId', async (req, res) => {
try {
const deleteResult = await userService.remove(req.params.userId);
if (deleteResult === 0) {
throw new Error('Result returned zero deleted documents!');
}
} catch (err) {
// Error handling
req.session.messages.push({
type: 'danger',
text: 'There was an error while deleting the user!',
});
log.fatal(err);
return res.redirect('/admin/user');
}
// Let the user knows that everything went fine
req.session.messages.push({
type: 'success',
text: 'The user was successfully deleted!',
});
return res.redirect('/admin/user');
});
router.get('/impersonate/:userId', (req, res) => {
req.session.userId = req.params.userId;
req.session.messages.push({
type: 'success',
text: 'User successfully switched',
});
return res.redirect('/admin/user');
});
return router;
};
basketService.js
let client = null;
// add
async function add(itemId, userId) {
return new Promise((resolve, reject) => {
client.hget(`basket: ${userId}`, itemId, (err, obj) => {
if (err) return reject(err);
if (!obj) {
return client.hset(`basket: ${userId}`, itemId, 1, (seterr, res) => {
if (seterr) return reject(seterr);
return resolve(res);
});
}
return client.hincrby(`basket: ${userId}`, itemId, 1, (incerr, res) => {
if (incerr) return reject(incerr);
return resolve(res);
});
});
});
}
// getAll
async function getAll(userId) {
return new Promise((resolve, reject) => {
client.hgetall(`basket: ${userId}`, (err, res) => {
if (err) return reject(err);
return resolve(res);
});
});
}
// remove
async function remove(itemId, userId) {
return new Promise((resolve, reject) => {
client.hdel(`basket: ${userId}`, itemId, (err, res) => {
if (err) return reject(err);
return resolve(res);
});
});
}
module.exports = (_client) => {
if (!_client) throw new Error('Missing redis client object');
client = _client;
return {
add,
getAll,
remove,
};
};
itemService.js
const ItemModel = require('../models/mongoose/Item');
async function getAll() {
return ItemModel.find({}).sort({ createdAt: -1 });
}
async function getOne(itemId) {
return ItemModel.findOne({ _id: itemId });
}
async function create(data) {
const item = new ItemModel(data);
return item.save();
}
async function update(itemId, data) {
const item = await getOne(itemId);
if (!item) throw new Error('Could not find the requested item');
Object.keys(data).forEach((key) => {
item[key] = data[key];
});
return item.save();
}
async function remove(query) {
const result = await ItemModel.remove(query);
return result.result.n;
}
module.exports = {
getAll,
getOne,
create,
update,
remove,
};
config
const bunyan = require('bunyan');
const appname = 'Shopsy';
module.exports = {
applicationName: appname,
logger: bunyan.createLogger({ name: appname }),
mongodb: {
dsn: 'mongodb://localhost:37017/shopsy',
},
redis: {
options: { port: 7379 },
},
};