Skip to content

Commit

Permalink
feat(shopping-cart): Ability to make transactions (#11)
Browse files Browse the repository at this point in the history
BREAKING CHANGE: payloads across the project please review the docs

Besides was added a cron job looking for inactive shopping carts.
  • Loading branch information
5h1rU committed Aug 21, 2018
1 parent 0033e40 commit 688956f
Show file tree
Hide file tree
Showing 27 changed files with 568 additions and 76 deletions.
4 changes: 3 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,4 @@
node_modules/
.env
.env
coverage
.nyc_output
2 changes: 1 addition & 1 deletion api/controllers/account.js
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ const Account = {
}),
delete: asyncUtil(async (req, res, next) => {
await UserService.delete(req.user.id);
res.status(204).json({ success: true });
res.status(204).json();
})
};

Expand Down
87 changes: 87 additions & 0 deletions api/controllers/cart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
const CartService = require('../services/cart');
const InventoryService = require('../services/inventory');
const asyncUtil = require('../lib/async');
const { errorBuilder } = require('../lib/errors');

const Cart = {
getTotal: cart => {
return cart.products
.map(product => product.quantity * product.product.price)
.reduce((prev, curr) => prev + curr, 0);
},
create: asyncUtil(async (req, res, next) => {
const cart = await CartService.create({ user: req.user.id });
if (!cart) {
throw errorBuilder({
name: 'ValidationError',
message: 'Cart already existent'
});
}

res.status(201).json({ success: true, cart });
}),
read: asyncUtil(async (req, res, next) => {
const cart = await CartService.read({ user: req.user.id });
if (!cart) {
throw errorBuilder({
name: 'NotFoundError',
message: 'Cart not found'
});
}
res.status(200).json({ success: true, cart });
}),
update: asyncUtil(async (req, res, next) => {
const item = req.body;

const inventory = await InventoryService.read({ product: item.id });
if (!inventory) {
throw errorBuilder({
name: 'NotFoundError',
message: 'Inventory not found'
});
}

const quantityByUserId = inventory.reservations
.map(r => (r._id == req.user.id ? r.quantity : 0))
.reduce((prev, curr) => prev + curr, 0);

const oldQuantity = inventory.quantity + quantityByUserId;
const totalQuantity = item.quantity - oldQuantity;

const payload = {
quantity: item.quantity,
id: item.id,
delta: totalQuantity
};

const inventoryUpdated = await InventoryService.update(
req.user.id,
payload
);
if (!inventoryUpdated) {
throw errorBuilder({
name: 'NotFoundError',
message: 'Not enough product'
});
}

let cart;
if (item.quantity <= 0) {
await InventoryService.deleteProduct(req.user.id, item);
cart = await CartService.removeProduct(req.user.id, item);
} else {
cart = await CartService.update(req.user.id, item);
}
if (!cart) {
throw errorBuilder({
name: 'NotFoundError',
message: 'Product not available'
});
}

res.status(200).json({ success: true, cart });
}),
delete: asyncUtil(async (req, res, next) => {})
};

module.exports = Cart;
54 changes: 54 additions & 0 deletions api/controllers/event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const EventService = require('../services/event');
const asyncUtil = require('../lib/async');
const { errorBuilder } = require('../lib/errors');

const Event = {
create: asyncUtil(async (req, res, next) => {
const event = await EventService.create(req.body);
if (!event) {
throw errorBuilder({
name: 'ValidationError',
message: 'Problem creating the event'
});
}
res.status(201).json({ success: true, event });
}),
read: asyncUtil(async (req, res, next) => {
const event = await EventService.read({});
if (!event) {
throw errorBuilder({
name: 'NotFoundError',
message: 'Event not found'
});
}
res.status(200).json({ success: true, event });
}),
readAll: asyncUtil(async (req, res, next) => {
const events = await EventService.readAll({ page: 1, limit: 10 });
res.status(200).json({ success: true, events });
}),
update: asyncUtil(async (req, res, next) => {
const payload = req.body;
const event = await EventService.update(req.params.id, payload);
if (!event) {
throw errorBuilder({
name: 'NotFoundError',
message: 'Event not found'
});
}
res.status(200).json({ success: true, event });
}),
delete: asyncUtil(async (req, res, next) => {
const event = await EventService.delete(req.params.id);
console.log(event);
if (!event) {
throw errorBuilder({
name: 'NotFoundError',
message: 'Event not found'
});
}
res.status(204).json();
})
};

module.exports = Event;
23 changes: 23 additions & 0 deletions api/controllers/inventory.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
const InventoryService = require('../services/inventory');
const asyncUtil = require('../lib/async');
const { errorBuilder } = require('../lib/errors');

// TODO: Populate the right response, not only showing the ID's
const Inventory = {
read: asyncUtil(async (req, res, next) => {
const inventory = await InventoryService.read({ product: req.params.id });
if (!inventory) {
throw errorBuilder({
name: 'NotFoundError',
message: 'Inventory not found'
});
}
res.status(200).json({ success: true, inventory });
}),
readAll: asyncUtil(async (req, res, next) => {
const inventory = await InventoryService.readAll({ page: 1 });
res.status(200).json({ success: true, inventory });
})
};

module.exports = Inventory;
13 changes: 12 additions & 1 deletion api/controllers/product.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,16 @@
const ProductService = require('../services/product');
const InventoryService = require('../services/inventory');
const asyncUtil = require('../lib/async');
const { errorBuilder } = require('../lib/errors');

const Product = {
create: asyncUtil(async (req, res, next) => {
const product = await ProductService.create(req.body);
const payload = {
id: product.id,
quantity: req.body.quantity
};
const inventory = await InventoryService.create(payload);
res.status(201).json({ success: true, product });
}),
read: asyncUtil(async (req, res, next) => {
Expand All @@ -17,6 +23,11 @@ const Product = {
}
res.status(200).json({ success: true, product });
}),
readAll: asyncUtil(async (req, res, next) => {
// TODO: use parameters for handle page and limit
const products = await ProductService.readAll({ page: 1, limit: 10 });
res.status(200).json({ success: true, products });
}),
update: asyncUtil(async (req, res, next) => {
const payload = req.body;
const product = await ProductService.update(req.params.id, payload);
Expand All @@ -36,7 +47,7 @@ const Product = {
message: 'Product not found'
});
}
res.status(204).json({ success: true });
res.status(204).json();
})
};

Expand Down
21 changes: 0 additions & 21 deletions api/controllers/shoppingCart.js

This file was deleted.

54 changes: 54 additions & 0 deletions api/controllers/venue.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
const VenueService = require('../services/venue');
const asyncUtil = require('../lib/async');
const { errorBuilder } = require('../lib/errors');

const Venue = {
create: asyncUtil(async (req, res, next) => {
const venue = await VenueService.create(req.body);
if (!venue) {
throw errorBuilder({
name: 'ValidationError',
message: 'Problem creating the venue'
});
}
res.status(201).json({ success: true, venue });
}),
read: asyncUtil(async (req, res, next) => {
const venue = await VenueService.read({});
if (!venue) {
throw errorBuilder({
name: 'NotFoundError',
message: 'venue not found'
});
}
res.status(200).json({ success: true, venue });
}),
readAll: asyncUtil(async (req, res, next) => {
// TODO: use parameters for handle page and limit
const venues = await VenueService.readAll({ page: 1, limit: 10 });
res.status(200).json({ success: true, venues });
}),
update: asyncUtil(async (req, res, next) => {
const payload = req.body;
const venue = await VenueService.update(req.params.id, payload);
if (!venue) {
throw errorBuilder({
name: 'NotFoundError',
message: 'venue not found'
});
}
res.status(200).json({ success: true, venue });
}),
delete: asyncUtil(async (req, res, next) => {
const venue = await VenueService.delete(req.params.id);
if (!venue) {
throw errorBuilder({
name: 'NotFoundError',
message: 'venue not found'
});
}
res.status(204).json();
})
};

module.exports = Venue;
13 changes: 7 additions & 6 deletions api/lib/auth.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,6 @@ const JWT = {
verify: token => {
return new Promise((resolve, reject) => {
jwt.verify(token, SECRET_KEY_TOKEN, (error, decodedToken) => {
if (decodedToken.exp < getTime(new Date())) {
return reject({
name: 'UnauthorizedError',
message: 'Token expired'
});
}
if (error || !decodedToken) {
const error = errorBuilder({
name: 'UnauthorizedError',
Expand All @@ -23,6 +17,13 @@ const JWT = {
return reject(error);
}

if (decodedToken.exp < getTime(new Date())) {
return reject({
name: 'UnauthorizedError',
message: 'Token expired'
});
}

resolve(decodedToken);
});
});
Expand Down
12 changes: 12 additions & 0 deletions api/lib/cart.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,12 @@
const CronJob = require('cron').CronJob;
const CartService = require('../services/cart');

const job = new CronJob('* */10 * * * *', function() {
const cutOffDate = new Date();
cutOffDate.setMinutes(cutOffDate.getMinutes() - 1);
CartService.watcherExpiration(cutOffDate);
});

job.start();

module.exports = job;
13 changes: 8 additions & 5 deletions api/models/cart.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,18 @@ const { Schema } = mongoose;

const CartSchema = new Schema(
{
_userId: {
user: {
type: Schema.Types.ObjectId,
required: true,
ref: 'User'
ref: 'User',
required: true
},
products: [
{
type: Schema.Types.ObjectId,
ref: 'Product',
product: {
type: Schema.Types.ObjectId,
ref: 'Product',
unique: true
},
quantity: {
type: Number,
required: true
Expand Down
19 changes: 19 additions & 0 deletions api/models/event.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
const mongoose = require('mongoose');
const mongoosePaginate = require('mongoose-paginate');

const { Schema } = mongoose;

const EventSchema = new Schema(
{
venue: {
type: Schema.Types.ObjectId,
ref: 'Venue',
required: true
}
},
{ timestamps: true }
);

EventSchema.plugin(mongoosePaginate);

module.exports = mongoose.model('Event', EventSchema);
Loading

0 comments on commit 688956f

Please sign in to comment.