Skip to content

Commit

Permalink
Merge 88ccd51 into de5f505
Browse files Browse the repository at this point in the history
  • Loading branch information
akhilome committed Oct 1, 2018
2 parents de5f505 + 88ccd51 commit 794ca78
Show file tree
Hide file tree
Showing 8 changed files with 280 additions and 17 deletions.
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,8 @@
"purge-db": "echo 'DROP DATABASE IF EXISTS fastfoodfast;' | psql -U postgres && echo 'CREATE DATABASE fastfoodfast;' | psql -U postgres",
"setup-schema": "psql -U postgres fastfoodfast < ./server/db/schema.sql",
"config-db": "npm run purge-db && npm run setup-schema",
"setup-testdb": "echo 'DROP DATABASE IF EXISTS fastfoodfast_test;' | psql -U postgres && echo 'CREATE DATABASE fastfoodfast_test;' | psql -U postgres"
"setup-testdb": "echo 'DROP DATABASE IF EXISTS fastfoodfast_test;' | psql -U postgres && echo 'CREATE DATABASE fastfoodfast_test;' | psql -U postgres",
"seed-db": "psql -U postgres fastfoodfast < ./server/db/seed.sql"
},
"engines": {
"node": "8.12.0"
Expand Down
30 changes: 30 additions & 0 deletions server/controllers/orderController.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import orders from '../db/orders';
import Order from '../models/Order';
import pool from '../db/config';

class OrderController {
static getAllOrders(req, res) {
Expand Down Expand Up @@ -56,6 +57,35 @@ class OrderController {
order: orders[orderIndex],
});
}

static async getAllUserOrders(req, res) {
const { id } = req.params;

if (Number.isNaN(Number(id))) {
return res.status(400).json({
status: 'error',
message: 'invalid user id',
});
}

if (Number(id) !== req.userId) {
return res.status(403).json({
status: 'error',
message: 'you\'re not allowed to do that',
});
}

try {
const userOrders = (await pool.query('SELECT * FROM orders WHERE author=$1', [id])).rows;
return res.status(200).json({
status: 'success',
message: 'orders fetched successfully',
orders: userOrders,
});
} catch (error) {
return res.status(500).json({ error });
}
}
}

export default OrderController;
20 changes: 20 additions & 0 deletions server/db/seed.sql
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
INSERT INTO menu(food_name, food_image, price)
VALUES(
'Tasty Prawns',
'https://i.imgur.com/mTHYwlc.jpg',
1250
);

INSERT INTO menu(food_name, food_image, price)
VALUES(
'Turkey Wings',
'https://i.imgur.com/Bfn1CxC.jpg',
950
);

INSERT INTO menu(food_name, food_image, price)
VALUES(
'Chicken Wings',
'https://i.imgur.com/z490cis.jpg',
850
);
5 changes: 4 additions & 1 deletion server/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import bodyParser from 'body-parser';
import dotenv from 'dotenv';
import router from './routes/routes';
import authRouter from './routes/authRouter';
import ordersRouter from './routes/ordersRouter';

dotenv.config();
const app = express();
Expand All @@ -17,8 +18,10 @@ app.use(bodyParser.json());
app.use(bodyParser.urlencoded({ extended: false }));

app.use('/api/v1', router);
// Orders routes
app.use('/api/v1', ordersRouter);
// Auth routes
app.use('/api/v1/auth/', authRouter);
app.use('/api/v1/auth', authRouter);

app.listen(process.env.PORT);

Expand Down
9 changes: 9 additions & 0 deletions server/routes/ordersRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { Router } from 'express';
import AuthHandler from '../middleware/authHandler';
import OrderController from '../controllers/orderController';

const router = new Router();

router.get('/users/:id/orders', AuthHandler.authorize, OrderController.getAllUserOrders);

export default router;
6 changes: 3 additions & 3 deletions tests/routes/auth.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,13 @@ import 'chai/register-should';
import chaiHttp from 'chai-http';
import dirtyChai from 'dirty-chai';
import app from '../../server/index';
import { seedData, populateTables, populateUsersTable } from '../seed/seed';
import { seedData, emptyTables, populateUsersTable } from '../seed/seed';

chai.use(chaiHttp);
chai.use(dirtyChai);

describe('POST /auth/signup', () => {
before(populateTables);
before(emptyTables);

it('should signup a valid user successfully', (done) => {
chai.request(app)
Expand Down Expand Up @@ -100,7 +100,7 @@ describe('POST /auth/signup', () => {
});

describe('POST /auth/login', () => {
beforeEach(populateTables);
beforeEach(emptyTables);
beforeEach(populateUsersTable);

it('should sign an existing user in', (done) => {
Expand Down
100 changes: 100 additions & 0 deletions tests/routes/orders.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,100 @@
import chai from 'chai';
import 'chai/register-should';
import chaiHttp from 'chai-http';
import dirtyChai from 'dirty-chai';

import app from '../../server/index';
import pool from '../../server/db/config';
import {
seedData,
emptyTablesPromise,
populateUsersTablePromise,
populateMenuTablePromise,
populateOrdersTablePromise,
generateValidToken,
} from '../seed/seed';

chai.use(chaiHttp);
chai.use(dirtyChai);

describe('GET /users/<userId>/orders', () => {
beforeEach(async () => {
await emptyTablesPromise;
await populateMenuTablePromise;
await populateUsersTablePromise;
await populateOrdersTablePromise;
});
const { validUser, validUserTwo } = seedData.users;

it('should successfully get all orders for specified user', (done) => {
chai.request(app)
.get(`/api/v1/users/${validUser.id}/orders`)
.set('x-auth', generateValidToken(validUser))
.end((err, res) => {
if (err) done(err);

res.status.should.eql(200);
res.body.should.have.all.keys(['status', 'message', 'orders']);
res.body.orders.should.be.an('array');
done();
});
});

it('should return a 401 if user isn\'t authenticated', (done) => {
chai.request(app)
.get(`/api/v1/users/${validUser.id}/orders`)
.set('x-auth', '')
.end((err, res) => {
if (err) done(err);

res.status.should.eql(401);
res.body.should.have.all.keys(['status', 'message']);
res.body.status.should.eql('error');
done();
});
});

it('should only return orders placed by specified user', (done) => {
chai.request(app)
.get(`/api/v1/users/${validUser.id}/orders`)
.set('x-auth', generateValidToken(validUser))
.end(async (err, res) => {
if (err) done(err);

try {
const orderCount = (await pool.query('SELECT * FROM orders WHERE author=$1', [validUser.id])).rowCount;
res.body.orders.length.should.eql(orderCount);
done();
} catch (error) {
done(error);
}
});
});

it('should return a 403 if user tries to get orders not placed by them', (done) => {
chai.request(app)
.get(`/api/v1/users/${validUserTwo.id}/orders`)
.set('x-auth', generateValidToken(validUser))
.end((err, res) => {
if (err) done(err);

res.status.should.eql(403);
res.body.status.should.eql('error');
done();
});
});

it('should return a 400 if specified user id is not a number', (done) => {
chai.request(app)
.get('/api/v1/users/dontdothis/orders')
.set('x-auth', generateValidToken(validUser))
.end((err, res) => {
if (err) done(err);

res.status.should.eql(400);
res.body.status.should.eql('error');
res.body.message.should.eql('invalid user id');
done();
});
});
});
124 changes: 112 additions & 12 deletions tests/seed/seed.js
Original file line number Diff line number Diff line change
@@ -1,20 +1,32 @@
import bcrypt from 'bcryptjs';
import jwt from 'jsonwebtoken';
import pool from '../../server/db/config';

const adminEmail = 'hovkard@gmail.com';

const seedData = {
users: {
admin: {
id: null,
name: 'Kizito',
email: 'hovkard@gmail.com',
email: adminEmail,
password: 'suppersecurepassword',
confirmPassword: 'suppersecurepassword',
},
validUser: {
id: null,
name: 'James',
email: 'daniel@james.com',
password: 'pixel2user',
confirmPassword: 'pixel2user',
},
validUserTwo: {
id: null,
name: 'Philip',
email: 'philip@new.man',
password: 'facilitate',
confirmPassword: 'facilitate',
},
validUserInvalidPass: {
email: 'daniel@james.com',
password: 'thisiswrong',
Expand Down Expand Up @@ -46,10 +58,26 @@ const seedData = {
confirmPassword: 'anEntirelyDifferentThing',
},
},
menu: [
{ name: 'Burger', price: 700 },
{ name: 'Spiced turkey', price: 1200 },
{ name: 'Interesting biscuits', price: 12500 },
],
};

const populateTables = async () => {
const dropUsersTableQuery = 'DROP TABLE IF EXISTS users';
function generateValidToken(userObject) {
return jwt.sign({
userId: userObject.id,
userName: userObject.name,
userEmail: userObject.email,
userStatus: userObject.email === adminEmail ? 'admin' : 'customer',
}, process.env.JWT_SECRET).toString();
}

const emptyTables = async () => {
const dropUsersTableQuery = 'DROP TABLE IF EXISTS users CASCADE';
const dropMenuTableQuery = 'DROP TABLE IF EXISTS menu CASCADE';
const dropOrdersTableQuery = 'DROP TABLE IF EXISTS orders CASCADE';

const createUsersTableQuery = `CREATE TABLE users (
id serial PRIMARY KEY,
Expand All @@ -59,25 +87,97 @@ const populateTables = async () => {
is_admin BOOLEAN NOT NULL
)`;

const createMenuTableQuery = `CREATE TABLE menu (
id serial PRIMARY KEY,
food_name VARCHAR(255) NOT NULL,
food_image VARCHAR(255) NOT NULL DEFAULT 'https://i.imgur.com/yRLsidK.jpg',
price REAL NOT NULL
)`;

const createOrdersTableQuery = `CREATE TABLE orders (
id serial PRIMARY KEY,
item INTEGER REFERENCES menu(id),
author INTEGER REFERENCES users(id),
date DATE NOT NULL DEFAULT CURRENT_DATE,
status VARCHAR(50) NOT NULL DEFAULT 'new'
)`;

await pool.query(dropUsersTableQuery);
await pool.query(dropMenuTableQuery);
await pool.query(dropOrdersTableQuery);
await pool.query(createUsersTableQuery);
await pool.query(createMenuTableQuery);
await pool.query(createOrdersTableQuery);
};

const emptyTablesPromise = new Promise((resolve) => {
resolve(emptyTables);
});

const populateUsersTable = async () => {
// hash passwords
const adminHashedPassword = await bcrypt.hash(seedData.users.admin.password, 10);
const userHashedPassword = await bcrypt.hash(seedData.users.validUser.password, 10);
const insertQuery = 'INSERT INTO users(name, email, password, is_admin) VALUES($1, $2, $3, $4)';
const userOneHashedPassword = await bcrypt.hash(seedData.users.validUser.password, 10);
const userTwoHashedPassword = await bcrypt.hash(seedData.users.validUserTwo.password, 10);
const insertQuery = 'INSERT INTO users(name, email, password, is_admin) VALUES($1, $2, $3, $4) RETURNING id';
// Admin user
await pool.query(
seedData.users.admin.id = (await pool.query(
insertQuery,
[seedData.users.admin.name, seedData.users.admin.email, adminHashedPassword, 't'],
);
// Customer
await pool.query(
)).rows[0].id;
// Customer 1
seedData.users.validUser.id = (await pool.query(
insertQuery,
[seedData.users.validUser.name, seedData.users.validUser.email, userOneHashedPassword, 'f'],
)).rows[0].id;
// Customer 2
seedData.users.validUserTwo.id = (await pool.query(
insertQuery,
[seedData.users.validUser.name, seedData.users.validUser.email, userHashedPassword, 'f'],
);
[seedData.users.validUserTwo.name, seedData.users.validUserTwo.email, userTwoHashedPassword, 'f'],
)).rows[0].id;
};

export { seedData, populateTables, populateUsersTable };
const populateUsersTablePromise = new Promise((resolve) => {
resolve(populateUsersTable);
});

const populateMenuTable = () => {
const insertQuery = 'INSERT INTO menu(food_name, price) VALUES($1, $2)';

seedData.menu.forEach(async (food) => {
await pool.query(insertQuery, [food.name, food.price]);
});
};

const populateMenuTablePromise = new Promise((resolve) => {
resolve(populateMenuTable);
});

const populateOrdersTable = async () => {
const insertQuery = 'INSERT INTO orders(item, author) VALUES($1, $2)';
const randomFoodId = Math.ceil(seedData.menu.length * Math.random());

const orderOnePromise = pool.query(insertQuery, [randomFoodId, seedData.users.validUser.id]);
const orderTwoPromise = pool.query(insertQuery, [randomFoodId, seedData.users.validUser.id]);
const orderThreePromise = pool.query(insertQuery, [randomFoodId, seedData.users.validUserTwo.id]);
const orderFourPromise = pool.query(insertQuery, [randomFoodId, seedData.users.validUser.id]);

await Promise.all([orderOnePromise, orderTwoPromise, orderThreePromise, orderFourPromise]);
};

const populateOrdersTablePromise = new Promise((resolve) => {
resolve(populateOrdersTable);
});

export {
seedData,
emptyTables,
emptyTablesPromise,
populateUsersTable,
populateUsersTablePromise,
populateMenuTable,
populateMenuTablePromise,
populateOrdersTable,
populateOrdersTablePromise,
generateValidToken,
};

0 comments on commit 794ca78

Please sign in to comment.