-
Notifications
You must be signed in to change notification settings - Fork 140
nsLittle's Node Eval #169
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
base: master
Are you sure you want to change the base?
nsLittle's Node Eval #169
Changes from all commits
c283c1e
2ab66a7
cc0806e
ec453a3
688af61
bbed24d
e280c36
20a6349
44bb09e
5871502
0c0bca4
d96d306
48ff4c1
2c8f251
43abb3c
95958d4
086106a
f9e300d
257e766
8f69779
4819553
8c824be
a9b11e2
86cdae6
4b05011
7a1c348
d3dffea
3a6b93b
2f9e786
75d2937
0519670
074c640
e3fefa6
2d6b0db
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| 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" | ||
| } | ||
| ] | ||
| } |
| 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. |
| 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...'); | ||
| 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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); | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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) => { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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
There was a problem hiding this comment. Choose a reason for hiding this commentThe 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; | ||
There was a problem hiding this comment.
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.