Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
b6247a4
swagger: /api/brand endpoint
Ronin619 Feb 28, 2026
f1e3e67
implement test and end point for /api/brands
Ronin619 Feb 28, 2026
5749317
implement test for /api/brands/:id/products endpoint
Ronin619 Mar 1, 2026
d9d02e6
implement endpoint for /brands/:id/products
Ronin619 Mar 1, 2026
99d8b03
update swagger file with api/brands/{id}/products documentation
Ronin619 Mar 1, 2026
e36b232
update swagger file format
Ronin619 Mar 1, 2026
11cdc0d
implement test for api/products
Ronin619 Mar 1, 2026
3bcbe0e
implement endpoint for api/products
Ronin619 Mar 1, 2026
2ba1aaa
update swagger file with /api/products doc
Ronin619 Mar 1, 2026
2dc1ad0
implement test for /api/login
Ronin619 Mar 4, 2026
55aa69b
implement endpoint for api/login
Ronin619 Mar 4, 2026
f65a4bd
install crypto and create a secret key
Ronin619 Mar 4, 2026
23d0dad
implement /log swagger doc
Ronin619 Mar 4, 2026
b373f11
implement end point for /api/me/cart
Ronin619 Mar 6, 2026
e72a821
implement test for /api/me/cart
Ronin619 Mar 6, 2026
0ce90b4
implement swagger doc for /me/cart
Ronin619 Mar 6, 2026
4311c13
implement token validator helper function
Ronin619 Mar 6, 2026
7d9073a
implement end point for post /api/me/cart
Ronin619 Mar 9, 2026
6dff331
implement test for post /api/me/cart
Ronin619 Mar 9, 2026
d505bd1
update post /api/me/cart endpoint
Ronin619 Mar 9, 2026
a6f03c4
update swagger doc with post /api/me/cart
Ronin619 Mar 9, 2026
21a55e1
implement delete api/me/cart/:productId endpoint
Ronin619 Mar 10, 2026
29713b8
update: delete endpoint
Ronin619 Mar 10, 2026
fb6ced8
implement tests for delete /api/me/cart/:productId
Ronin619 Mar 10, 2026
8a802fc
implement delete product docs
Ronin619 Mar 10, 2026
9ba72c8
implement update quantity end point post /api/me/cart/:productId
Ronin619 Mar 10, 2026
91e16a0
implement test for updating product quantity post /api/me/cart/:produ…
Ronin619 Mar 10, 2026
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
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ typings/

# dotenv environment variables file
.env
.env.local

# next.js build output
.next
248 changes: 235 additions & 13 deletions app/server.js
Original file line number Diff line number Diff line change
@@ -1,31 +1,253 @@
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");
require("dotenv").config({ path: ".env.local" });
const jwt = require("jsonwebtoken");
const crypto = require("crypto");
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 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));

// token validator helper function

const tokenValidator = (authHeader) => {
const token = authHeader.split(" ")[1];

const verify = jwt.verify(token, process.env.SECRET_KEY);
const username = verify.username;
const user = users.find((user) => user.login.username === username);

return user;
};

// End Points

app.get("/api/brands", function (request, response) {
if (!brands) {
response.writeHead(404);
return response.end("Brand does not exist.");
}

response.writeHead(200, { "Content-Type": "application/json" });
return response.end(JSON.stringify(brands));
});

app.get("/api/brands/:id/products", function (request, response) {
const brandId = request.params.id;

let filteredProducts = products.filter(
(product) => product.categoryId === brandId,
);

if (filteredProducts.length === 0) {
response.writeHead(404, { "Content-Type": "application/json" });
return response.end(JSON.stringify({ message: "products do not exist." }));
}

response.writeHead(200, { "Content-Type": "application/json" });
return response.end(JSON.stringify(filteredProducts));
});

app.get("/api/products", function (request, response) {
if (products.length === 0) {
response.writeHead(404, { "Content-Type": "application/json" });
return response.end(JSON.stringify({ message: "No products available." }));
}

response.writeHead(200, { "Content-Type": "application/json" });
return response.end(JSON.stringify(products));
});

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

if (!username || !password) {
response.writeHead(404, { "Content-Type": "application/json" });
return response.end(
JSON.stringify({ message: "Please enter a username and or password." }),
);
}

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

if (!user) {
response.writeHead(401, { "Content-Type": "application/json" });
return response.end(JSON.stringify({ message: "User does not exist." }));
}

const hashedPassword = crypto
.createHash("sha256")
.update(password + user.login.salt)
.digest("hex");

if (hashedPassword !== user.login.sha256) {
response.writeHead(401, { "Content-Type": "application/json" });
return response.end();
}

if (user) {
response.writeHead(200, { "Content-Type": "application/json" });

const token = jwt.sign({ username: username }, process.env.SECRET_KEY, {
expiresIn: "1h",
});
response.end(JSON.stringify({ token }));
}
});

app.get("/api/me/cart", function (request, response) {
const authHeader = request.headers.authorization;

if (!authHeader) {
response.writeHead(401, { "Content-Type": "application/json" });
return response.end(JSON.stringify({ error: "No token provided" }));
}

try {
const user = tokenValidator(authHeader);

response.writeHead(200, { "Content-Type": "application/json" });
response.end(JSON.stringify(user.cart));
} catch (err) {
response.writeHead(401, { "Content-Type": "application/json" });
response.end(JSON.stringify({ error: "Invalid or expired token" }));
}
});

app.post("/api/me/cart", function (request, response) {
const authHeader = request.headers.authorization;

if (!authHeader) {
response.writeHead(401, { "Content-Type": "application/json" });
return response.end(JSON.stringify({ error: "No token provided" }));
}

try {
const user = tokenValidator(authHeader);
const { id } = request.body;
const product = products.find((product) => product.id === id);

if (!product) {
response.writeHead(404, { "Content-Type": "application/json" });
return response.end(JSON.stringify({ error: "No product found." }));
}

const brand = brands.find((item) => item.id === product.categoryId);

if (!brand) {
response.writeHead(404, { "Content-Type": "application/json" });
return response.end(JSON.stringify({ error: "No brand found." }));
}

const cartItem = {
id: product.id,
brandName: brand.name,
desciption: product.name,
quantity: 1,
};
const cart = user.cart;
const foundItem = cart.find((item) => item.id === cartItem.id);

if (foundItem) {
foundItem.quantity += 1;
} else {
cart.push(cartItem);
}
response.writeHead(200, { "Content-Type": "application/json" });
response.end(JSON.stringify(user.cart));
} catch (err) {
response.writeHead(401, { "Content-Type": "application/json" });
response.end(JSON.stringify({ error: "Invalid or expired token" }));
}
});

app.delete("/api/me/cart/:productId", function (request, response) {
const authHeader = request.headers.authorization;

if (!authHeader) {
response.writeHead(401, { "Content-Type": "application/json" });
return response.end(JSON.stringify({ error: "No token provided" }));
}

try {
const user = tokenValidator(authHeader);
let cart = user.cart;
const { productId } = request.params;
const deleteIndex = cart.findIndex((product) => product.id === productId);

if (deleteIndex === -1) {
response.writeHead(404, { "Content-Type": "application/json" });
return response.end(JSON.stringify({ error: "Product not in cart" }));
}

cart.splice(deleteIndex, 1);

response.writeHead(200, { "Content-Type": "application/json" });
response.end(JSON.stringify(cart));
} catch (err) {
console.log(err);
response.writeHead(401, { "Content-Type": "application/json" });
response.end(JSON.stringify({ error: "Invalid or expired token" }));
}
});

app.post("/api/me/cart/:productId", function (request, response) {
const authHeader = request.headers.authorization;

if (!authHeader) {
response.writeHead(401, { "Content-Type": "application/json" });
return response.end(JSON.stringify({ error: "No token provided" }));
}

try {
const user = tokenValidator(authHeader);
let cart = user.cart;
const { productId } = request.params;
const { quantity } = request.body;
const productIndex = cart.findIndex((product) => product.id === productId);

if (productIndex === -1) {
response.writeHead(404, { "Content-Type": "application/json" });
return response.end(JSON.stringify({ error: "Product not in cart" }));
}

if (!quantity || quantity < 1) {
response.writeHead(404, { "Content-Type": "application/json" });
return response.end(JSON.stringify({ error: "Invalid quantity" }));
}

cart[productIndex].quantity = quantity;

response.writeHead(200, { "Content-Type": "application/json" });
response.end(JSON.stringify(cart));
} catch (err) {
response.writeHead(401, { "Content-Type": "application/json" });
response.end(JSON.stringify({ error: "Invalid or expired token" }));
}
});

// 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;
42 changes: 21 additions & 21 deletions initial-data/brands.json
Original file line number Diff line number Diff line change
@@ -1,22 +1,22 @@
[
{
"id": "1",
"name" : "Oakley"
},
{
"id": "2",
"name" : "Ray Ban"
},
{
"id": "3",
"name" : "Levi's"
},
{
"id": "4",
"name" : "DKNY"
},
{
"id": "5",
"name" : "Burberry"
}
]
{
"id": "1",
"name": "Oakley"
},
{
"id": "2",
"name": "Ray Ban"
},
{
"id": "3",
"name": "Levi's"
},
{
"id": "4",
"name": "DKNY"
},
{
"id": "5",
"name": "Burberry"
}
]
Loading