Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
34 commits
Select commit Hold shift + click to select a range
c283c1e
initial commit; three passing tests
nsLittle Jul 9, 2024
2ab66a7
4 passing; working on authentication
nsLittle Jul 13, 2024
cc0806e
6 working routes
nsLittle Jul 14, 2024
ec453a3
'/', '/brands', '/brands/:name', '/products', '/products/:name', '/us…
nsLittle Jul 14, 2024
688af61
routes good plus 5 tests
nsLittle Jul 14, 2024
bbed24d
restarted swagger file
nsLittle Jul 15, 2024
e280c36
6 routes, 5 passing tests, plus wireframe on home page
nsLittle Jul 15, 2024
20a6349
5 tests, wireframe, authentication bbegun
nsLittle Jul 20, 2024
44bb09e
yay oh yay i'm doing it
nsLittle Jul 20, 2024
5871502
cart looks good
nsLittle Jul 20, 2024
0c0bca4
lets make authentication work
nsLittle Jul 26, 2024
d96d306
/:name get/post/delete need work in swagger and postman; works in bro…
nsLittle Jul 27, 2024
48ff4c1
all routes working on browser, swagger and postman; authentication works
nsLittle Jul 27, 2024
2c8f251
10 routes good in browser, psotman and swagger; 7 tests passing
nsLittle Jul 27, 2024
43abb3c
POST test and server = works
nsLittle Jul 29, 2024
95958d4
Is this it?
nsLittle Aug 2, 2024
086106a
post /cart is still wonky
nsLittle Aug 2, 2024
f9e300d
still working on jwt
nsLittle Aug 14, 2024
257e766
/login now works; still need auth to work
nsLittle Aug 22, 2024
8f69779
login routes to /{user}; just need authentication to work
nsLittle Aug 22, 2024
4819553
arrrrgh
nsLittle Aug 23, 2024
8c824be
something something
nsLittle Aug 23, 2024
a9b11e2
yada yada
nsLittle Aug 23, 2024
86cdae6
so fucking close
nsLittle Aug 23, 2024
4b05011
so close...
nsLittle Aug 24, 2024
7a1c348
something is working
nsLittle Aug 24, 2024
d3dffea
reroutes, but authentication fails
nsLittle Aug 24, 2024
3a6b93b
all works?
nsLittle Aug 24, 2024
2f9e786
everything but the testing
nsLittle Aug 27, 2024
75d2937
starting testing 2.0
nsLittle Aug 31, 2024
0519670
testing underway
nsLittle Sep 1, 2024
074c640
oh yeah, testing is vavromming along
nsLittle Sep 1, 2024
e3fefa6
first round of (re)testing complete
nsLittle Sep 1, 2024
2d6b0db
ready for second submission
nsLittle Sep 1, 2024
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
26 changes: 13 additions & 13 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
{
// Use IntelliSense to learn about possible Node.js debug attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Sunglasses.io",
"program": "${workspaceRoot}/app/server.js"
}
]
}
// Use IntelliSense to learn about possible Node.js debug attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"type": "node",
"request": "launch",
"name": "Sunglasses.io",
"program": "${workspaceRoot}/app/server.js"
}
]
}
45 changes: 42 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,44 @@
## Sunglasses.io Server
## Sunglasses.io

This project has been created by a student at Project Shift, a software engineering fellowship located in Downtown Durham. The work in this repository is wholly of the student based on a sample starter project that can be accessed by looking at the repository that this project forks.
This project was created by Mutsumi Hata, a student at Parsity, an online software engineering program. The work in this repository is wholly of the student based on a sample starter project that can be accessed by looking at the original repository from which this project forks.

If you have any questions about this project or the program in general, visit projectshift.io or email [email protected].
If you have any questions about this project or the program in general, visit [parsity.io](https://parsity.io/) or email [email protected].

### Project Description

This simple Express application is a mockup of an online Sunglass store. This is my first backend API project defining routes and APIs. I used Swagger Editor to create a yaml file to document the routes.

### Table of Contents

- Sunglasses.io
- app
- server.js
- initial-data
- braands.json
- products.json
- users.json
- test
-server.test.js
- package.json
- README.md
- SunglassesWireframe.png
- swagger.yaml

### How to Run Application

1. Open terminal
2. Locate file: sunglasses-io
3. Type: npm start dev
4. Type: open http://localhost:3000 (or other appropriate host)

### Things to Add/Edit

1.SORT /products by alphabetical order 2.

3. SORT /users by alphabetical order
4. GET /{wrong user name} should not return "error: invalid token". Should return "error: user not found"
5. POST /{wrong user name} should not return "error: invalid token". Should return "error: user not found"
6. DELETE /{wrong user name} should be return "You should be adding to the cart". Should return "You should login first"
7. TESTING. ALL OF IT.
8. TEST error handling
9. ERROR codes should be consistently used.
233 changes: 227 additions & 6 deletions app/server.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,252 @@
const http = require('http');
const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');
const bcrypt= require('bcrypt');
const swaggerUi = require('swagger-ui-express');
const YAML = require('yamljs');
const swaggerDocument = YAML.load('./swagger.yaml'); // Replace './swagger.yaml' with the path to your Swagger file
// const swaggerDocument = YAML.load('../swagger.yaml');
const app = express();
const path = require('path');

// ABSOLUTE PATHS TO SERVER
const swaggerPath = path.resolve(__dirname, '../swagger.yaml');
const swaggerDocument = YAML.load(swaggerPath);

// PARSE JSON MIDDLEWARE
app.use(express.json())
app.use(bodyParser.json());

// Importing the data from JSON files
// JSON FILES
const users = require('../initial-data/users.json');
const brands = require('../initial-data/brands.json');
const products = require('../initial-data/products.json');

// Error handling
// CORS MIDDLEWARE
const CORS_HEADERS = {
"Access-Control-Allow-Origin": "*",
"Access-Control-Allow-Headers": "Origin, X-Requested-With, Content-Type, Accept, Authorization, X-Authentication, X-Username, X-Password, X-ApiKey",
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS"
};

// BASIC MIDDLEWARE
app.use((req, res, next) => {
console.log('Basic Middleware Stuff...');
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

need this console log? use comment instead.

res.set(CORS_HEADERS);
if (req.method === 'OPTIONS') {
return res.status(200).end();
}
next();
});

// ERROR HANDLING
app.use((err, req, res, next) => {
console.error(err.stack);
res.status(500).send('Something broke!');
});

// Swagger
// SWAGGER
app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument));

// Starting the server
// STATIC PUBLIC DIRECTORY
app.use('/initial-data', express.static(path.join(__dirname, '../initial-data')));

// ROUTE TO ROOT
app.get('/', (req, res) => {
console.log('hello');
res.status(200).send('Are you ready to shop?');
});

// NON-AUTHENTICATED ROUTES
app.get('/brands', (req, res) => {
const brandNames = brands.map(brand => brand.name);
brandNames.sort();
res.json( { 'All Brand Names': brandNames });
});

app.get('/brands/:name', (req, res) => {
// NO SYMBOLS & NO SPACE
const brandName = req.params.name;
const brand = brands.find(brand => brand.name.toUpperCase === brandName.toUpperCase);

if (brand) {
const brandId = brand.id;

const productsByBrand = products.filter(product => product.categoryId === brandId);

productsByBrand.sort((a, b) => {
if (a.name < b.name) return -1;
if (a.name > b.name) return 1;
return a.price - b.price;
});

res.json({ [brandName]: productsByBrand });
} else {
res.status(401).send('Brand name not found');
};
});

app.get('/products', (req, res) => {
try {
const capitalizeNames = (name) => {
if (!name) return '';

return name
.split(' ')
.map(word => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase())
.join(' ');
}

const productNames = products
.map(product => capitalizeNames(product.name))
.sort()

res.json({ 'All Product Names': productNames });
} catch (error) {
console.error('Error in /products route: ', error);
res.status(500).json({ error: 'Internal server error or something something'})
}

});

app.get('/products/:name', (req, res) => {
// NO SYMBOLS & NO SPACE
const productName = req.params.name.toLowerCase();
const product = products.find(product => product.name.toLowerCase() === productName);

if (product) {
const productDetails = {
name: product.name,
description: product.description,
price: product.price,
imageUrls: product.imageUrls
};
res.json({ 'Product Details': productDetails });
} else {
res.status(401).send('Product not found');
};
});

// JWT_SECRET
const JWT_SECRET = '9527e3a06a598251710743aa74e29e3681762684a01b184762469005a26afef3';

// LOGIN
app.post('/login', (req, res) => {
console.log('LOGIN...');
authHeader = req.headers.authorization;
console.log('AuthHeader: ', authHeader);
if(!authHeader || !authHeader.startsWith('Basic ')) {
return res.status(401).send({ error: 'Unauthorized' });
}
const base64Credentials = authHeader.split(' ')[1];
const credentials = Buffer.from(base64Credentials, 'base64').toString('utf-8');
const [username, password] = credentials.split(':');

// const { username, password } = req.body;
const user = users.find(user => user.login.username === username);
console.log('Username: ', username);
console.log('Password: ', password);

if (user && password === user.login.password) {
// GENERATES TOKEN
const token = jwt.sign({ username: user.login.username }, JWT_SECRET, {expiresIn: '1d' });

console.log('Token: ', token);

// ROUTE to (/{user.name.first})
const redirectUrl =`/${user.name.first}`;
console.log('ReDirectUrl: ', redirectUrl);

res.status(200).json({ token, redirectUrl: `/${user.name.first}`});
} else {
res.status(401).send('Username or password is incorrect');
}
});

// AUTHENTICATION MIDDLEWARE
const authenticateJWT = (req, res, next) => {
let authHeader = req.headers['authorization'];
console.log('AUTHENTICATION');
console.log('AuthHeader: ', authHeader);
Comment on lines +169 to +170
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

clean up your code please


if (authHeader) {
const token = authHeader.split(' ')[1];
console.log('AuthHeader Deconstructed: ', token);
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need


jwt.verify(token, JWT_SECRET, (err, user) => {
if (err) {
return res.status(403).json({ error: 'Invalid token' });
}

req.user = user;
next();
});
} else {
res.status(401).json({ error: 'Authorization header missing' });
}
};

// AUTHENTICATED ROUTES
app.get('/users', authenticateJWT, (req, res) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is the purpose of this api?

const userNames = users.map(user => user.name.first);
res.status(200).json({ users: userNames });
});

app.get('/:name', authenticateJWT, (req, res) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You defined well the cart routes in the swagger file but this is not matching your server.js file. Here the url is not containing the resource (cart). Make sure to always include it!

const userName = req.params.name.toLowerCase();
const user = users.find(user => user.name.first.toLowerCase() === userName);

if (user) {
const userCart = user.cart;
res.status(200).json({ userCart: userCart });
} else {
res.status(401).json({ error: 'Unauthorized' });
};
});

app.post('/:name', authenticateJWT, (req, res) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

same as above, I dont know that this is the cart

const userName = req.params.name.toLowerCase();
const user = users.find(user => user.name.first.toLowerCase() === userName);

if (!user) {
return res.status(401).json({ error: 'User not found.' });
};

if (!user.cart) {
user.cart = { items: [], total: 0 };
};

const newItem= {
product: req.body.product || 'glas',
quantity: req.body.quantity || 1,
price: req.body.price || 50,
};
Comment on lines +219 to +223
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

big no to send the whole product in the body from the client, you need to send the id and then just find this id in your products list in the BE. Also, why do you have a default? Big no as well, if the param is not provided, then you need to reject the call with 400 - client error.


user.cart.items.push(newItem);
user.cart.total = user.cart.items.reduce(
(sum, item) => sum + (item.price * item.quantity), 0
);

res.status(200).json({ userCart: user.cart });
});

app.delete('/:name', authenticateJWT, (req, res) => {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing /cart

const userName = req.params.name.toLowerCase();
const user = users.find(user => user.name.first.toLowerCase() === userName);
Comment on lines +234 to +235
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

you are repeating this validation in almost every api, maybe can be part of the authenticationToken?


if (!user) {
return res.status(401).json({ error: 'User not found'});
}

user.cart = { items: [], total: 0 };

res.status(200).json({ message: 'Cart was successfully deleted' });
});

// START SERVER
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`Server running on port ${PORT}`);
});

module.exports = app;
module.exports = app;
Loading