├── .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 |
11 |
12 | 3. If the AI responds and you don't understand something, ask it for more details.
13 |
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 |
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 |
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 | ];
--------------------------------------------------------------------------------