Skip to content
Open
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
52 changes: 49 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,51 @@
## Sunglasses.io Server
# Sunglasses.io Server

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 is an API for an e-commerce store selling sunglasses.

If you have any questions about this project or the program in general, visit projectshift.io or email hello@projectshift.io.
## Routes

The following routes are supported by this API

```
GET /brands
GET /brands/:id/products
GET /products
POST /login
GET /me/cart
DELETE /me/cart/:productId
POST /me/cart/:productId
```

Documentation for each route can be found in the swagger.yaml file

## Getting Started

### Prerequisites

- Node.js (version 14 or above)
- npm

### Installation

1. Clone the repository:

```bash
git clone https://github.com/acl13/sunglasses-io.git
cd sunglasses-io
```

2. Install dependencies:

```bash
npm install
```

### Testing

```bash
npm test
```

This project has been created by a student at Parsity, an online software engineering course. 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.

If you have any questions about this project or the program in general, visit [parsity.io](https://parsity.io/) or email hello@parsity.io.
174 changes: 161 additions & 13 deletions app/server.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,179 @@
const express = require('express');
const bodyParser = require('body-parser');
const jwt = require('jsonwebtoken');
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 express = require("express");
const bodyParser = require("body-parser");
const jwt = require("jsonwebtoken");
const swaggerUi = require("swagger-ui-express");
const YAML = require("yamljs");
const swaggerDocument = YAML.load("./swagger.yaml");
const app = express();

app.use(bodyParser.json());

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

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

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

app.get("/brands", (request, response) => {
response.send(brands);
});

app.get("/products", (request, response) => {
response.send(products);
});

app.get("/brands/:id/products", (request, response) => {
// Get all products that match the brand id
// The sample data provided has a categoryId property that aligns with the brand id numbers - I think it would be better named "brandId" but chose not to change the existing data
const filteredProducts = products.filter((product) => {

Choose a reason for hiding this comment

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

you should validate that the brand id is correct.

return product.categoryId == request.params.id;
});

if (filteredProducts.length === 0) {
response.status(404);
response.send({ message: "No products found" });
} else {
Comment on lines +40 to +43

Choose a reason for hiding this comment

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

not accurate, is not a client error that there are no filtered products, you can return an empty array and show an empty state in the client.

response.send(filteredProducts);
}
});

app.post("/login", (request, response) => {
const { username, password } = request.body;

const user = users.find((user) => {
return user.login.username == username && user.login.password == password;
});

if (!username || !password) {
response.status(401);
response.send({ message: "Both a username and password are required" });
}

if (user) {
const token = jwt.sign({ user }, "secretKey", { expiresIn: "1hr" });
response.set("authorization", `Bearer ${token}`);
response.status(200);
response.send(user);
} else {
response.status(401);
response.send({ message: "Incorrect username or password" });
}
});

const getAuthenticatedUser = (request) => {
const authHeader = request.headers["authorization"];
if (!authHeader) {
return;
}
const token = authHeader.split(" ")[1];
const decoded = jwt.verify(token, "secretKey");

let authenticatedUser = users.find(
(user) => user.login.username == decoded.user.login.username
);
return authenticatedUser;
};

app.get("/me/cart", (request, response) => {
const user = getAuthenticatedUser(request);
if (!user) {
response.status(401);
response.send({ message: "Authentication failed" });
} else {
response.status(200);
response.send(user.cart);
}
});

app.post("/me/cart", (request, response) => {
const user = getAuthenticatedUser(request);
if (!user) {
response.status(401);
response.send({ message: "Authentication failed" });
}

const product = products.find((product) => product.id == request.body.id);

if (!product) {
response.status(404);
response.send({ message: "Product not found" });
}

user.cart.push(product);
response.status(200);
response.send(user.cart);
});

app.delete("/me/cart/:productId", (request, response) => {
const user = getAuthenticatedUser(request);
if (!user) {
response.status(401);
response.send({ message: "Authentication failed" });
}

const product = products.find(
(product) => product.id == request.params.productId
);

if (!product) {
response.status(404);
response.send({ message: "Product not found" });
}

const productIndex = user.cart.indexOf(product);
user.cart.splice(productIndex, 1);
response.status(200);
response.send(user.cart);
});

app.post("/me/cart/:productId", (request, response) => {
const user = getAuthenticatedUser(request);
if (!user) {
response.status(401);
response.send({ message: "Authentication failed" });
}

const product = products.find(
(product) => product.id == request.params.productId
);

if (!product) {
response.status(404);
response.send({ message: "Product not found" });
}

// Something like an "amountInCart" property on each product would be much easier to update, but this is what I could get working with the provided data
const currentAmount = user.cart.filter(
(product) => product.id == request.params.productId
).length;
const desiredAmount = request.body.amount;

if (desiredAmount > currentAmount) {
for (let i = currentAmount; i < desiredAmount; i++) {
user.cart.push(product);
}
} else if (desiredAmount < currentAmount) {
for (let i = desiredAmount; i < currentAmount; i++) {
const productIndex = user.cart.indexOf(product);
user.cart.splice(productIndex, 1);
}
}
response.status(200);
response.send(user.cart);
});

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

module.exports = app;
Loading