├── .eslintignore ├── .gitignore ├── images ├── logo.png ├── logo-white.png ├── mobile-logo.png ├── icons │ ├── buy-again.png │ ├── cart-icon.png │ ├── checkmark.png │ ├── search-icon.png │ ├── checkmark-white.png │ └── checkout-lock-icon.png ├── products │ ├── .DS_Store │ ├── bathroom-mat.jpg │ ├── straw-sunhat.jpg │ ├── luxury-towel-set.jpg │ ├── vanity-mirror-pink.jpg │ ├── 2-slot-toaster-white.jpg │ ├── 3-piece-cooking-set.jpg │ ├── men-chino-pants-beige.jpg │ ├── round-sunglasses-gold.jpg │ ├── laundry-detergent-tabs.jpg │ ├── men-brown-flat-sneakers.jpg │ ├── artistic-bowl-set-6-piece.jpg │ ├── blackout-curtain-set-beige.jpg │ ├── blackout-curtains-set-teal.jpg │ ├── duvet-cover-set-gray-queen.jpg │ ├── men-athletic-shoes-white.jpg │ ├── men-golf-polo-t-shirt-gray.jpg │ ├── sky-leaf-branch-earrings.jpg │ ├── women-striped-beach-dress.jpg │ ├── women-summer-jean-shorts.jpg │ ├── bath-towel-set-gray-rosewood.jpg │ ├── facial-tissue-2-ply-8-boxes.jpg │ ├── kitchen-paper-towels-8-pack.jpg │ ├── knit-athletic-sneakers-gray.jpg │ ├── women-knit-ballet-flat-white.jpg │ ├── athletic-cotton-socks-6-pairs.jpg │ ├── athletic-skateboard-shoes-gray.jpg │ ├── black-and-silver-espresso-maker.jpg │ ├── countertop-push-blender-black.jpg │ ├── elegant-white-dinner-plate-set.jpg │ ├── glass-screw-lid-food-containers.jpg │ ├── men-navigator-sunglasses-black.jpg │ ├── men-stretch-wool-sweater-black.jpg │ ├── non-stick-cooking-set-4-pieces.jpg │ ├── women-knit-beanie-pom-pom-blue.jpg │ ├── women-relaxed-lounge-pants-pink.jpg │ ├── women-sandal-heels-white-pink.jpg │ ├── intermediate-composite-basketball.jpg │ ├── men-cozy-fleece-hoodie-light-teal.jpg │ ├── crystal-zirconia-stud-earrings-pink.jpg │ ├── adults-plain-cotton-tshirt-2-pack-teal.jpg │ ├── electric-steel-hot-water-kettle-white.jpg │ └── women-plain-cotton-oversized-sweater-gray.jpg ├── mobile-logo-white.png ├── ratings │ ├── rating-0.png │ ├── rating-10.png │ ├── rating-15.png │ ├── rating-20.png │ ├── rating-25.png │ ├── rating-30.png │ ├── rating-35.png │ ├── rating-40.png │ ├── rating-45.png │ ├── rating-5.png │ └── rating-50.png ├── appliance-warranty.png ├── clothing-size-chart.png └── appliance-instructions.png ├── backend ├── cart.json ├── deliveryOptions.json ├── orders.json └── products.json ├── exercise-solutions ├── 1a.md ├── 1b.md ├── 1c.md ├── 1e.md └── 1d.md ├── defaultData ├── defaultCart.js ├── defaultDeliveryOptions.js ├── defaultOrders.js └── defaultProducts.js ├── .eslintrc.json ├── models ├── DeliveryOption.js ├── Order.js ├── CartItem.js ├── Product.js └── index.js ├── routes ├── deliveryOptions.js ├── products.js ├── paymentSummary.js ├── reset.js ├── cartItems.js └── orders.js ├── package.json ├── README.md ├── zipFiles.js ├── patches └── sql.js-as-sqlite3+0.2.1.patch ├── troubleshooting.md ├── server.js └── documentation.md /.eslintignore: -------------------------------------------------------------------------------- 1 | node_modules/ 2 | database.sqlite 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | database.sqlite 3 | *.zip 4 | dist 5 | .DS_Store -------------------------------------------------------------------------------- /images/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/logo.png -------------------------------------------------------------------------------- /images/logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/logo-white.png -------------------------------------------------------------------------------- /images/mobile-logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/mobile-logo.png -------------------------------------------------------------------------------- /images/icons/buy-again.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/icons/buy-again.png -------------------------------------------------------------------------------- /images/icons/cart-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/icons/cart-icon.png -------------------------------------------------------------------------------- /images/icons/checkmark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/icons/checkmark.png -------------------------------------------------------------------------------- /images/products/.DS_Store: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/.DS_Store -------------------------------------------------------------------------------- /images/icons/search-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/icons/search-icon.png -------------------------------------------------------------------------------- /images/mobile-logo-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/mobile-logo-white.png -------------------------------------------------------------------------------- /images/ratings/rating-0.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/ratings/rating-0.png -------------------------------------------------------------------------------- /images/ratings/rating-10.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/ratings/rating-10.png -------------------------------------------------------------------------------- /images/ratings/rating-15.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/ratings/rating-15.png -------------------------------------------------------------------------------- /images/ratings/rating-20.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/ratings/rating-20.png -------------------------------------------------------------------------------- /images/ratings/rating-25.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/ratings/rating-25.png -------------------------------------------------------------------------------- /images/ratings/rating-30.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/ratings/rating-30.png -------------------------------------------------------------------------------- /images/ratings/rating-35.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/ratings/rating-35.png -------------------------------------------------------------------------------- /images/ratings/rating-40.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/ratings/rating-40.png -------------------------------------------------------------------------------- /images/ratings/rating-45.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/ratings/rating-45.png -------------------------------------------------------------------------------- /images/ratings/rating-5.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/ratings/rating-5.png -------------------------------------------------------------------------------- /images/ratings/rating-50.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/ratings/rating-50.png -------------------------------------------------------------------------------- /images/appliance-warranty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/appliance-warranty.png -------------------------------------------------------------------------------- /images/clothing-size-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/clothing-size-chart.png -------------------------------------------------------------------------------- /images/appliance-instructions.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/appliance-instructions.png -------------------------------------------------------------------------------- /images/icons/checkmark-white.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/icons/checkmark-white.png -------------------------------------------------------------------------------- /images/products/bathroom-mat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/bathroom-mat.jpg -------------------------------------------------------------------------------- /images/products/straw-sunhat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/straw-sunhat.jpg -------------------------------------------------------------------------------- /images/icons/checkout-lock-icon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/icons/checkout-lock-icon.png -------------------------------------------------------------------------------- /images/products/luxury-towel-set.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/luxury-towel-set.jpg -------------------------------------------------------------------------------- /images/products/vanity-mirror-pink.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/vanity-mirror-pink.jpg -------------------------------------------------------------------------------- /images/products/2-slot-toaster-white.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/2-slot-toaster-white.jpg -------------------------------------------------------------------------------- /images/products/3-piece-cooking-set.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/3-piece-cooking-set.jpg -------------------------------------------------------------------------------- /images/products/men-chino-pants-beige.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/men-chino-pants-beige.jpg -------------------------------------------------------------------------------- /images/products/round-sunglasses-gold.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/round-sunglasses-gold.jpg -------------------------------------------------------------------------------- /images/products/laundry-detergent-tabs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/laundry-detergent-tabs.jpg -------------------------------------------------------------------------------- /images/products/men-brown-flat-sneakers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/men-brown-flat-sneakers.jpg -------------------------------------------------------------------------------- /images/products/artistic-bowl-set-6-piece.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/artistic-bowl-set-6-piece.jpg -------------------------------------------------------------------------------- /images/products/blackout-curtain-set-beige.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/blackout-curtain-set-beige.jpg -------------------------------------------------------------------------------- /images/products/blackout-curtains-set-teal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/blackout-curtains-set-teal.jpg -------------------------------------------------------------------------------- /images/products/duvet-cover-set-gray-queen.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/duvet-cover-set-gray-queen.jpg -------------------------------------------------------------------------------- /images/products/men-athletic-shoes-white.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/men-athletic-shoes-white.jpg -------------------------------------------------------------------------------- /images/products/men-golf-polo-t-shirt-gray.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/men-golf-polo-t-shirt-gray.jpg -------------------------------------------------------------------------------- /images/products/sky-leaf-branch-earrings.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/sky-leaf-branch-earrings.jpg -------------------------------------------------------------------------------- /images/products/women-striped-beach-dress.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/women-striped-beach-dress.jpg -------------------------------------------------------------------------------- /images/products/women-summer-jean-shorts.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/women-summer-jean-shorts.jpg -------------------------------------------------------------------------------- /images/products/bath-towel-set-gray-rosewood.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/bath-towel-set-gray-rosewood.jpg -------------------------------------------------------------------------------- /images/products/facial-tissue-2-ply-8-boxes.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/facial-tissue-2-ply-8-boxes.jpg -------------------------------------------------------------------------------- /images/products/kitchen-paper-towels-8-pack.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/kitchen-paper-towels-8-pack.jpg -------------------------------------------------------------------------------- /images/products/knit-athletic-sneakers-gray.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/knit-athletic-sneakers-gray.jpg -------------------------------------------------------------------------------- /images/products/women-knit-ballet-flat-white.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/women-knit-ballet-flat-white.jpg -------------------------------------------------------------------------------- /images/products/athletic-cotton-socks-6-pairs.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/athletic-cotton-socks-6-pairs.jpg -------------------------------------------------------------------------------- /images/products/athletic-skateboard-shoes-gray.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/athletic-skateboard-shoes-gray.jpg -------------------------------------------------------------------------------- /images/products/black-and-silver-espresso-maker.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/black-and-silver-espresso-maker.jpg -------------------------------------------------------------------------------- /images/products/countertop-push-blender-black.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/countertop-push-blender-black.jpg -------------------------------------------------------------------------------- /images/products/elegant-white-dinner-plate-set.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/elegant-white-dinner-plate-set.jpg -------------------------------------------------------------------------------- /images/products/glass-screw-lid-food-containers.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/glass-screw-lid-food-containers.jpg -------------------------------------------------------------------------------- /images/products/men-navigator-sunglasses-black.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/men-navigator-sunglasses-black.jpg -------------------------------------------------------------------------------- /images/products/men-stretch-wool-sweater-black.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/men-stretch-wool-sweater-black.jpg -------------------------------------------------------------------------------- /images/products/non-stick-cooking-set-4-pieces.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/non-stick-cooking-set-4-pieces.jpg -------------------------------------------------------------------------------- /images/products/women-knit-beanie-pom-pom-blue.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/women-knit-beanie-pom-pom-blue.jpg -------------------------------------------------------------------------------- /images/products/women-relaxed-lounge-pants-pink.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/women-relaxed-lounge-pants-pink.jpg -------------------------------------------------------------------------------- /images/products/women-sandal-heels-white-pink.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/women-sandal-heels-white-pink.jpg -------------------------------------------------------------------------------- /images/products/intermediate-composite-basketball.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/intermediate-composite-basketball.jpg -------------------------------------------------------------------------------- /images/products/men-cozy-fleece-hoodie-light-teal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/men-cozy-fleece-hoodie-light-teal.jpg -------------------------------------------------------------------------------- /images/products/crystal-zirconia-stud-earrings-pink.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/crystal-zirconia-stud-earrings-pink.jpg -------------------------------------------------------------------------------- /images/products/adults-plain-cotton-tshirt-2-pack-teal.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/adults-plain-cotton-tshirt-2-pack-teal.jpg -------------------------------------------------------------------------------- /images/products/electric-steel-hot-water-kettle-white.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/electric-steel-hot-water-kettle-white.jpg -------------------------------------------------------------------------------- /images/products/women-plain-cotton-oversized-sweater-gray.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/SuperSimpleDev/ecommerce-backend-ai/HEAD/images/products/women-plain-cotton-oversized-sweater-gray.jpg -------------------------------------------------------------------------------- /backend/cart.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "productId": "e43638ce-6aa0-4b85-b27f-e1d07eb678c6", 4 | "quantity": 2, 5 | "deliveryOptionId": "1" 6 | }, 7 | { 8 | "productId": "15b6fc6f-327a-4ec4-896f-486349e85a3d", 9 | "quantity": 1, 10 | "deliveryOptionId": "2" 11 | } 12 | ] 13 | -------------------------------------------------------------------------------- /exercise-solutions/1a.md: -------------------------------------------------------------------------------- 1 | ``` 2 | Prompt: 3 | In GET /api/products, add a query parameter search=___. 4 | If this parameter exists, only return products where 5 | the search text is included in the product name. 6 | ``` 7 | 8 | ### Code Changes 9 | https://github.com/SuperSimpleDev/ecommerce-backend-ai/pull/1/files -------------------------------------------------------------------------------- /backend/deliveryOptions.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "1", 4 | "deliveryDays": 7, 5 | "priceCents": 0 6 | }, 7 | { 8 | "id": "2", 9 | "deliveryDays": 3, 10 | "priceCents": 499 11 | }, 12 | { 13 | "id": "3", 14 | "deliveryDays": 1, 15 | "priceCents": 999 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /defaultData/defaultCart.js: -------------------------------------------------------------------------------- 1 | export const defaultCart = [ 2 | { 3 | productId: "e43638ce-6aa0-4b85-b27f-e1d07eb678c6", 4 | quantity: 2, 5 | deliveryOptionId: "1" 6 | }, 7 | { 8 | productId: "15b6fc6f-327a-4ec4-896f-486349e85a3d", 9 | quantity: 1, 10 | deliveryOptionId: "2" 11 | } 12 | ]; 13 | -------------------------------------------------------------------------------- /defaultData/defaultDeliveryOptions.js: -------------------------------------------------------------------------------- 1 | export const defaultDeliveryOptions = [ 2 | { 3 | id: "1", 4 | deliveryDays: 7, 5 | priceCents: 0 6 | }, 7 | { 8 | id: "2", 9 | deliveryDays: 3, 10 | priceCents: 499 11 | }, 12 | { 13 | id: "3", 14 | deliveryDays: 1, 15 | priceCents: 999 16 | } 17 | ]; 18 | -------------------------------------------------------------------------------- /exercise-solutions/1b.md: -------------------------------------------------------------------------------- 1 | In Postman, use `GET http://localhost:3000/api/products?search=basketball` and press Send. 2 | 3 | 4 | 5 | Try some other search texts like `?search=shirt` 6 | 7 | -------------------------------------------------------------------------------- /exercise-solutions/1c.md: -------------------------------------------------------------------------------- 1 | ``` 2 | Prompt: 3 | Each product has a keyword property. Update the 4 | search feature so if the search text is included 5 | in any of the keywords, it will also return the 6 | product. 7 | ``` 8 | 9 | ### Code Changes 10 | https://github.com/SuperSimpleDev/ecommerce-backend-ai/pull/2/files 11 | 12 | - I added a comment explaining why the code generated by the AI works. -------------------------------------------------------------------------------- /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "env": { 3 | "browser": true, 4 | "es2021": true, 5 | "node": true 6 | }, 7 | "extends": "eslint:recommended", 8 | "parserOptions": { 9 | "ecmaVersion": 2022, // Update to support top-level await 10 | "sourceType": "module" 11 | }, 12 | "rules": { 13 | "indent": ["error", 2], 14 | "linebreak-style": ["error", "unix"], 15 | "semi": ["error", "always"] 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /models/DeliveryOption.js: -------------------------------------------------------------------------------- 1 | import { DataTypes } from 'sequelize'; 2 | import { sequelize } from './index.js'; 3 | 4 | export const DeliveryOption = sequelize.define('DeliveryOption', { 5 | id: { 6 | type: DataTypes.STRING, 7 | primaryKey: true 8 | }, 9 | deliveryDays: { 10 | type: DataTypes.INTEGER, 11 | allowNull: false 12 | }, 13 | priceCents: { 14 | type: DataTypes.INTEGER, 15 | allowNull: false 16 | }, 17 | createdAt: { 18 | type: DataTypes.DATE(3) 19 | }, 20 | updatedAt: { 21 | type: DataTypes.DATE(3) 22 | }, 23 | }, { 24 | defaultScope: { 25 | order: [['createdAt', 'ASC']] 26 | } 27 | }); 28 | -------------------------------------------------------------------------------- /models/Order.js: -------------------------------------------------------------------------------- 1 | import { DataTypes } from 'sequelize'; 2 | import { sequelize } from './index.js'; 3 | 4 | export const Order = sequelize.define('Order', { 5 | id: { 6 | type: DataTypes.UUID, 7 | defaultValue: DataTypes.UUIDV4, 8 | primaryKey: true 9 | }, 10 | orderTimeMs: { 11 | type: DataTypes.BIGINT, 12 | allowNull: false 13 | }, 14 | totalCostCents: { 15 | type: DataTypes.INTEGER, 16 | allowNull: false 17 | }, 18 | products: { 19 | type: DataTypes.JSON, 20 | allowNull: false 21 | }, 22 | createdAt: { 23 | type: DataTypes.DATE(3) 24 | }, 25 | updatedAt: { 26 | type: DataTypes.DATE(3) 27 | }, 28 | }, { 29 | defaultScope: { 30 | order: [['createdAt', 'ASC']] 31 | } 32 | }); 33 | -------------------------------------------------------------------------------- /routes/deliveryOptions.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { DeliveryOption } from '../models/DeliveryOption.js'; 3 | 4 | const router = express.Router(); 5 | 6 | router.get('/', async (req, res) => { 7 | const expand = req.query.expand; 8 | const deliveryOptions = await DeliveryOption.findAll(); 9 | let response = deliveryOptions; 10 | 11 | if (expand === 'estimatedDeliveryTime') { 12 | response = deliveryOptions.map(option => { 13 | const deliveryTimeMs = Date.now() + option.deliveryDays * 24 * 60 * 60 * 1000; 14 | return { 15 | ...option.toJSON(), 16 | estimatedDeliveryTimeMs: deliveryTimeMs 17 | }; 18 | }); 19 | } 20 | 21 | res.json(response); 22 | }); 23 | 24 | export default router; 25 | -------------------------------------------------------------------------------- /models/CartItem.js: -------------------------------------------------------------------------------- 1 | import { DataTypes } from 'sequelize'; 2 | import { sequelize } from './index.js'; 3 | 4 | export const CartItem = sequelize.define('CartItem', { 5 | productId: { 6 | type: DataTypes.UUID, 7 | allowNull: false, 8 | references: { 9 | model: 'Products', 10 | key: 'id' 11 | } 12 | }, 13 | quantity: { 14 | type: DataTypes.INTEGER, 15 | allowNull: false 16 | }, 17 | deliveryOptionId: { 18 | type: DataTypes.STRING, 19 | allowNull: false, 20 | references: { 21 | model: 'DeliveryOptions', 22 | key: 'id' 23 | } 24 | }, 25 | createdAt: { 26 | type: DataTypes.DATE(3) 27 | }, 28 | updatedAt: { 29 | type: DataTypes.DATE(3) 30 | }, 31 | }, { 32 | defaultScope: { 33 | order: [['createdAt', 'ASC']] 34 | } 35 | }); 36 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ecommerce-backend", 3 | "version": "1.0.0", 4 | "main": "server.js", 5 | "type": "module", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1", 8 | "start": "node server.js", 9 | "dev": "nodemon server.js", 10 | "zip": "node zipFiles.js", 11 | "postinstall": "patch-package" 12 | }, 13 | "keywords": [], 14 | "author": "", 15 | "license": "ISC", 16 | "description": "", 17 | "dependencies": { 18 | "cors": "^2.8.5", 19 | "express": "^4.21.2", 20 | "mysql2": "^3.14.1", 21 | "patch-package": "8.0.0", 22 | "pg": "^8.16.0", 23 | "sequelize": "^6.6.5", 24 | "sql.js": "1.10.3", 25 | "sql.js-as-sqlite3": "0.2.1" 26 | }, 27 | "devDependencies": { 28 | "archiver": "^7.0.1", 29 | "eslint": "^8.0.0", 30 | "nodemon": "^3.1.9" 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /backend/orders.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "27cba69d-4c3d-4098-b42d-ac7fa62b7664", 4 | "orderTimeMs": 1723456800000, 5 | "totalCostCents": 3506, 6 | "products": [ 7 | { 8 | "productId": "e43638ce-6aa0-4b85-b27f-e1d07eb678c6", 9 | "quantity": 1, 10 | "estimatedDeliveryTimeMs": 1723716000000 11 | }, 12 | { 13 | "productId": "83d4ca15-0f35-48f5-b7a3-1ea210004f2e", 14 | "quantity": 2, 15 | "estimatedDeliveryTimeMs": 1723456800000 16 | } 17 | ] 18 | }, 19 | { 20 | "id": "b6b6c212-d30e-4d4a-805d-90b52ce6b37d", 21 | "orderTimeMs": 1718013600000, 22 | "totalCostCents": 4190, 23 | "products": [ 24 | { 25 | "productId": "15b6fc6f-327a-4ec4-896f-486349e85a3d", 26 | "quantity": 2, 27 | "estimatedDeliveryTimeMs": 1718618400000 28 | } 29 | ] 30 | } 31 | ] -------------------------------------------------------------------------------- /defaultData/defaultOrders.js: -------------------------------------------------------------------------------- 1 | export const defaultOrders = [ 2 | { 3 | id: "27cba69d-4c3d-4098-b42d-ac7fa62b7664", 4 | orderTimeMs: 1723456800000, 5 | totalCostCents: 3506, 6 | products: [ 7 | { 8 | productId: "e43638ce-6aa0-4b85-b27f-e1d07eb678c6", 9 | quantity: 1, 10 | estimatedDeliveryTimeMs: 1723716000000 11 | }, 12 | { 13 | productId: "83d4ca15-0f35-48f5-b7a3-1ea210004f2e", 14 | quantity: 2, 15 | estimatedDeliveryTimeMs: 1723456800000 16 | } 17 | ] 18 | }, 19 | { 20 | id: "b6b6c212-d30e-4d4a-805d-90b52ce6b37d", 21 | orderTimeMs: 1718013600000, 22 | totalCostCents: 4190, 23 | products: [ 24 | { 25 | productId: "15b6fc6f-327a-4ec4-896f-486349e85a3d", 26 | quantity: 2, 27 | estimatedDeliveryTimeMs: 1718618400000 28 | } 29 | ] 30 | } 31 | ]; 32 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## Intro to the Project 2 | This is the backend for the [ecommerce-project](https://github.com/SuperSimpleDev/ecommerce-project). 3 | - 95% of the code was generated with AI. 4 | 5 | ## Video Tutorials 6 | **Part 1 - Create the Backend:** https://youtu.be/vBprybSmJs8 7 | 8 | ## Set up this backend 9 | 1. Make sure you have NodeJS installed (version 22+). If not, [click here to install](https://nodejs.org/). 10 | 2. Download this code by clicking the green `Code` button (in the top-right) > Click `Download Zip`. 11 | 3. Unzip the code. On Windows, right-click the zip file > `Extract All`. On Mac, double-click the zip file. 12 | 4. Open this code in VSCode. 13 | 5. At the top menu of VSCode, click `Terminal` > `New Terminal`. 14 | 6. In the Terminal, run `npm install`, and run `npm run dev`. 15 | 16 | ## Troubleshooting 17 | If you run into issues, see the [troubleshooting steps](troubleshooting.md). 18 | -------------------------------------------------------------------------------- /routes/products.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { Product } from '../models/Product.js'; 3 | 4 | const router = express.Router(); 5 | 6 | router.get('/', async (req, res) => { 7 | const search = req.query.search; 8 | 9 | let products; 10 | if (search) { 11 | products = await Product.findAll(); 12 | 13 | // Filter products by case-insensitive search on name or keywords 14 | const lowerCaseSearch = search.toLowerCase(); 15 | 16 | products = products.filter(product => { 17 | const nameMatch = product.name.toLowerCase().includes(lowerCaseSearch); 18 | 19 | const keywordsMatch = product.keywords.some(keyword => keyword.toLowerCase().includes(lowerCaseSearch)); 20 | 21 | return nameMatch || keywordsMatch; 22 | }); 23 | 24 | } else { 25 | products = await Product.findAll(); 26 | } 27 | 28 | res.json(products); 29 | }); 30 | 31 | export default router; -------------------------------------------------------------------------------- /models/Product.js: -------------------------------------------------------------------------------- 1 | import { DataTypes } from 'sequelize'; 2 | import { sequelize } from './index.js'; 3 | 4 | export const Product = sequelize.define('Product', { 5 | id: { 6 | type: DataTypes.UUID, 7 | defaultValue: DataTypes.UUIDV4, 8 | primaryKey: true 9 | }, 10 | image: { 11 | type: DataTypes.STRING, 12 | allowNull: false 13 | }, 14 | name: { 15 | type: DataTypes.STRING, 16 | allowNull: false 17 | }, 18 | rating: { 19 | type: DataTypes.JSON, 20 | allowNull: false 21 | }, 22 | priceCents: { 23 | type: DataTypes.INTEGER, 24 | allowNull: false 25 | }, 26 | keywords: { 27 | type: DataTypes.STRING, 28 | allowNull: false, 29 | get() { 30 | return this.getDataValue('keywords').split(','); 31 | }, 32 | set(val) { 33 | this.setDataValue('keywords', val.join(',')); 34 | } 35 | }, 36 | createdAt: { 37 | type: DataTypes.DATE(3) 38 | }, 39 | updatedAt: { 40 | type: DataTypes.DATE(3) 41 | }, 42 | }, { 43 | defaultScope: { 44 | order: [['createdAt', 'ASC']] 45 | } 46 | }); 47 | -------------------------------------------------------------------------------- /routes/paymentSummary.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { CartItem } from '../models/CartItem.js'; 3 | import { Product } from '../models/Product.js'; 4 | import { DeliveryOption } from '../models/DeliveryOption.js'; 5 | 6 | const router = express.Router(); 7 | 8 | router.get('/', async (req, res) => { 9 | const cartItems = await CartItem.findAll(); 10 | let totalItems = 0; 11 | let productCostCents = 0; 12 | let shippingCostCents = 0; 13 | 14 | for (const item of cartItems) { 15 | const product = await Product.findByPk(item.productId); 16 | const deliveryOption = await DeliveryOption.findByPk(item.deliveryOptionId); 17 | totalItems += item.quantity; 18 | productCostCents += product.priceCents * item.quantity; 19 | shippingCostCents += deliveryOption.priceCents; 20 | } 21 | 22 | const totalCostBeforeTaxCents = productCostCents + shippingCostCents; 23 | const taxCents = Math.round(totalCostBeforeTaxCents * 0.1); 24 | const totalCostCents = totalCostBeforeTaxCents + taxCents; 25 | 26 | res.json({ 27 | totalItems, 28 | productCostCents, 29 | shippingCostCents, 30 | totalCostBeforeTaxCents, 31 | taxCents, 32 | totalCostCents 33 | }); 34 | }); 35 | 36 | export default router; 37 | -------------------------------------------------------------------------------- /zipFiles.js: -------------------------------------------------------------------------------- 1 | import fs from 'fs'; 2 | import path from 'path'; 3 | import archiver from 'archiver'; 4 | 5 | const outputDir = path.resolve('./'); 6 | const zipFilePrefix = 'ecommerce-backend-'; 7 | 8 | // Get the next zip file ID 9 | const getNextZipId = () => { 10 | const files = fs.readdirSync(outputDir); 11 | const zipFiles = files.filter(file => file.startsWith(zipFilePrefix) && file.endsWith('.zip')); 12 | const ids = zipFiles.map(file => parseInt(file.replace(zipFilePrefix, '').replace('.zip', ''), 10)).filter(Number.isInteger); 13 | return ids.length > 0 ? Math.max(...ids) + 1 : 1; 14 | }; 15 | 16 | const zipId = getNextZipId(); 17 | const zipFileName = `${zipFilePrefix}${zipId}.zip`; 18 | const output = fs.createWriteStream(path.join(outputDir, zipFileName)); 19 | const archive = archiver('zip', { zlib: { level: 9 } }); 20 | 21 | output.on('close', () => { 22 | console.log(`${zipFileName} has been created. Total size: ${archive.pointer()} bytes`); 23 | }); 24 | 25 | archive.on('error', err => { 26 | throw err; 27 | }); 28 | 29 | archive.pipe(output); 30 | 31 | // Add files and folders to the archive using glob patterns 32 | archive.glob('**/*', { 33 | cwd: outputDir, 34 | dot: true, // Include files that start with a dot 35 | ignore: [ 36 | 'node_modules/**', // Exclude node_modules 37 | 'database.sqlite', // Exclude database.sqlite 38 | `${zipFilePrefix}*.zip` // Exclude previous zip files 39 | ] 40 | }); 41 | 42 | // Finalize the archive 43 | archive.finalize(); 44 | -------------------------------------------------------------------------------- /models/index.js: -------------------------------------------------------------------------------- 1 | import { Sequelize } from 'sequelize'; 2 | import sqlJsAsSqlite3 from 'sql.js-as-sqlite3'; 3 | import fs from 'fs'; 4 | 5 | const isUsingRDS = process.env.RDS_HOSTNAME && process.env.RDS_USERNAME && process.env.RDS_PASSWORD; 6 | const dbType = process.env.DB_TYPE || 'mysql'; 7 | const defaultPorts = { 8 | mysql: 3306, 9 | postgres: 5432, 10 | }; 11 | const defaultPort = defaultPorts[dbType]; 12 | 13 | export let sequelize; 14 | 15 | if (isUsingRDS) { 16 | sequelize = new Sequelize({ 17 | database: process.env.RDS_DB_NAME, 18 | username: process.env.RDS_USERNAME, 19 | password: process.env.RDS_PASSWORD, 20 | host: process.env.RDS_HOSTNAME, 21 | port: process.env.RDS_PORT || defaultPort, 22 | dialect: dbType, 23 | logging: false 24 | }); 25 | } else { 26 | sequelize = new Sequelize({ 27 | dialect: 'sqlite', 28 | dialectModule: sqlJsAsSqlite3, 29 | logging: false 30 | }); 31 | 32 | // Save database to file after write operations. 33 | sequelize.addHook('afterCreate', saveDatabaseToFile); 34 | sequelize.addHook('afterDestroy', saveDatabaseToFile); 35 | sequelize.addHook('afterUpdate', saveDatabaseToFile); 36 | sequelize.addHook('afterSave', saveDatabaseToFile); 37 | sequelize.addHook('afterUpsert', saveDatabaseToFile); 38 | sequelize.addHook('afterBulkCreate', saveDatabaseToFile); 39 | sequelize.addHook('afterBulkDestroy', saveDatabaseToFile); 40 | sequelize.addHook('afterBulkUpdate', saveDatabaseToFile); 41 | } 42 | 43 | export async function saveDatabaseToFile() { 44 | const dbInstance = await sequelize.connectionManager.getConnection(); 45 | const binaryArray = dbInstance.database.export(); 46 | const buffer = Buffer.from(binaryArray); 47 | fs.writeFileSync('database.sqlite', buffer); 48 | } 49 | -------------------------------------------------------------------------------- /exercise-solutions/1e.md: -------------------------------------------------------------------------------- 1 | ``` 2 | Prompt: 3 | How do I add a new product to the database? 4 | ``` 5 | 6 | - Copilot creates a new API POST /api/products for adding a new product to the database. 7 | 8 | - This is not what I want. I just want to add 1 new product to the database once. 9 | 10 | - I realize I don't need new code in my backend, I just need some code to run once to add something to my database. Therefore, I switch to Copilot Chat. 11 | 12 | ``` 13 | Prompt to Copilot Chat: 14 | I have a product model in my code. Using 15 | the product model, how do I add a new 16 | product to the database? 17 | ``` 18 | 19 | - Copilot Chat responds with how to create a POST /api/products API, which is not what I want. 20 | 21 | ``` 22 | Prompt to Copilot Chat: 23 | No I don't want to create a new API, I 24 | just want to add a new product to the 25 | database once. 26 | ``` 27 | 28 | - Copilot Chat correctly gives me a script that I can run to add to the database once. See the code changes below. 29 | 30 | ### Code Changes 31 | https://github.com/SuperSimpleDev/ecommerce-backend-ai/pull/4/files 32 | 33 | - Copilot gave me a very rough script for adding a product to the database. It used the Product model correctly, but several properties were missing / wrong. 34 | 35 | - I manually filled in the properties of the product with a sample product, a tennis shoe, and gave it keywords `["apparel", "tennis shoes"]` 36 | 37 | - I downloaded an image of a tennis shoe and put it in the images/ folder. 38 | 39 | - I ran the script it created using `node scripts/addProduct.js`. This adds the product to the database once. 40 | 41 | - I added the product to the defaultProducts array so if I reset the backend, my new product will be added automatically. 42 | 43 | - I tested with Postman `?search=apprl` and `?search=tennis shoe`. It returned the tennis shoe. 44 | -------------------------------------------------------------------------------- /routes/reset.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { sequelize } from '../models/index.js'; 3 | import { Product } from '../models/Product.js'; 4 | import { DeliveryOption } from '../models/DeliveryOption.js'; 5 | import { CartItem } from '../models/CartItem.js'; 6 | import { Order } from '../models/Order.js'; 7 | import { defaultProducts } from '../defaultData/defaultProducts.js'; 8 | import { defaultDeliveryOptions } from '../defaultData/defaultDeliveryOptions.js'; 9 | import { defaultCart } from '../defaultData/defaultCart.js'; 10 | import { defaultOrders } from '../defaultData/defaultOrders.js'; 11 | 12 | const router = express.Router(); 13 | 14 | router.post('/', async (req, res) => { 15 | await sequelize.sync({ force: true }); 16 | 17 | const timestamp = Date.now(); 18 | 19 | const productsWithTimestamps = defaultProducts.map((product, index) => ({ 20 | ...product, 21 | createdAt: new Date(timestamp + index), 22 | updatedAt: new Date(timestamp + index) 23 | })); 24 | 25 | const deliveryOptionsWithTimestamps = defaultDeliveryOptions.map((option, index) => ({ 26 | ...option, 27 | createdAt: new Date(timestamp + index), 28 | updatedAt: new Date(timestamp + index) 29 | })); 30 | 31 | const cartItemsWithTimestamps = defaultCart.map((item, index) => ({ 32 | ...item, 33 | createdAt: new Date(timestamp + index), 34 | updatedAt: new Date(timestamp + index) 35 | })); 36 | 37 | const ordersWithTimestamps = defaultOrders.map((order, index) => ({ 38 | ...order, 39 | createdAt: new Date(timestamp + index), 40 | updatedAt: new Date(timestamp + index) 41 | })); 42 | 43 | await Product.bulkCreate(productsWithTimestamps); 44 | await DeliveryOption.bulkCreate(deliveryOptionsWithTimestamps); 45 | await CartItem.bulkCreate(cartItemsWithTimestamps); 46 | await Order.bulkCreate(ordersWithTimestamps); 47 | 48 | res.status(204).send(); 49 | }); 50 | 51 | export default router; 52 | -------------------------------------------------------------------------------- /patches/sql.js-as-sqlite3+0.2.1.patch: -------------------------------------------------------------------------------- 1 | diff --git a/node_modules/sql.js-as-sqlite3/modules/Database.js b/node_modules/sql.js-as-sqlite3/modules/Database.js 2 | index 87a9034..3486cec 100644 3 | --- a/node_modules/sql.js-as-sqlite3/modules/Database.js 4 | +++ b/node_modules/sql.js-as-sqlite3/modules/Database.js 5 | @@ -12,6 +12,8 @@ function _getPrototypeOf(o) { _getPrototypeOf = Object.setPrototypeOf ? Object.g 6 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function"); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, writable: true, configurable: true } }); Object.defineProperty(subClass, "prototype", { writable: false }); if (superClass) _setPrototypeOf(subClass, superClass); } 7 | function _setPrototypeOf(o, p) { _setPrototypeOf = Object.setPrototypeOf ? Object.setPrototypeOf.bind() : function _setPrototypeOf(o, p) { o.__proto__ = p; return o; }; return _setPrototypeOf(o, p); } 8 | import EventEmitter from 'eventemitter3'; 9 | +import fs from 'fs'; 10 | +import path from 'path'; 11 | import getNextTickFunction from './getNextTickFunction.js'; 12 | var config = {}; 13 | export function configure(_ref) { 14 | @@ -137,7 +139,16 @@ var Database = /*#__PURE__*/function (_EventEmitter) { 15 | } 16 | }).then(function (SQL) { 17 | // Create a database. 18 | - _this.database = new SQL.Database(); 19 | + const databaseFile = path.join(process.cwd(), 'database.sqlite'); 20 | + if (fs.existsSync(databaseFile)) { 21 | + console.log('Loaded db from database.sqlite'); 22 | + const fileBuffer = fs.readFileSync(databaseFile); 23 | + _this.database = new SQL.Database(fileBuffer); 24 | + } else { 25 | + console.log('Loaded fresh db'); 26 | + _this.database = new SQL.Database(); 27 | + } 28 | + 29 | onSuccess(); 30 | }, onError); 31 | }, onError); 32 | -------------------------------------------------------------------------------- /troubleshooting.md: -------------------------------------------------------------------------------- 1 | ## Troubleshooting Steps 2 | AI generates different code each time so you might run into different problems than me.
3 | Here's what to do: 4 | 5 | ## Copy-Paste the Error Into the AI 6 | 1. If you encounter an error, for example.
7 | 8 | 9 | 2. Copy-paste the error directly into the AI and ask what it means.
10 | Image 11 | 12 | 3. If the AI responds and you don't understand something, ask it for more details.
13 | Image 14 | 15 | 4. You can also just ask the AI to try and fix the error. Review the changes it made and see if the changes make sense.
16 | Image 17 | 18 | 5. For the best learning experience, it's better to ask the AI about the error first and try to understand what it means. Then ask the AI to fix it. 19 | 20 | ## Still Have Issues? Download My Code 21 | If you still cannot fix the issue, you can download a copy of my code and start from there. 22 | 23 | 1. Open this link: https://github.com/SuperSimpleDev/ecommerce-backend-ai/commits/main/. This will give you a list of Git Commits I made in the video. 24 | 25 | 2. In the video, go backwards and find the most recent Commit I made (before you ran into your issue). 26 | Image 27 | 28 | 3. Find the Commit in the list of Git Commits and click "Browse repository at this point"
29 | 30 | 31 | 4. This will open the code at that Commit (at that point in time). Click `Code` > `Download ZIP`.
32 | 33 | 34 | 5. Stop any other backends running on port 3000 (find the command line the other backend is running in and press Ctrl + C). 35 | 36 | 6. Unzip the code you downloaded earlier and open the folder in VSCode. 37 | 38 | 7. Open the Command Line by clicking `Terminal` > `New Terminal`. 39 | 40 | 8. Run `npm install`, and run `node server.js`. Now you can start from this Commit in the video. 41 | -------------------------------------------------------------------------------- /routes/cartItems.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { CartItem } from '../models/CartItem.js'; 3 | import { Product } from '../models/Product.js'; 4 | import { DeliveryOption } from '../models/DeliveryOption.js'; 5 | 6 | const router = express.Router(); 7 | 8 | router.get('/', async (req, res) => { 9 | const expand = req.query.expand; 10 | let cartItems = await CartItem.findAll(); 11 | 12 | if (expand === 'product') { 13 | cartItems = await Promise.all(cartItems.map(async (item) => { 14 | const product = await Product.findByPk(item.productId); 15 | return { 16 | ...item.toJSON(), 17 | product 18 | }; 19 | })); 20 | } 21 | 22 | res.json(cartItems); 23 | }); 24 | 25 | router.post('/', async (req, res) => { 26 | const { productId, quantity } = req.body; 27 | 28 | const product = await Product.findByPk(productId); 29 | if (!product) { 30 | return res.status(400).json({ error: 'Product not found' }); 31 | } 32 | 33 | if (typeof quantity !== 'number' || quantity < 1 || quantity > 10) { 34 | return res.status(400).json({ error: 'Quantity must be a number between 1 and 10' }); 35 | } 36 | 37 | let cartItem = await CartItem.findOne({ where: { productId } }); 38 | if (cartItem) { 39 | cartItem.quantity += quantity; 40 | await cartItem.save(); 41 | } else { 42 | cartItem = await CartItem.create({ productId, quantity, deliveryOptionId: "1" }); 43 | } 44 | 45 | res.status(201).json(cartItem); 46 | }); 47 | 48 | router.put('/:productId', async (req, res) => { 49 | const { productId } = req.params; 50 | const { quantity, deliveryOptionId } = req.body; 51 | 52 | const cartItem = await CartItem.findOne({ where: { productId } }); 53 | if (!cartItem) { 54 | return res.status(404).json({ error: 'Cart item not found' }); 55 | } 56 | 57 | if (quantity !== undefined) { 58 | if (typeof quantity !== 'number' || quantity < 1) { 59 | return res.status(400).json({ error: 'Quantity must be a number greater than 0' }); 60 | } 61 | cartItem.quantity = quantity; 62 | } 63 | 64 | if (deliveryOptionId !== undefined) { 65 | const deliveryOption = await DeliveryOption.findByPk(deliveryOptionId); 66 | if (!deliveryOption) { 67 | return res.status(400).json({ error: 'Invalid delivery option' }); 68 | } 69 | cartItem.deliveryOptionId = deliveryOptionId; 70 | } 71 | 72 | await cartItem.save(); 73 | res.json(cartItem); 74 | }); 75 | 76 | router.delete('/:productId', async (req, res) => { 77 | const { productId } = req.params; 78 | 79 | const cartItem = await CartItem.findOne({ where: { productId } }); 80 | if (!cartItem) { 81 | return res.status(404).json({ error: 'Cart item not found' }); 82 | } 83 | 84 | await cartItem.destroy(); 85 | res.status(204).send(); 86 | }); 87 | 88 | export default router; 89 | -------------------------------------------------------------------------------- /exercise-solutions/1d.md: -------------------------------------------------------------------------------- 1 | ``` 2 | Prompt: 3 | Update GET /api/products to support fuzzy search. 4 | Fuzzy search means if I mispell a word like 5 | "bsktbll" for "basketball", it will still match 6 | "basketball" 7 | ``` 8 | 9 | - Copilot creates a solution that use the `ILIKE` feature in SQLite. However, running the backend causes an error. 10 | 11 | ``` 12 | Prompt: 13 | I got an error: 14 | Error: SQLITE_ERROR: near "ILIKE": syntax error 15 | ``` 16 | 17 | - Copilot attempts to use an alternative solution that uses case-insensitive matching in SQLite to search the text ("BASKETBALL" matches "basketball"). 18 | 19 | - However, I realize early that this does not solve the problem I want, which is to handle mispellings (I want "bsktball" to match "basketball"). 20 | 21 | ``` 22 | Prompt: 23 | I want a search that handles mispellings, not 24 | just case-insensitivity. 25 | ``` 26 | 27 | - Copilot creates a solution that uses an npm package fuzzysort to do the fuzzy searching. 28 | 29 | - In the command line, I run `npm install fuzzysort` 30 | 31 | - In Postman, I test with `?search=bsktball` and it works 32 | 33 | - I test with `?search=apprl` and it doesn't work 34 | 35 | - I take a look at the code, and realize it's trying to use fuzzysort on keywords, but keywords is an array. 36 | 37 | ``` 38 | Prompt: 39 | This solution does not work with keywords, which 40 | is an array not a string. 41 | ``` 42 | 43 | - Copilot creates a solution that transforms the keywords array into a string (by combining the keywords with `,`). 44 | 45 | - However, when I get the products from the GET /api/products API, the keyword property is now a string instead of an array. 46 | 47 | ``` 48 | Prompt: 49 | This actually modifies the product's keywords array 50 | and turns it into a string. Can we avoid this? 51 | ``` 52 | 53 | - Copilot creates a solution that saves the combined keywords string into a new property called keywordString. 54 | 55 | - However, when I get the products from the GET /api/products API, the products no longer have the keywords property, and only have the keywordString property. 56 | 57 | ``` 58 | Prompt: 59 | No, I don't want to modify the original product 60 | object. This creates a new property called 61 | keywordString, but the original object has a 62 | keywords property that is an array of strings. 63 | ``` 64 | 65 | - Copilot generates a somewhat correct solution that involves creating a new products array specifically for searching and leaving the original products array untouched. 66 | 67 | ### Code Changes 68 | https://github.com/SuperSimpleDev/ecommerce-backend-ai/pull/3/files 69 | 70 | - I add some comments to make the code more clear and clean up the code a bit (break some long lines of code into multiple lines). 71 | 72 | - The final code works, but there are some issues. For example, the code combines the keywords before searching. This means a keywords array like `["apparel", "shoes"]` will be combined into `"apparel,shoes"` and a search text like "relsho" would match the end of "apparel" and the start of "shoes". 73 | 74 | - The ideal solution would probably be to use a different technology for searching, but this exercise just gives you an example of interacting with the AI back-and-forth. -------------------------------------------------------------------------------- /routes/orders.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import { Order } from '../models/Order.js'; 3 | import { Product } from '../models/Product.js'; 4 | import { DeliveryOption } from '../models/DeliveryOption.js'; 5 | import { CartItem } from '../models/CartItem.js'; 6 | 7 | const router = express.Router(); 8 | 9 | router.get('/', async (req, res) => { 10 | const expand = req.query.expand; 11 | let orders = await Order.unscoped().findAll({ order: [['orderTimeMs', 'DESC']] }); // Sort by most recent 12 | 13 | if (expand === 'products') { 14 | orders = await Promise.all(orders.map(async (order) => { 15 | const products = await Promise.all(order.products.map(async (product) => { 16 | const productDetails = await Product.findByPk(product.productId); 17 | return { 18 | ...product, 19 | product: productDetails 20 | }; 21 | })); 22 | return { 23 | ...order.toJSON(), 24 | products 25 | }; 26 | })); 27 | } 28 | 29 | res.json(orders); 30 | }); 31 | 32 | router.post('/', async (req, res) => { 33 | const cartItems = await CartItem.findAll(); 34 | 35 | if (cartItems.length === 0) { 36 | return res.status(400).json({ error: 'Cart is empty' }); 37 | } 38 | 39 | let totalCostCents = 0; 40 | const products = await Promise.all(cartItems.map(async (item) => { 41 | const product = await Product.findByPk(item.productId); 42 | if (!product) { 43 | throw new Error(`Product not found: ${item.productId}`); 44 | } 45 | const deliveryOption = await DeliveryOption.findByPk(item.deliveryOptionId); 46 | if (!deliveryOption) { 47 | throw new Error(`Invalid delivery option: ${item.deliveryOptionId}`); 48 | } 49 | const productCost = product.priceCents * item.quantity; 50 | const shippingCost = deliveryOption.priceCents; 51 | totalCostCents += productCost + shippingCost; 52 | const estimatedDeliveryTimeMs = Date.now() + deliveryOption.deliveryDays * 24 * 60 * 60 * 1000; 53 | return { 54 | productId: item.productId, 55 | quantity: item.quantity, 56 | estimatedDeliveryTimeMs 57 | }; 58 | })); 59 | 60 | totalCostCents = Math.round(totalCostCents * 1.1); 61 | 62 | const order = await Order.create({ 63 | orderTimeMs: Date.now(), 64 | totalCostCents, 65 | products 66 | }); 67 | 68 | await CartItem.destroy({ where: {} }); 69 | 70 | res.status(201).json(order); 71 | }); 72 | 73 | router.get('/:orderId', async (req, res) => { 74 | const { orderId } = req.params; 75 | const expand = req.query.expand; 76 | 77 | let order = await Order.findByPk(orderId); 78 | if (!order) { 79 | return res.status(404).json({ error: 'Order not found' }); 80 | } 81 | 82 | if (expand === 'products') { 83 | const products = await Promise.all(order.products.map(async (product) => { 84 | const productDetails = await Product.findByPk(product.productId); 85 | return { 86 | ...product, 87 | product: productDetails 88 | }; 89 | })); 90 | order = { 91 | ...order.toJSON(), 92 | products 93 | }; 94 | } 95 | 96 | res.json(order); 97 | }); 98 | 99 | export default router; 100 | -------------------------------------------------------------------------------- /server.js: -------------------------------------------------------------------------------- 1 | import express from 'express'; 2 | import cors from 'cors'; 3 | import path from 'path'; 4 | import { fileURLToPath } from 'url'; 5 | import { sequelize } from './models/index.js'; 6 | import productRoutes from './routes/products.js'; 7 | import deliveryOptionRoutes from './routes/deliveryOptions.js'; 8 | import cartItemRoutes from './routes/cartItems.js'; 9 | import orderRoutes from './routes/orders.js'; 10 | import resetRoutes from './routes/reset.js'; 11 | import paymentSummaryRoutes from './routes/paymentSummary.js'; 12 | import { Product } from './models/Product.js'; 13 | import { DeliveryOption } from './models/DeliveryOption.js'; 14 | import { CartItem } from './models/CartItem.js'; 15 | import { Order } from './models/Order.js'; 16 | import { defaultProducts } from './defaultData/defaultProducts.js'; 17 | import { defaultDeliveryOptions } from './defaultData/defaultDeliveryOptions.js'; 18 | import { defaultCart } from './defaultData/defaultCart.js'; 19 | import { defaultOrders } from './defaultData/defaultOrders.js'; 20 | import fs from 'fs'; 21 | 22 | const app = express(); 23 | const PORT = process.env.PORT || 3000; 24 | const __filename = fileURLToPath(import.meta.url); 25 | const __dirname = path.dirname(__filename); 26 | 27 | // Middleware 28 | app.use(cors()); 29 | app.use(express.json()); 30 | 31 | // Serve images from the images folder 32 | app.use('/images', express.static(path.join(__dirname, 'images'))); 33 | 34 | // Use routes 35 | app.use('/api/products', productRoutes); 36 | app.use('/api/delivery-options', deliveryOptionRoutes); 37 | app.use('/api/cart-items', cartItemRoutes); 38 | app.use('/api/orders', orderRoutes); 39 | app.use('/api/reset', resetRoutes); 40 | app.use('/api/payment-summary', paymentSummaryRoutes); 41 | 42 | // Serve static files from the dist folder 43 | app.use(express.static(path.join(__dirname, 'dist'))); 44 | 45 | // Catch-all route to serve index.html for any unmatched routes 46 | app.get('*', (req, res) => { 47 | const indexPath = path.join(__dirname, 'dist', 'index.html'); 48 | if (fs.existsSync(indexPath)) { 49 | res.sendFile(indexPath); 50 | } else { 51 | res.status(404).send('index.html not found'); 52 | } 53 | }); 54 | 55 | // Error handling middleware 56 | /* eslint-disable no-unused-vars */ 57 | app.use((err, req, res, next) => { 58 | console.error(err.stack); 59 | res.status(500).json({ error: 'Something went wrong!' }); 60 | }); 61 | /* eslint-enable no-unused-vars */ 62 | 63 | // Sync database and load default data if none exist 64 | await sequelize.sync(); 65 | 66 | const productCount = await Product.count(); 67 | if (productCount === 0) { 68 | const timestamp = Date.now(); 69 | 70 | const productsWithTimestamps = defaultProducts.map((product, index) => ({ 71 | ...product, 72 | createdAt: new Date(timestamp + index), 73 | updatedAt: new Date(timestamp + index) 74 | })); 75 | 76 | const deliveryOptionsWithTimestamps = defaultDeliveryOptions.map((option, index) => ({ 77 | ...option, 78 | createdAt: new Date(timestamp + index), 79 | updatedAt: new Date(timestamp + index) 80 | })); 81 | 82 | const cartItemsWithTimestamps = defaultCart.map((item, index) => ({ 83 | ...item, 84 | createdAt: new Date(timestamp + index), 85 | updatedAt: new Date(timestamp + index) 86 | })); 87 | 88 | const ordersWithTimestamps = defaultOrders.map((order, index) => ({ 89 | ...order, 90 | createdAt: new Date(timestamp + index), 91 | updatedAt: new Date(timestamp + index) 92 | })); 93 | 94 | await Product.bulkCreate(productsWithTimestamps); 95 | await DeliveryOption.bulkCreate(deliveryOptionsWithTimestamps); 96 | await CartItem.bulkCreate(cartItemsWithTimestamps); 97 | await Order.bulkCreate(ordersWithTimestamps); 98 | 99 | console.log('Default data added to the database.'); 100 | } 101 | 102 | // Start server 103 | app.listen(PORT, () => { 104 | console.log(`Server is running on port ${PORT}`); 105 | }); 106 | -------------------------------------------------------------------------------- /documentation.md: -------------------------------------------------------------------------------- 1 | ## E-Commerce Backend Documentation 2 | Here's a list of all the URL Paths you can use with this backend and what each URL Path does. 3 | 4 | **Products, Delivery Options** 5 | - [GET /api/products](#get-apiproducts) 6 | - [GET /api/delivery-options](#get-apidelivery-options) 7 | 8 | **Cart** 9 | - [GET /api/cart-items](#get-apicart-items) 10 | - [POST /api/cart-items](#post-apicart-items) 11 | - [PUT /api/cart-items/:productId](#put-apicart-itemsproductid) 12 | - [DELETE /api/cart-items/:productId](#delete-apicart-itemsproductid) 13 | 14 | **Orders** 15 | - [GET /api/orders](#get-apiorders) 16 | - [POST /api/orders](#post-apiorders) 17 | - [GET /api/orders/:orderId](#get-apiordersorderid) 18 | 19 | **Payment Summary, Reset** 20 | - [GET /api/payment-summary](#get-apipayment-summary) 21 | - [POST /api/reset](#post-apireset) 22 | 23 | ## GET /api/products 24 | Returns a list of products. 25 | 26 | **Query Parameters:** 27 | - `search=...` (optional): Search term to find products by name or keywords 28 | 29 | **Response:** 30 | ```js 31 | [ 32 | { 33 | "id": "uuid", 34 | "image": "string", 35 | "name": "string", 36 | "rating": { 37 | "stars": "number", 38 | "count": "number" 39 | }, 40 | "priceCents": "number", 41 | "keywords": ["string"] 42 | } 43 | ] 44 | ``` 45 | 46 | ## GET /api/delivery-options 47 | Returns a list of all delivery options. 48 | 49 | **Query Parameters:** 50 | - `expand=estimatedDeliveryTime` (optional): includes estimated delivery times 51 | 52 | **Response:** 53 | ```js 54 | [ 55 | { 56 | "id": "string", 57 | "deliveryDays": "number", 58 | "priceCents": "number", 59 | // Only included when expand=estimatedDeliveryTime 60 | "estimatedDeliveryTimeMs": "number" 61 | } 62 | ] 63 | ``` 64 | 65 | ## GET /api/cart-items 66 | Returns all items in the cart. 67 | 68 | **Query Parameters:** 69 | - `expand=product` (optional): include full product details 70 | 71 | **Response:** 72 | ```js 73 | [ 74 | { 75 | "productId": "uuid", 76 | "quantity": "number", 77 | "deliveryOptionId": "string", 78 | // product object, only when expand=product 79 | "product": "object" 80 | } 81 | ] 82 | ``` 83 | 84 | ## POST /api/cart-items 85 | Adds a product to the cart. 86 | 87 | **Request:** 88 | ```js 89 | { 90 | "productId": "uuid", 91 | // Must be between 1 and 10 92 | "quantity": "number" 93 | } 94 | ``` 95 | 96 | **Response:** 97 | ```js 98 | { 99 | "productId": "uuid", 100 | "quantity": "number", 101 | "deliveryOptionId": "string", 102 | } 103 | ``` 104 | 105 | ## PUT /api/cart-items/:productId 106 | Updates a cart item. 107 | 108 | **URL Parameters:** 109 | - `productId`: ID of the product to update 110 | 111 | **Request:** 112 | ```js 113 | { 114 | // Optional, must be ≥ 1 115 | "quantity": "number", 116 | 117 | // Optional 118 | "deliveryOptionId": "string" 119 | } 120 | ``` 121 | 122 | **Response:** 123 | ```js 124 | { 125 | "productId": "uuid", 126 | "quantity": "number", 127 | "deliveryOptionId": "string", 128 | } 129 | ``` 130 | 131 | ## DELETE /api/cart-items/:productId 132 | Removes an item from the cart. 133 | 134 | **URL Parameters:** 135 | - `productId`: ID of the product to remove 136 | 137 | **Response:** 138 | - Status: 204 (No response) 139 | 140 | ## GET /api/orders 141 | Returns all orders, sorted by most recent first. 142 | 143 | **Query Parameters:** 144 | - `expand=products` (optional): include full product details 145 | 146 | **Response:** 147 | ```js 148 | [ 149 | { 150 | "id": "uuid", 151 | "orderTimeMs": "number", 152 | "totalCostCents": "number", 153 | "products": [ 154 | { 155 | "productId": "uuid", 156 | "quantity": "number", 157 | "estimatedDeliveryTimeMs": "number", 158 | // product object, only when expand=products 159 | "product": "object" 160 | } 161 | ] 162 | } 163 | ] 164 | ``` 165 | 166 | ## POST /api/orders 167 | Creates a new order from the current cart items. 168 | 169 | **Response:** 170 | ```js 171 | { 172 | "id": "uuid", 173 | "orderTimeMs": "number", 174 | "totalCostCents": "number", 175 | "products": [ 176 | { 177 | "productId": "uuid", 178 | "quantity": "number", 179 | "estimatedDeliveryTimeMs": "number", 180 | } 181 | ] 182 | } 183 | ``` 184 | - Side effect: Cart is emptied 185 | 186 | ## GET /api/orders/:orderId 187 | Returns a specific order. 188 | 189 | **URL Parameters:** 190 | - `orderId`: ID of the order 191 | 192 | **Query Parameters:** 193 | - `expand=products` (optional): include full product details 194 | 195 | **Response:** 196 | ```js 197 | { 198 | "id": "uuid", 199 | "orderTimeMs": "number", 200 | "totalCostCents": "number", 201 | "products": [ 202 | { 203 | "productId": "uuid", 204 | "quantity": "number", 205 | "estimatedDeliveryTimeMs": "number", 206 | // product object, only when expand=products 207 | "product": "object" 208 | } 209 | ] 210 | } 211 | ``` 212 | 213 | ## GET /api/payment-summary 214 | Calculates and returns the payment summary for the current cart. 215 | 216 | **Response:** 217 | ```js 218 | { 219 | "totalItems": "number", 220 | "productCostCents": "number", 221 | "shippingCostCents": "number", 222 | "totalCostBeforeTaxCents": "number", 223 | "taxCents": "number", 224 | "totalCostCents": "number" 225 | } 226 | ``` 227 | 228 | ## POST /api/reset 229 | Resets the database to its default state. 230 | 231 | **Response:** 232 | - Status: 204 No Response 233 | -------------------------------------------------------------------------------- /backend/products.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "id": "e43638ce-6aa0-4b85-b27f-e1d07eb678c6", 4 | "image": "images/products/athletic-cotton-socks-6-pairs.jpg", 5 | "name": "Black and Gray Athletic Cotton Socks - 6 Pairs", 6 | "rating": { 7 | "stars": 4.5, 8 | "count": 87 9 | }, 10 | "priceCents": 1090, 11 | "keywords": ["socks", "sports", "apparel"] 12 | }, 13 | { 14 | "id": "15b6fc6f-327a-4ec4-896f-486349e85a3d", 15 | "image": "images/products/intermediate-composite-basketball.jpg", 16 | "name": "Intermediate Size Basketball", 17 | "rating": { 18 | "stars": 4, 19 | "count": 127 20 | }, 21 | "priceCents": 2095, 22 | "keywords": ["sports", "basketballs"] 23 | }, 24 | { 25 | "id": "83d4ca15-0f35-48f5-b7a3-1ea210004f2e", 26 | "image": "images/products/adults-plain-cotton-tshirt-2-pack-teal.jpg", 27 | "name": "Adults Plain Cotton T-Shirt - 2 Pack", 28 | "rating": { 29 | "stars": 4.5, 30 | "count": 56 31 | }, 32 | "priceCents": 799, 33 | "keywords": ["tshirts", "apparel", "mens"] 34 | }, 35 | { 36 | "id": "54e0eccd-8f36-462b-b68a-8182611d9add", 37 | "image": "images/products/2-slot-toaster-white.jpg", 38 | "name": "2 Slot Toaster - White", 39 | "rating": { 40 | "stars": 5, 41 | "count": 2197 42 | }, 43 | "priceCents": 1899, 44 | "keywords": ["toaster", "kitchen", "appliances"] 45 | }, 46 | { 47 | "id": "3ebe75dc-64d2-4137-8860-1f5a963e534b", 48 | "image": "images/products/elegant-white-dinner-plate-set.jpg", 49 | "name": "2 Piece White Dinner Plate Set", 50 | "rating": { 51 | "stars": 4, 52 | "count": 37 53 | }, 54 | "priceCents": 2067, 55 | "keywords": ["plates", "kitchen", "dining"] 56 | }, 57 | { 58 | "id": "8c9c52b5-5a19-4bcb-a5d1-158a74287c53", 59 | "image": "images/products/3-piece-cooking-set.jpg", 60 | "name": "3 Piece Non-Stick, Black Cooking Pot Set", 61 | "rating": { 62 | "stars": 4.5, 63 | "count": 175 64 | }, 65 | "priceCents": 3499, 66 | "keywords": ["kitchen", "cookware"] 67 | }, 68 | { 69 | "id": "dd82ca78-a18b-4e2a-9250-31e67412f98d", 70 | "image": "images/products/women-plain-cotton-oversized-sweater-gray.jpg", 71 | "name": "Cotton Oversized Sweater - Gray", 72 | "rating": { 73 | "stars": 4.5, 74 | "count": 317 75 | }, 76 | "priceCents": 2400, 77 | "keywords": ["sweaters", "apparel"] 78 | }, 79 | { 80 | "id": "77919bbe-0e56-475b-adde-4f24dfed3a04", 81 | "image": "images/products/luxury-towel-set.jpg", 82 | "name": "2 Piece Luxury Towel Set - White", 83 | "rating": { 84 | "stars": 4.5, 85 | "count": 144 86 | }, 87 | "priceCents": 3599, 88 | "keywords": ["bathroom", "washroom", "restroom", "towels", "bath towels"] 89 | }, 90 | { 91 | "id": "6b07d4e7-f540-454e-8a1e-363f25dbae7d", 92 | "image": "images/products/facial-tissue-2-ply-8-boxes.jpg", 93 | "name": "Ultra Soft Tissue 2-Ply - 8 Boxes", 94 | "rating": { 95 | "stars": 4, 96 | "count": 99 97 | }, 98 | "priceCents": 2374, 99 | "keywords": ["kleenex", "tissues", "kitchen", "napkins"] 100 | }, 101 | { 102 | "id": "5968897c-4d27-4872-89f6-5bcb052746d7", 103 | "image": "images/products/women-striped-beach-dress.jpg", 104 | "name": "Women's Striped Beach Dress", 105 | "rating": { 106 | "stars": 4.5, 107 | "count": 235 108 | }, 109 | "priceCents": 2970, 110 | "keywords": ["robe", "swimsuit", "swimming", "bathing", "apparel"] 111 | }, 112 | { 113 | "id": "b86ddc8b-3501-4b17-9889-a3bad6fb585f", 114 | "image": "images/products/women-sandal-heels-white-pink.jpg", 115 | "name": "Women's Sandal Heels - Pink", 116 | "rating": { 117 | "stars": 4.5, 118 | "count": 2286 119 | }, 120 | "priceCents": 5300, 121 | "keywords": ["womens", "shoes", "heels", "sandals"] 122 | }, 123 | { 124 | "id": "aad29d11-ea98-41ee-9285-b916638cac4a", 125 | "image": "images/products/round-sunglasses-gold.jpg", 126 | "name": "Round Sunglasses", 127 | "rating": { 128 | "stars": 4.5, 129 | "count": 30 130 | }, 131 | "priceCents": 3560, 132 | "keywords": ["accessories", "shades"] 133 | }, 134 | { 135 | "id": "901eb2ca-386d-432e-82f0-6fb1ee7bf969", 136 | "image": "images/products/blackout-curtain-set-beige.jpg", 137 | "name": "Blackout Curtains Set - Beige", 138 | "rating": { 139 | "stars": 4.5, 140 | "count": 232 141 | }, 142 | "priceCents": 4599, 143 | "keywords": ["bedroom", "curtains", "home"] 144 | }, 145 | { 146 | "id": "82bb68d7-ebc9-476a-989c-c78a40ee5cd9", 147 | "image": "images/products/women-summer-jean-shorts.jpg", 148 | "name": "Women's Summer Jean Shorts", 149 | "rating": { 150 | "stars": 4, 151 | "count": 160 152 | }, 153 | "priceCents": 1699, 154 | "keywords": ["shorts", "apparel", "womens"] 155 | }, 156 | { 157 | "id": "c2a82c5e-aff4-435f-9975-517cfaba2ece", 158 | "image": "images/products/electric-steel-hot-water-kettle-white.jpg", 159 | "name": "Electric Hot Water Kettle - White", 160 | "rating": { 161 | "stars": 5, 162 | "count": 846 163 | }, 164 | "priceCents": 5074, 165 | "keywords": ["water kettle", "appliances", "kitchen"] 166 | }, 167 | { 168 | "id": "58b4fc92-e98c-42aa-8c55-b6b79996769a", 169 | "image": "images/products/knit-athletic-sneakers-gray.jpg", 170 | "name": "Waterproof Knit Athletic Sneakers - Gray", 171 | "rating": { 172 | "stars": 4, 173 | "count": 89 174 | }, 175 | "priceCents": 5390, 176 | "keywords": ["shoes", "running shoes", "footwear"] 177 | }, 178 | { 179 | "id": "a82c6bac-3067-4e68-a5ba-d827ac0be010", 180 | "image": "images/products/straw-sunhat.jpg", 181 | "name": "Straw Wide Brim Sun Hat", 182 | "rating": { 183 | "stars": 4, 184 | "count": 215 185 | }, 186 | "priceCents": 2200, 187 | "keywords": ["hats", "straw hats", "summer", "apparel"] 188 | }, 189 | { 190 | "id": "1c079479-8586-494f-ab53-219325432536", 191 | "image": "images/products/men-athletic-shoes-white.jpg", 192 | "name": "Men's Athletic Sneaker - White", 193 | "rating": { 194 | "stars": 4, 195 | "count": 229 196 | }, 197 | "priceCents": 4590, 198 | "keywords": ["shoes", "running shoes", "footwear", "mens"] 199 | }, 200 | { 201 | "id": "b0f17cc5-8b40-4ca5-9142-b61fe3d98c85", 202 | "image": "images/products/men-stretch-wool-sweater-black.jpg", 203 | "name": "Men's Wool Sweater - Black", 204 | "rating": { 205 | "stars": 4.5, 206 | "count": 2465 207 | }, 208 | "priceCents": 3374, 209 | "keywords": ["sweaters", "apparel"] 210 | }, 211 | { 212 | "id": "a93a101d-79ef-4cf3-a6cf-6dbe532a1b4a", 213 | "image": "images/products/bathroom-mat.jpg", 214 | "name": "Bathroom Bath Mat 16 x 32 Inch - Grey", 215 | "rating": { 216 | "stars": 4.5, 217 | "count": 119 218 | }, 219 | "priceCents": 1850, 220 | "keywords": ["bathmat", "bathroom", "home"] 221 | }, 222 | { 223 | "id": "4f4fbcc2-4e72-45cc-935c-9e13d79cc57f", 224 | "image": "images/products/women-knit-ballet-flat-white.jpg", 225 | "name": "Women's Ballet Flat - White", 226 | "rating": { 227 | "stars": 4, 228 | "count": 326 229 | }, 230 | "priceCents": 2640, 231 | "keywords": ["shoes", "flats", "womens", "footwear"] 232 | }, 233 | { 234 | "id": "8b5a2ee1-6055-422a-a666-b34ba28b76d4", 235 | "image": "images/products/men-golf-polo-t-shirt-gray.jpg", 236 | "name": "Men's Golf Polo Shirt - Gray", 237 | "rating": { 238 | "stars": 4.5, 239 | "count": 2556 240 | }, 241 | "priceCents": 1599, 242 | "keywords": ["tshirts", "shirts", "apparel", "mens"] 243 | }, 244 | { 245 | "id": "3fdfe8d6-9a15-4979-b459-585b0d0545b9", 246 | "image": "images/products/laundry-detergent-tabs.jpg", 247 | "name": "Laundry Detergent Tabs, 50 Loads", 248 | "rating": { 249 | "stars": 4.5, 250 | "count": 305 251 | }, 252 | "priceCents": 2899, 253 | "keywords": ["bathroom", "cleaning"] 254 | }, 255 | { 256 | "id": "e4f64a65-1377-42bc-89a5-e572d19252e2", 257 | "image": "images/products/sky-leaf-branch-earrings.jpg", 258 | "name": "Sterling Silver Leaf Branch Earrings", 259 | "rating": { 260 | "stars": 4.5, 261 | "count": 52 262 | }, 263 | "priceCents": 6799, 264 | "keywords": ["jewelry", "accessories", "womens"] 265 | }, 266 | { 267 | "id": "19c6a64a-5463-4d45-9af8-e41140a4100c", 268 | "image": "images/products/duvet-cover-set-gray-queen.jpg", 269 | "name": "Duvet Cover Set, Diamond Pattern", 270 | "rating": { 271 | "stars": 4, 272 | "count": 456 273 | }, 274 | "priceCents": 4399, 275 | "keywords": ["bedroom", "bed sheets", "sheets", "covers", "home"] 276 | }, 277 | { 278 | "id": "d2785924-743d-49b3-8f03-ec258e640503", 279 | "image": "images/products/women-knit-beanie-pom-pom-blue.jpg", 280 | "name": "Women's Knit Winter Beanie - Blue", 281 | "rating": { 282 | "stars": 5, 283 | "count": 83 284 | }, 285 | "priceCents": 1950, 286 | "keywords": ["hats", "winter hats", "beanies", "apparel", "womens"] 287 | }, 288 | { 289 | "id": "ee1f7c56-f977-40a4-9642-12ba5072e2b0", 290 | "image": "images/products/men-chino-pants-beige.jpg", 291 | "name": "Men's Chino Pants - Beige", 292 | "rating": { 293 | "stars": 4.5, 294 | "count": 9017 295 | }, 296 | "priceCents": 2290, 297 | "keywords": ["pants", "apparel", "mens"] 298 | }, 299 | { 300 | "id": "4df68c27-fd59-4a6a-bbd1-e754ddb6d53c", 301 | "image": "images/products/men-navigator-sunglasses-black.jpg", 302 | "name": "Men's Navigator Sunglasses", 303 | "rating": { 304 | "stars": 3.5, 305 | "count": 42 306 | }, 307 | "priceCents": 3690, 308 | "keywords": ["sunglasses", "glasses", "accessories", "shades"] 309 | }, 310 | { 311 | "id": "04701903-bc79-49c6-bc11-1af7e3651358", 312 | "image": "images/products/men-brown-flat-sneakers.jpg", 313 | "name": "Men's Brown Flat Sneakers", 314 | "rating": { 315 | "stars": 4.5, 316 | "count": 562 317 | }, 318 | "priceCents": 2499, 319 | "keywords": ["footwear", "men", "sneakers"] 320 | }, 321 | { 322 | "id": "4e37dd03-3b23-4bc6-9ff8-44e112a92c64", 323 | "image": "images/products/non-stick-cooking-set-4-pieces.jpg", 324 | "name": "Non-Stick Cook Set With Lids - 4 Pieces", 325 | "rating": { 326 | "stars": 4.5, 327 | "count": 511 328 | }, 329 | "priceCents": 6797, 330 | "keywords": ["cooking set", "kitchen"] 331 | }, 332 | { 333 | "id": "a434b69f-1bc1-482d-9ce7-cd7f4a66ce8d", 334 | "image": "images/products/vanity-mirror-pink.jpg", 335 | "name": "Vanity Mirror with LED Lights - Pink", 336 | "rating": { 337 | "stars": 4.5, 338 | "count": 130 339 | }, 340 | "priceCents": 2549, 341 | "keywords": ["bathroom", "washroom", "mirrors", "home"] 342 | }, 343 | { 344 | "id": "a45cfa0a-66d6-4dc7-9475-e2b01595f7d7", 345 | "image": "images/products/women-relaxed-lounge-pants-pink.jpg", 346 | "name": "Women's Relaxed Lounge Pants - Pink", 347 | "rating": { 348 | "stars": 4.5, 349 | "count": 248 350 | }, 351 | "priceCents": 3400, 352 | "keywords": ["pants", "apparel", "womens"] 353 | }, 354 | { 355 | "id": "d339adf3-e004-4c20-a120-40e8874c66cb", 356 | "image": "images/products/crystal-zirconia-stud-earrings-pink.jpg", 357 | "name": "Crystal Zirconia Stud Earrings - Pink", 358 | "rating": { 359 | "stars": 4.5, 360 | "count": 117 361 | }, 362 | "priceCents": 3467, 363 | "keywords": ["accessories", "womens"] 364 | }, 365 | { 366 | "id": "d37a651a-d501-483b-aae6-a9659b0757a0", 367 | "image": "images/products/glass-screw-lid-food-containers.jpg", 368 | "name": "Glass Screw Lid Containers - 3 Pieces", 369 | "rating": { 370 | "stars": 4, 371 | "count": 126 372 | }, 373 | "priceCents": 2899, 374 | "keywords": ["food containers", "kitchen"] 375 | }, 376 | { 377 | "id": "0d7f9afa-2efe-4fd9-b0fd-ba5663e0a524", 378 | "image": "images/products/black-and-silver-espresso-maker.jpg", 379 | "name": "Black and Silver Espresso Maker", 380 | "rating": { 381 | "stars": 4.5, 382 | "count": 1211 383 | }, 384 | "priceCents": 8250, 385 | "keywords": ["espresso makers", "kitchen", "appliances"] 386 | }, 387 | { 388 | "id": "02e3a47e-dd68-467e-9f71-8bf6f723fdae", 389 | "image": "images/products/blackout-curtains-set-teal.jpg", 390 | "name": "Blackout Curtains Set 42 x 84-Inch - Teal", 391 | "rating": { 392 | "stars": 4.5, 393 | "count": 363 394 | }, 395 | "priceCents": 3099, 396 | "keywords": ["bedroom", "home", "curtains"] 397 | }, 398 | { 399 | "id": "8a53b080-6d40-4a65-ab26-b24ecf700bce", 400 | "image": "images/products/bath-towel-set-gray-rosewood.jpg", 401 | "name": "Bath Towels 2 Pack - Gray, Rosewood", 402 | "rating": { 403 | "stars": 4.5, 404 | "count": 93 405 | }, 406 | "priceCents": 2990, 407 | "keywords": ["bathroom", "home", "towels"] 408 | }, 409 | { 410 | "id": "10ed8504-57db-433c-b0a3-fc71a35c88a1", 411 | "image": "images/products/athletic-skateboard-shoes-gray.jpg", 412 | "name": "Athletic Skateboard Shoes - Gray", 413 | "rating": { 414 | "stars": 4, 415 | "count": 89 416 | }, 417 | "priceCents": 3390, 418 | "keywords": ["shoes", "running shoes", "footwear"] 419 | }, 420 | { 421 | "id": "77a845b1-16ed-4eac-bdf9-5b591882113d", 422 | "image": "images/products/countertop-push-blender-black.jpg", 423 | "name": "Countertop Push Blender - Black", 424 | "rating": { 425 | "stars": 4, 426 | "count": 3 427 | }, 428 | "priceCents": 10747, 429 | "keywords": ["food blenders", "kitchen", "appliances"] 430 | }, 431 | { 432 | "id": "bc2847e9-5323-403f-b7cf-57fde044a955", 433 | "image": "images/products/men-cozy-fleece-hoodie-light-teal.jpg", 434 | "name": "Men's Fleece Hoodie - Light Teal", 435 | "rating": { 436 | "stars": 4.5, 437 | "count": 3157 438 | }, 439 | "priceCents": 3800, 440 | "keywords": ["sweaters", "hoodies", "apparel", "mens"] 441 | }, 442 | { 443 | "id": "36c64692-677f-4f58-b5ec-0dc2cf109e27", 444 | "image": "images/products/artistic-bowl-set-6-piece.jpg", 445 | "name": "Artistic Bowl and Plate Set - 6 Pieces", 446 | "rating": { 447 | "stars": 5, 448 | "count": 679 449 | }, 450 | "priceCents": 3899, 451 | "keywords": ["bowls set", "kitchen"] 452 | }, 453 | { 454 | "id": "aaa65ef3-8d6f-4eb3-bc9b-a6ea49047d8f", 455 | "image": "images/products/kitchen-paper-towels-8-pack.jpg", 456 | "name": "2-Ply Kitchen Paper Towels - 8 Pack", 457 | "rating": { 458 | "stars": 4.5, 459 | "count": 1045 460 | }, 461 | "priceCents": 1899, 462 | "keywords": ["kitchen", "kitchen towels", "tissues"] 463 | } 464 | ] -------------------------------------------------------------------------------- /defaultData/defaultProducts.js: -------------------------------------------------------------------------------- 1 | export const defaultProducts = [ 2 | { 3 | "id": "e43638ce-6aa0-4b85-b27f-e1d07eb678c6", 4 | "image": "images/products/athletic-cotton-socks-6-pairs.jpg", 5 | "name": "Black and Gray Athletic Cotton Socks - 6 Pairs", 6 | "rating": { 7 | "stars": 4.5, 8 | "count": 87 9 | }, 10 | "priceCents": 1090, 11 | "keywords": ["socks", "sports", "apparel"] 12 | }, 13 | { 14 | "id": "15b6fc6f-327a-4ec4-896f-486349e85a3d", 15 | "image": "images/products/intermediate-composite-basketball.jpg", 16 | "name": "Intermediate Size Basketball", 17 | "rating": { 18 | "stars": 4, 19 | "count": 127 20 | }, 21 | "priceCents": 2095, 22 | "keywords": ["sports", "basketballs"] 23 | }, 24 | { 25 | "id": "83d4ca15-0f35-48f5-b7a3-1ea210004f2e", 26 | "image": "images/products/adults-plain-cotton-tshirt-2-pack-teal.jpg", 27 | "name": "Adults Plain Cotton T-Shirt - 2 Pack", 28 | "rating": { 29 | "stars": 4.5, 30 | "count": 56 31 | }, 32 | "priceCents": 799, 33 | "keywords": ["tshirts", "apparel", "mens"] 34 | }, 35 | { 36 | "id": "54e0eccd-8f36-462b-b68a-8182611d9add", 37 | "image": "images/products/2-slot-toaster-white.jpg", 38 | "name": "2 Slot Toaster - White", 39 | "rating": { 40 | "stars": 5, 41 | "count": 2197 42 | }, 43 | "priceCents": 1899, 44 | "keywords": ["toaster", "kitchen", "appliances"] 45 | }, 46 | { 47 | "id": "3ebe75dc-64d2-4137-8860-1f5a963e534b", 48 | "image": "images/products/elegant-white-dinner-plate-set.jpg", 49 | "name": "2 Piece White Dinner Plate Set", 50 | "rating": { 51 | "stars": 4, 52 | "count": 37 53 | }, 54 | "priceCents": 2067, 55 | "keywords": ["plates", "kitchen", "dining"] 56 | }, 57 | { 58 | "id": "8c9c52b5-5a19-4bcb-a5d1-158a74287c53", 59 | "image": "images/products/3-piece-cooking-set.jpg", 60 | "name": "3 Piece Non-Stick, Black Cooking Pot Set", 61 | "rating": { 62 | "stars": 4.5, 63 | "count": 175 64 | }, 65 | "priceCents": 3499, 66 | "keywords": ["kitchen", "cookware"] 67 | }, 68 | { 69 | "id": "dd82ca78-a18b-4e2a-9250-31e67412f98d", 70 | "image": "images/products/women-plain-cotton-oversized-sweater-gray.jpg", 71 | "name": "Cotton Oversized Sweater - Gray", 72 | "rating": { 73 | "stars": 4.5, 74 | "count": 317 75 | }, 76 | "priceCents": 2400, 77 | "keywords": ["sweaters", "apparel"] 78 | }, 79 | { 80 | "id": "77919bbe-0e56-475b-adde-4f24dfed3a04", 81 | "image": "images/products/luxury-towel-set.jpg", 82 | "name": "2 Piece Luxury Towel Set - White", 83 | "rating": { 84 | "stars": 4.5, 85 | "count": 144 86 | }, 87 | "priceCents": 3599, 88 | "keywords": ["bathroom", "washroom", "restroom", "towels", "bath towels"] 89 | }, 90 | { 91 | "id": "6b07d4e7-f540-454e-8a1e-363f25dbae7d", 92 | "image": "images/products/facial-tissue-2-ply-8-boxes.jpg", 93 | "name": "Ultra Soft Tissue 2-Ply - 8 Boxes", 94 | "rating": { 95 | "stars": 4, 96 | "count": 99 97 | }, 98 | "priceCents": 2374, 99 | "keywords": ["kleenex", "tissues", "kitchen", "napkins"] 100 | }, 101 | { 102 | "id": "5968897c-4d27-4872-89f6-5bcb052746d7", 103 | "image": "images/products/women-striped-beach-dress.jpg", 104 | "name": "Women's Striped Beach Dress", 105 | "rating": { 106 | "stars": 4.5, 107 | "count": 235 108 | }, 109 | "priceCents": 2970, 110 | "keywords": ["robe", "swimsuit", "swimming", "bathing", "apparel"] 111 | }, 112 | { 113 | "id": "b86ddc8b-3501-4b17-9889-a3bad6fb585f", 114 | "image": "images/products/women-sandal-heels-white-pink.jpg", 115 | "name": "Women's Sandal Heels - Pink", 116 | "rating": { 117 | "stars": 4.5, 118 | "count": 2286 119 | }, 120 | "priceCents": 5300, 121 | "keywords": ["womens", "shoes", "heels", "sandals"] 122 | }, 123 | { 124 | "id": "aad29d11-ea98-41ee-9285-b916638cac4a", 125 | "image": "images/products/round-sunglasses-gold.jpg", 126 | "name": "Round Sunglasses", 127 | "rating": { 128 | "stars": 4.5, 129 | "count": 30 130 | }, 131 | "priceCents": 3560, 132 | "keywords": ["accessories", "shades"] 133 | }, 134 | { 135 | "id": "901eb2ca-386d-432e-82f0-6fb1ee7bf969", 136 | "image": "images/products/blackout-curtain-set-beige.jpg", 137 | "name": "Blackout Curtains Set - Beige", 138 | "rating": { 139 | "stars": 4.5, 140 | "count": 232 141 | }, 142 | "priceCents": 4599, 143 | "keywords": ["bedroom", "curtains", "home"] 144 | }, 145 | { 146 | "id": "82bb68d7-ebc9-476a-989c-c78a40ee5cd9", 147 | "image": "images/products/women-summer-jean-shorts.jpg", 148 | "name": "Women's Summer Jean Shorts", 149 | "rating": { 150 | "stars": 4, 151 | "count": 160 152 | }, 153 | "priceCents": 1699, 154 | "keywords": ["shorts", "apparel", "womens"] 155 | }, 156 | { 157 | "id": "c2a82c5e-aff4-435f-9975-517cfaba2ece", 158 | "image": "images/products/electric-steel-hot-water-kettle-white.jpg", 159 | "name": "Electric Hot Water Kettle - White", 160 | "rating": { 161 | "stars": 5, 162 | "count": 846 163 | }, 164 | "priceCents": 5074, 165 | "keywords": ["water kettle", "appliances", "kitchen"] 166 | }, 167 | { 168 | "id": "58b4fc92-e98c-42aa-8c55-b6b79996769a", 169 | "image": "images/products/knit-athletic-sneakers-gray.jpg", 170 | "name": "Waterproof Knit Athletic Sneakers - Gray", 171 | "rating": { 172 | "stars": 4, 173 | "count": 89 174 | }, 175 | "priceCents": 5390, 176 | "keywords": ["shoes", "running shoes", "footwear"] 177 | }, 178 | { 179 | "id": "a82c6bac-3067-4e68-a5ba-d827ac0be010", 180 | "image": "images/products/straw-sunhat.jpg", 181 | "name": "Straw Wide Brim Sun Hat", 182 | "rating": { 183 | "stars": 4, 184 | "count": 215 185 | }, 186 | "priceCents": 2200, 187 | "keywords": ["hats", "straw hats", "summer", "apparel"] 188 | }, 189 | { 190 | "id": "1c079479-8586-494f-ab53-219325432536", 191 | "image": "images/products/men-athletic-shoes-white.jpg", 192 | "name": "Men's Athletic Sneaker - White", 193 | "rating": { 194 | "stars": 4, 195 | "count": 229 196 | }, 197 | "priceCents": 4590, 198 | "keywords": ["shoes", "running shoes", "footwear", "mens"] 199 | }, 200 | { 201 | "id": "b0f17cc5-8b40-4ca5-9142-b61fe3d98c85", 202 | "image": "images/products/men-stretch-wool-sweater-black.jpg", 203 | "name": "Men's Wool Sweater - Black", 204 | "rating": { 205 | "stars": 4.5, 206 | "count": 2465 207 | }, 208 | "priceCents": 3374, 209 | "keywords": ["sweaters", "apparel"] 210 | }, 211 | { 212 | "id": "a93a101d-79ef-4cf3-a6cf-6dbe532a1b4a", 213 | "image": "images/products/bathroom-mat.jpg", 214 | "name": "Bathroom Bath Mat 16 x 32 Inch - Grey", 215 | "rating": { 216 | "stars": 4.5, 217 | "count": 119 218 | }, 219 | "priceCents": 1850, 220 | "keywords": ["bathmat", "bathroom", "home"] 221 | }, 222 | { 223 | "id": "4f4fbcc2-4e72-45cc-935c-9e13d79cc57f", 224 | "image": "images/products/women-knit-ballet-flat-white.jpg", 225 | "name": "Women's Ballet Flat - White", 226 | "rating": { 227 | "stars": 4, 228 | "count": 326 229 | }, 230 | "priceCents": 2640, 231 | "keywords": ["shoes", "flats", "womens", "footwear"] 232 | }, 233 | { 234 | "id": "8b5a2ee1-6055-422a-a666-b34ba28b76d4", 235 | "image": "images/products/men-golf-polo-t-shirt-gray.jpg", 236 | "name": "Men's Golf Polo Shirt - Gray", 237 | "rating": { 238 | "stars": 4.5, 239 | "count": 2556 240 | }, 241 | "priceCents": 1599, 242 | "keywords": ["tshirts", "shirts", "apparel", "mens"] 243 | }, 244 | { 245 | "id": "3fdfe8d6-9a15-4979-b459-585b0d0545b9", 246 | "image": "images/products/laundry-detergent-tabs.jpg", 247 | "name": "Laundry Detergent Tabs, 50 Loads", 248 | "rating": { 249 | "stars": 4.5, 250 | "count": 305 251 | }, 252 | "priceCents": 2899, 253 | "keywords": ["bathroom", "cleaning"] 254 | }, 255 | { 256 | "id": "e4f64a65-1377-42bc-89a5-e572d19252e2", 257 | "image": "images/products/sky-leaf-branch-earrings.jpg", 258 | "name": "Sterling Silver Leaf Branch Earrings", 259 | "rating": { 260 | "stars": 4.5, 261 | "count": 52 262 | }, 263 | "priceCents": 6799, 264 | "keywords": ["jewelry", "accessories", "womens"] 265 | }, 266 | { 267 | "id": "19c6a64a-5463-4d45-9af8-e41140a4100c", 268 | "image": "images/products/duvet-cover-set-gray-queen.jpg", 269 | "name": "Duvet Cover Set, Diamond Pattern", 270 | "rating": { 271 | "stars": 4, 272 | "count": 456 273 | }, 274 | "priceCents": 4399, 275 | "keywords": ["bedroom", "bed sheets", "sheets", "covers", "home"] 276 | }, 277 | { 278 | "id": "d2785924-743d-49b3-8f03-ec258e640503", 279 | "image": "images/products/women-knit-beanie-pom-pom-blue.jpg", 280 | "name": "Women's Knit Winter Beanie - Blue", 281 | "rating": { 282 | "stars": 5, 283 | "count": 83 284 | }, 285 | "priceCents": 1950, 286 | "keywords": ["hats", "winter hats", "beanies", "apparel", "womens"] 287 | }, 288 | { 289 | "id": "ee1f7c56-f977-40a4-9642-12ba5072e2b0", 290 | "image": "images/products/men-chino-pants-beige.jpg", 291 | "name": "Men's Chino Pants - Beige", 292 | "rating": { 293 | "stars": 4.5, 294 | "count": 9017 295 | }, 296 | "priceCents": 2290, 297 | "keywords": ["pants", "apparel", "mens"] 298 | }, 299 | { 300 | "id": "4df68c27-fd59-4a6a-bbd1-e754ddb6d53c", 301 | "image": "images/products/men-navigator-sunglasses-black.jpg", 302 | "name": "Men's Navigator Sunglasses", 303 | "rating": { 304 | "stars": 3.5, 305 | "count": 42 306 | }, 307 | "priceCents": 3690, 308 | "keywords": ["sunglasses", "glasses", "accessories", "shades"] 309 | }, 310 | { 311 | "id": "04701903-bc79-49c6-bc11-1af7e3651358", 312 | "image": "images/products/men-brown-flat-sneakers.jpg", 313 | "name": "Men's Brown Flat Sneakers", 314 | "rating": { 315 | "stars": 4.5, 316 | "count": 562 317 | }, 318 | "priceCents": 2499, 319 | "keywords": ["footwear", "men", "sneakers"] 320 | }, 321 | { 322 | "id": "4e37dd03-3b23-4bc6-9ff8-44e112a92c64", 323 | "image": "images/products/non-stick-cooking-set-4-pieces.jpg", 324 | "name": "Non-Stick Cook Set With Lids - 4 Pieces", 325 | "rating": { 326 | "stars": 4.5, 327 | "count": 511 328 | }, 329 | "priceCents": 6797, 330 | "keywords": ["cooking set", "kitchen"] 331 | }, 332 | { 333 | "id": "a434b69f-1bc1-482d-9ce7-cd7f4a66ce8d", 334 | "image": "images/products/vanity-mirror-pink.jpg", 335 | "name": "Vanity Mirror with LED Lights - Pink", 336 | "rating": { 337 | "stars": 4.5, 338 | "count": 130 339 | }, 340 | "priceCents": 2549, 341 | "keywords": ["bathroom", "washroom", "mirrors", "home"] 342 | }, 343 | { 344 | "id": "a45cfa0a-66d6-4dc7-9475-e2b01595f7d7", 345 | "image": "images/products/women-relaxed-lounge-pants-pink.jpg", 346 | "name": "Women's Relaxed Lounge Pants - Pink", 347 | "rating": { 348 | "stars": 4.5, 349 | "count": 248 350 | }, 351 | "priceCents": 3400, 352 | "keywords": ["pants", "apparel", "womens"] 353 | }, 354 | { 355 | "id": "d339adf3-e004-4c20-a120-40e8874c66cb", 356 | "image": "images/products/crystal-zirconia-stud-earrings-pink.jpg", 357 | "name": "Crystal Zirconia Stud Earrings - Pink", 358 | "rating": { 359 | "stars": 4.5, 360 | "count": 117 361 | }, 362 | "priceCents": 3467, 363 | "keywords": ["accessories", "womens"] 364 | }, 365 | { 366 | "id": "d37a651a-d501-483b-aae6-a9659b0757a0", 367 | "image": "images/products/glass-screw-lid-food-containers.jpg", 368 | "name": "Glass Screw Lid Containers - 3 Pieces", 369 | "rating": { 370 | "stars": 4, 371 | "count": 126 372 | }, 373 | "priceCents": 2899, 374 | "keywords": ["food containers", "kitchen"] 375 | }, 376 | { 377 | "id": "0d7f9afa-2efe-4fd9-b0fd-ba5663e0a524", 378 | "image": "images/products/black-and-silver-espresso-maker.jpg", 379 | "name": "Black and Silver Espresso Maker", 380 | "rating": { 381 | "stars": 4.5, 382 | "count": 1211 383 | }, 384 | "priceCents": 8250, 385 | "keywords": ["espresso makers", "kitchen", "appliances"] 386 | }, 387 | { 388 | "id": "02e3a47e-dd68-467e-9f71-8bf6f723fdae", 389 | "image": "images/products/blackout-curtains-set-teal.jpg", 390 | "name": "Blackout Curtains Set 42 x 84-Inch - Teal", 391 | "rating": { 392 | "stars": 4.5, 393 | "count": 363 394 | }, 395 | "priceCents": 3099, 396 | "keywords": ["bedroom", "home", "curtains"] 397 | }, 398 | { 399 | "id": "8a53b080-6d40-4a65-ab26-b24ecf700bce", 400 | "image": "images/products/bath-towel-set-gray-rosewood.jpg", 401 | "name": "Bath Towels 2 Pack - Gray, Rosewood", 402 | "rating": { 403 | "stars": 4.5, 404 | "count": 93 405 | }, 406 | "priceCents": 2990, 407 | "keywords": ["bathroom", "home", "towels"] 408 | }, 409 | { 410 | "id": "10ed8504-57db-433c-b0a3-fc71a35c88a1", 411 | "image": "images/products/athletic-skateboard-shoes-gray.jpg", 412 | "name": "Athletic Skateboard Shoes - Gray", 413 | "rating": { 414 | "stars": 4, 415 | "count": 89 416 | }, 417 | "priceCents": 3390, 418 | "keywords": ["shoes", "running shoes", "footwear"] 419 | }, 420 | { 421 | "id": "77a845b1-16ed-4eac-bdf9-5b591882113d", 422 | "image": "images/products/countertop-push-blender-black.jpg", 423 | "name": "Countertop Push Blender - Black", 424 | "rating": { 425 | "stars": 4, 426 | "count": 3 427 | }, 428 | "priceCents": 10747, 429 | "keywords": ["food blenders", "kitchen", "appliances"] 430 | }, 431 | { 432 | "id": "bc2847e9-5323-403f-b7cf-57fde044a955", 433 | "image": "images/products/men-cozy-fleece-hoodie-light-teal.jpg", 434 | "name": "Men's Fleece Hoodie - Light Teal", 435 | "rating": { 436 | "stars": 4.5, 437 | "count": 3157 438 | }, 439 | "priceCents": 3800, 440 | "keywords": ["sweaters", "hoodies", "apparel", "mens"] 441 | }, 442 | { 443 | "id": "36c64692-677f-4f58-b5ec-0dc2cf109e27", 444 | "image": "images/products/artistic-bowl-set-6-piece.jpg", 445 | "name": "Artistic Bowl and Plate Set - 6 Pieces", 446 | "rating": { 447 | "stars": 5, 448 | "count": 679 449 | }, 450 | "priceCents": 3899, 451 | "keywords": ["bowls set", "kitchen"] 452 | }, 453 | { 454 | "id": "aaa65ef3-8d6f-4eb3-bc9b-a6ea49047d8f", 455 | "image": "images/products/kitchen-paper-towels-8-pack.jpg", 456 | "name": "2-Ply Kitchen Paper Towels - 8 Pack", 457 | "rating": { 458 | "stars": 4.5, 459 | "count": 1045 460 | }, 461 | "priceCents": 1899, 462 | "keywords": ["kitchen", "kitchen towels", "tissues"] 463 | } 464 | ]; --------------------------------------------------------------------------------