Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Added menu and payment endpoints #12

Merged
merged 2 commits into from
Mar 26, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 6 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,26 +17,31 @@ The API offers the following endpoints:
| POST | /api/auth/login | Log in |
| GET | /api/auth/logout | Log out |
| POST | /api/orders | Create or place an order |
| GET | /api/orders | User retrieve own list of orders |
| GET | /api/orders/:id | User retrieve own single order |
| GET | /api/menu | Retrieve menu categories and items |
| POST | /api/payments | Create payment intent |
| GET | /api/admin/orders/:id | Admin retrieve a single order |
| GET | /api/admin/orders | Admin retrieve list of all the orders |
| PATCH | /api/admin/orders/:id | Admin update status of a single order |

<br/>

### Technologies used

#### Databases
1. PostgreSQL
2. Redis

#### Other libraries/frameworks
#### Libraries/frameworks
1. ExpressJS
2. Sequelize ORM
3. JSON Web Token
4. Joi
5. Bcrypt
6. Twilio
7. Cors
8. Stripe
<br/><br/>

### CI/CD
Expand Down
7 changes: 5 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
"sequelize": "^6.3.5",
"sequelize-cli": "^6.2.0",
"sequelize-test-helpers": "^1.3.2",
"stripe": "^8.138.0",
"twilio": "^3.54.1"
},
"devDependencies": {
Expand All @@ -55,9 +56,11 @@
"create-db": "npx sequelize-cli db:create",
"drop-db": "npx sequelize-cli db:drop",
"migrate": "npx sequelize-cli db:migrate",
"pretest": "yarn drop-db && yarn create-db && yarn migrate && yarn create-admin && yarn seed",
"pretest": "yarn lint && yarn drop-db && yarn create-db && yarn migrate && yarn create-admin && yarn seed",
"heroku-postbuild": "yarn migrate && yarn create-admin",
"create-admin": "babel-node ./src/database/scripts/adminScript.js",
"seed": "npx sequelize-cli db:seed:all"
"seed": "npx sequelize-cli db:seed:all",
"create-menu": "babel-node ./src/database/scripts/menuScript.js",
"lint": "yarn run eslint src/ && yarn run eslint tests/"
}
}
8 changes: 8 additions & 0 deletions src/config/stripeConfig.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
import dotenv from 'dotenv';
import Stripe from 'stripe';

dotenv.config();

const stripe = new Stripe(process.env.STRIPE_SECRET_KEY);

export default stripe;
30 changes: 30 additions & 0 deletions src/controllers/menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import statusCodes from '../utils/statusCodes';
import misc from '../helpers/misc';

const {
serverError,
success,
} = statusCodes;
const {
successResponse,
errorResponse,
} = misc;

/**
* @description Class to handle the creation, retrieval and updating of orders
*/
export default class Menu {
/**
* @description method that returns the menu items
* @param {object} req
* @param {object} res
* @returns {object} Success | error response object
*/
static getMenuItems = async (req, res) => {
try {
return successResponse(res, success, null, null, req.menuItems);
} catch (error) {
return errorResponse(res, serverError, error);
}
};
}
10 changes: 5 additions & 5 deletions src/database/models/contents.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';
const {
Model
Model,
} = require('sequelize');

module.exports = (sequelize, DataTypes) => {
class Contents extends Model {
/**
Expand All @@ -14,15 +14,15 @@ module.exports = (sequelize, DataTypes) => {
foreignKey: 'orderId',
});
}
};
}
Contents.init({
itemId: DataTypes.INTEGER,
itemName: DataTypes.STRING,
cost: DataTypes.DECIMAL,
quantity: DataTypes.INTEGER
quantity: DataTypes.INTEGER,
}, {
sequelize,
modelName: 'Contents',
});
return Contents;
};
};
10 changes: 5 additions & 5 deletions src/database/models/item.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';
const {
Model
Model,
} = require('sequelize');

module.exports = (sequelize, DataTypes) => {
class Item extends Model {
/**
Expand All @@ -14,16 +14,16 @@ module.exports = (sequelize, DataTypes) => {
foreignKey: 'menuId',
});
}
};
}
Item.init({
name: DataTypes.STRING,
description: DataTypes.STRING,
cost: DataTypes.DECIMAL,
size: DataTypes.STRING,
image: DataTypes.STRING
image: DataTypes.STRING,
}, {
sequelize,
modelName: 'Item',
});
return Item;
};
};
10 changes: 5 additions & 5 deletions src/database/models/menu.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';
const {
Model
Model,
} = require('sequelize');

module.exports = (sequelize, DataTypes) => {
class Menu extends Model {
/**
Expand All @@ -14,12 +14,12 @@ module.exports = (sequelize, DataTypes) => {
foreignKey: 'menuId',
});
}
};
}
Menu.init({
name: DataTypes.STRING
name: DataTypes.STRING,
}, {
sequelize,
modelName: 'Menu',
});
return Menu;
};
};
10 changes: 5 additions & 5 deletions src/database/models/order.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
'use strict';
const {
Model
Model,
} = require('sequelize');

module.exports = (sequelize, DataTypes) => {
class Order extends Model {
/**
Expand All @@ -17,14 +17,14 @@ module.exports = (sequelize, DataTypes) => {
foreignKey: 'userId',
});
}
};
}
Order.init({
total: DataTypes.DECIMAL,
status: DataTypes.STRING,
paymentId: DataTypes.STRING
paymentId: DataTypes.STRING,
}, {
sequelize,
modelName: 'Order',
});
return Order;
};
};
11 changes: 5 additions & 6 deletions src/database/models/user.js
Original file line number Diff line number Diff line change
@@ -1,12 +1,11 @@
'use strict';

import roles from '../../utils/roles';

const {CUSTOMER, STAFF, ADMIN} = roles;
const { CUSTOMER, STAFF, ADMIN } = roles;

const {
Model
Model,
} = require('sequelize');

module.exports = (sequelize, DataTypes) => {
class User extends Model {
/**
Expand All @@ -19,7 +18,7 @@ module.exports = (sequelize, DataTypes) => {
foreignKey: 'userId',
});
}
};
}
User.init({
firstName: DataTypes.STRING,
lastName: DataTypes.STRING,
Expand All @@ -34,4 +33,4 @@ module.exports = (sequelize, DataTypes) => {
modelName: 'User',
});
return User;
};
};
98 changes: 98 additions & 0 deletions src/database/scripts/menuScript.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
import models from '../models';

const { Menu, Item } = models;

/**
* @description Clears the menu records
*/
const clearMenu = async () => {
await models.sequelize.query('DELETE FROM "Items";');
await models.sequelize.query('DELETE FROM "Menus";');
};

/**
* @description Creates the menu items
*/
const createItems = async (id, name) => {
switch (name) {
case 'Breakfast':
await Item.create({
menuId: id,
name: 'French Omelette De Fromage',
description: 'Our famous Omelette De Fromage with lots of Cheese.',
cost: 4.00,
size: 'Medium',
image: 'https://media.istockphoto.com/photos/omelette-picture-id155375267',
});
break;
case 'Lunch/Dinner':
await Item.create({
menuId: id,
name: 'Double Cheese Burger',
description: 'This is a very tasty cheese burger.',
cost: 6.50,
size: 'Large',
image: 'https://media.istockphoto.com/photos/delicious-fresh-cooked-burger-with-a-side-of-french-fries-picture-id177556385',
});
await Item.create({
menuId: id,
name: 'Chicken Pizza',
description: 'This is a very tasty Pizza.',
cost: 9.00,
size: 'Large',
image: 'https://media.istockphoto.com/photos/delicious-vegetarian-pizza-on-white-picture-id1192094401',
});
break;
case 'Drinks':
await Item.create({
menuId: id,
name: 'Diet Coke',
description: 'Diet Coke without added sugar.',
cost: 1.50,
size: 'Small',
image: 'https://media.istockphoto.com/photos/can-of-cocacola-on-ice-picture-id487787108',
});
await Item.create({
menuId: id,
name: 'Cappucinno',
description: 'The best Cappucinno in town.',
cost: 1.50,
size: 'Medium',
image: 'https://media.istockphoto.com/photos/paper-coffee-cup-and-lid-isolated-on-white-picture-id1165889671?s=612x612',
});
break;
default:
break;
}
};

/**
* @description Creates the menu categories
*/
const createCategories = async () => {
const categoriesData = [
{ name: 'Breakfast' },
{ name: 'Lunch/Dinner' },
{ name: 'Drinks' },
];
categoriesData.forEach(async (category) => {
const menu = await Menu.findOrCreate({
where: { name: category.name },
defaults: category,
});
const { id, name } = menu[0];
await createItems(id, name);
});
};

/**
* @description Clears and creates menu categories and their items
*/
const createMenu = async () => {
await clearMenu();
await createCategories();
};

createMenu();

export default createMenu;
2 changes: 1 addition & 1 deletion src/database/seeders/20210111131026-items.js
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ module.exports = {
description: 'The best Cappucinno in town.',
cost: 1.50,
size: 'Medium',
image: 'https://www.istockphoto.com/photo/3d-paper-coffee-cup-and-lid-isolated-on-white-gm1165889671-320956287',
image: 'https://media.istockphoto.com/photos/paper-coffee-cup-and-lid-isolated-on-white-picture-id1165889671?s=612x612',
createdAt: new Date(),
updatedAt: new Date(),
},
Expand Down
33 changes: 33 additions & 0 deletions src/middlewares/menu.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
import _ from 'lodash';
import helpers from '../helpers/misc';
import models from '../database/models';
import services from '../services/services';
import statusCodes from '../utils/statusCodes';
import messages from '../utils/messages';

const { errorResponse } = helpers;
const { Menu, Item } = models;
const { findMenuItems } = services;
const { notFound, serverError } = statusCodes;
const { menuNotFound } = messages;

/**
* @description Retrieves the menu or returns an error response
* @param {object} req Request object
* @param {object} res Response object
* @param {function} next Callback function
*/
const findMenu = async (req, res, next) => {
try {
const menu = await findMenuItems(Menu, Item);
if (_.isEmpty(menu)) {
return errorResponse(res, notFound, menuNotFound);
}
req.menuItems = menu;
return next();
} catch (error) {
return errorResponse(res, serverError, error);
}
};

export default { findMenu };
Loading