diff --git a/app/server.js b/app/server.js index 5201d84d..23d03d19 100644 --- a/app/server.js +++ b/app/server.js @@ -2,30 +2,65 @@ 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 YAML = require('js-yaml'); +const swaggerDocument = YAML.load('./swagger.yaml'); const app = express(); app.use(bodyParser.json()); +// Define routes for brands/products/user authentication/user's cart +app.get('/api/brands', getBrands); +app.get('/api/products', getProducts); +app.post('/api/login', login); +app.get('/api/cart', getUserCart); +app.post('/api/cart', addToCart); + // 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'); + +// GET /api/brands +app.get("/api/brands", (req, res) => { + res.status(200).json(brands); + }); + + // POST /api/login +app.post("/api/me/cart", (req, res) => { + const { userId } = req; + const user = users.find((user) => user.id == userId); + + if (!user) { + return res.status(404).json({ message: "User not found" }); + } + + const { productId } = req.body; + const product = products.find((product) => { + return product.id == productId; + }); + + if (!product) { + return res.status(404).json({ message: "Product not found" }); + } + + user.cart.push(product); + return res.status(200).json({ message: "Product added to cart" }); + }); + -// Error handling +// Error handling middleware 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 +// Swagger UI app.use('/api-docs', swaggerUi.serve, swaggerUi.setup(swaggerDocument)); // 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; +module.exports = app; \ No newline at end of file diff --git a/package.json b/package.json index afe392cf..43fb3a0c 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "sunglasses-io", "version": "1.0.0", - "description": "", + "description": "sunglasses project", "main": "server.js", "type": "commonjs", "scripts": { @@ -9,8 +9,8 @@ "test": "mocha ./test/*.js" }, "keywords": [], - "author": "", - "license": "ISC", + "author": "Peyton", + "license": "UNLICENSED", "dependencies": { "body-parser": "^1.18.2", "express": "^4.18.2", @@ -27,5 +27,16 @@ "chai": "^4.4.1", "chai-http": "^4.4.0", "mocha": "^10.2.0" - } + }, + "directories": { + "test": "test" + }, + "repository": { + "type": "git", + "url": "git+https://github.com/PeytonLamb/sunglasses-io-peyton.git" + }, + "bugs": { + "url": "https://github.com/PeytonLamb/sunglasses-io-peyton/issues" + }, + "homepage": "https://github.com/PeytonLamb/sunglasses-io-peyton#readme" } diff --git a/swagger.yaml b/swagger.yaml index 1c2eb22f..8b8c24c6 100644 --- a/swagger.yaml +++ b/swagger.yaml @@ -1,11 +1,167 @@ -swagger: '2.0' +swagger: "2.0" info: - version: '1.0.0' - title: 'E-Commerce API' - description: 'API for managing brands, products, and user cart' + version: '2.0' + title: 'Sunglasses Store API' + description: "API for managing brands, products, user authentication, and user's cart" host: 'localhost:3000' schemes: - - 'http' -basePath: '/api' + - http +basePath: /api produces: - - 'application/json' + - application/json +paths: + /brands: + get: + summary: Retrieve all brands + responses: + 200: + description: List of + schema: + type: array + items: + $ref: '#/definitions/Brand' +/brands/{id}/products: + get: + summary: "Get products by brand ID" + parameters: + - name: id + in: path + description: "The ID of the brand to get products for" + required: true + type: string + responses: + '200': + description: 'Products retrieved successfully' + schema: + type: array + items: + $ref: "#/definitions/Product" + default: + description: Unexpected error + schema: + $ref: '#/definitions/Error' + /products: + get: + summary: Retrieve all products + responses: + 200: + description: 'Successfully retrieved products' + schema: + type: array + items: + $ref: '#/definitions/Product' + /login: + post: + summary: Authenticate user + consumes: + - application/x-www-form-urlencoded + parameters: + - name: username + in: formData + required: true + type: string + - name: password + in: formData + required: true + type: string + responses: + 200: + description: Successful authentication + 401: + description: Unauthorized + /me/cart: + get: + summary: "Get user cart" + responses: + 200: + description: "List of items in cart" + schema: + type: array + items: + $ref: "#/definitions/Cart" + /me/cart: + post: + summary: "Add product to cart" + description: "Add product to the cart" + parameters: + - name: body + in: body + description: "Product to add to cart" + required: true + schema: + $ref: "#/definitions/Product" + /me/cart/{productId}: + delete: + summary: "Delete item from cart" + description: "Remove item from the cart" + parameters: + - name: productId + in: path + description: "ID of item to remove" + required: true + type: string + /me/cart/{productId}: + post: + summary: "Change amount of item in cart" + description: "Update the amount of item in cart" + parameters: + - name: productId + in: path + description: "ID of product to update in cart" + required: true + type: string + - name: body + in: body + description: "New quantity value for product in cart" + required: true + schema: + type: object + properties: + quantity: + type: integer + description: "New amount of product in cart" +definitions: + Brand: + type: object + properties: + id: + type: integer + brand: + type: string + description: brand name + Product: + type: object + properties: + id: + type: integer + name: + type: string + description: name of product + brand: + type: string + description: brand of product + price: + type: number + User: + type: object + properties: + first_name: + type: string + description: first name of user + last_name: + type: string + description: last name of user + email: + type: string + description: email address of user + Cart: + type: array + Error: + type: object + properties: + code: + type: integer + message: + type: string + fields: + type: string \ No newline at end of file diff --git a/test/server.test.js b/test/server.test.js index 7ff14c8f..87aeec13 100644 --- a/test/server.test.js +++ b/test/server.test.js @@ -1,14 +1,151 @@ const chai = require('chai'); const chaiHttp = require('chai-http'); -const server = require('../app/server'); // Adjust the path as needed +const server = require('../app/server'); const should = chai.should(); chai.use(chaiHttp); -// TODO: Write tests for the server +// Tests for Brands endpoint +describe('Brands', () => { + it('should retrieve all brands', (done) => { + chai.request(server) + .get('/api/brands') + .end((err, res) => { + res.should.have.status(200); + res.body.should.be.a('array'); + res.body.length.should.be.above(0); + done(); + }); + }); +}); -describe('Brands', () => {}); +// Tests for Products endpoint +describe('Products', () => { + it('should retrieve all products', (done) => { + chai.request(server) + .get('/api/products') + .end((err, res) => { + res.should.have.status(200); + res.body.should.be.a('array'); + res.body.length.should.be.above(0); + done(); + }); + }); +}); -describe('Login', () => {}); +// Tests for Login endpoint +describe('Login', () => { + it('should authenticate user', (done) => { + chai.request(server) + .post('/api/login') + .send({ username: 'testuser', password: 'testpassword' }) // Provide valid credentials + .end((err, res) => { + res.should.have.status(200); + res.body.should.be.a('object'); + res.body.should.have.property('token'); + done(); + }); + }); -describe('Cart', () => {}); + it('should return unauthorized for invalid credentials', (done) => { + chai.request(server) + .post('/api/login') + .send({ username: 'invaliduser', password: 'invalidpassword' }) // Provide invalid credentials + .end((err, res) => { + res.should.have.status(401); + done(); + }); + }); +}); + +// Testss for Cart endpoint +describe('Cart', () => { + describe("/GET cart", () => { + it("should only get cart if logged in", (done) => { + chai + .request(server) + .get("/api/me/cart") + .end((err, res) => { + res.should.have.status(200); + done(); + }); + }); + }); + // POST items to the cart + describe("/POST cart", () => { + it("should POST product to cart", (done) => { + const productId = 1; + const userId = 1; + chai + .request(server) + .post("/api/me/cart") + .send({ userId: userId, productId: productId }) + .end((err, res) => { + res.should.have.status(200); + done(); + }); + }); + it("shouldn't POST product if user or product doesn't exist", (done) => { + const productId = 20; + const userId = 20; + chai + .request(server) + .post("/api/me/cart") + .send({ userId: userId, productId: productId }) + .end((err, res) => { + res.should.have.status(404); + done(); + }); + }); + }); + // DELETE items from the cart + describe("/DELETE cart", () => { + it("should DELETE an item from the cart", (done) => { + const productId = 1; + chai + .request(server) + .delete(`/api/me/cart/${productId}`) + .end((err, res) => { + res.should.have.status(200); + done(); + }); + }); + it("should not DELETE an object that doesn't exist in the cart", (done) => { + const productId = 21; + chai + .request(server) + .delete(`/api/me/cart/${productId}`) + .end((err, res) => { + res.should.have.status(404); + done(); + }); + }); + }); + describe("/PATCH cart", () => { + it("should update quantity of an item in the cart", (done) => { + const productId = 1; + const quantity = 2; + + chai + .request(server) + .patch(`/api/me/cart/${productId}`) + .send({ quantity: quantity }) + .end((err, res) => { + res.should.have.status(200); + done(); + }); + }); + it("should return 404 if no item is found in cart", (done) => { + const productId = 20; + const quantity = 2; + chai + .request(server) + .patch(`/api/me/cart/${productId}`) + .send({ quantity: quantity }) + .end((err, res) => { + res.should.have.status(404); + done(); + }); + }); + }); + }); \ No newline at end of file