Using redis to implement a shopping basket

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 },
  },
};

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章