Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
e0d0d78
added folders and files, ran npm y and npm i express mongoose body-pa…
martinsalinas0 May 18, 2025
38afec5
added boilerplate to server.js
martinsalinas0 May 18, 2025
cd1e568
added GET products - gets all no pagination
martinsalinas0 May 18, 2025
072b564
added nodemon
martinsalinas0 May 18, 2025
c02ee9a
added pagination to GET products
martinsalinas0 May 18, 2025
461953c
whitespace issue in package.json
martinsalinas0 May 18, 2025
0c5a202
moved server.js to backend folder
martinsalinas0 May 24, 2025
67d5b62
updated server.js and added mongo connection
martinsalinas0 May 25, 2025
45d8229
server.js is setup
martinsalinas0 May 25, 2025
5c5064e
GET all products w/pagination
martinsalinas0 May 25, 2025
6b9dafc
added all routes- review routes not functioning
martinsalinas0 May 25, 2025
baf857b
all routes created
martinsalinas0 May 27, 2025
13f80b4
Merge branch 'master' of https://github.com/martinsalinas0/product-list
martinsalinas0 May 27, 2025
70f0347
created backend and frontend fodlers
martinsalinas0 May 27, 2025
7450536
created db.js - has not been integrated to server.js yet
martinsalinas0 May 27, 2025
6291a98
inititated npx create next app
martinsalinas0 May 28, 2025
8541ffe
created the controllers folder and file, edited the routes fodlers an…
martinsalinas0 May 28, 2025
78d959d
fixed reviews ==> review in the import
martinsalinas0 May 28, 2025
9988785
added bootstrap
martinsalinas0 May 28, 2025
102feab
created product card component
martinsalinas0 May 28, 2025
c6e189c
npm i axios, created routes for products and product/:id
martinsalinas0 May 28, 2025
c4f25dc
updated the README.md
martinsalinas0 May 28, 2025
f4dfa18
set up basic axios.get for all products
martinsalinas0 May 28, 2025
b5c43dc
updated
martinsalinas0 May 28, 2025
c3ee23a
updated settings
martinsalinas0 May 28, 2025
e796075
update?
martinsalinas0 May 28, 2025
63da3ac
udpated fromat on read me
martinsalinas0 May 29, 2025
d8a099a
edited the props in productCard.jsx
martinsalinas0 May 29, 2025
b042345
fixed layout and added the products to be mapped in the 3x3 grid
martinsalinas0 May 29, 2025
fe320dc
set up redux store and productSlice
martinsalinas0 May 29, 2025
a650454
Merge branch 'master' of https://github.com/martinsalinas0/product-list
martinsalinas0 May 29, 2025
ae643cb
renamed productsSlice to productSlice
martinsalinas0 May 29, 2025
8f9e96f
created configureStore.js
martinsalinas0 May 29, 2025
d54749e
removed .vscoode
martinsalinas0 May 29, 2025
16ac73b
reformatted
martinsalinas0 May 29, 2025
b1a74b6
added min to price
martinsalinas0 May 29, 2025
fb6dfae
added a charcter limit to review.text
martinsalinas0 May 29, 2025
3c66309
fixed the double PAGE keys in getProducts
martinsalinas0 May 29, 2025
baf9915
renamed files and improved file structure
martinsalinas0 May 29, 2025
22705d0
removed the folder PUBLIC
martinsalinas0 May 29, 2025
9605fb7
added redux
martinsalinas0 May 29, 2025
42ec250
corrected import file path
martinsalinas0 May 29, 2025
bd8176e
deleted main nav bar
martinsalinas0 May 29, 2025
b48fa6e
deleted product nav bar
martinsalinas0 May 29, 2025
9ae2629
put search bar on this page, added some functionallity, needs testing
martinsalinas0 May 29, 2025
a0546c6
updated product card
martinsalinas0 May 29, 2025
7940d41
fixed file path and renamed this file
martinsalinas0 May 29, 2025
25a5d4d
added more reducers
martinsalinas0 May 29, 2025
65dca31
updated dependicies
martinsalinas0 May 31, 2025
d1a8863
updates README
martinsalinas0 May 31, 2025
10d2e7a
/proucts show products with filters
martinsalinas0 May 31, 2025
458f773
deleted vscode/setting.json
martinsalinas0 May 31, 2025
5077701
fixed /product route and link
martinsalinas0 Jun 1, 2025
138ce15
made a more simple product slice to only get products
martinsalinas0 Jun 2, 2025
3e43618
implemented the URL constructor
martinsalinas0 Jun 2, 2025
98bf657
updated prevPage and nextPage to dispatch
martinsalinas0 Jun 2, 2025
e861e97
general updates
martinsalinas0 Jun 2, 2025
fea2c9e
updated component to fetch correctly
martinsalinas0 Jun 2, 2025
c967280
added categories as an array
martinsalinas0 Jun 2, 2025
8b98cb5
added NavBar.jsx and added it to page,js
martinsalinas0 Jun 2, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
95 changes: 95 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# General
.DS_Store
.AppleDouble
.LSOverride

# Icon must end with two \r
Icon


# Thumbnails
._*

# Files that might appear in the root of a volume
.DocumentRevisions-V100
.fseventsd
.Spotlight-V100
.TemporaryItems
.Trashes
.VolumeIcon.icns
.com.apple.timemachine.donotpresent

# Directories potentially created on remote AFP share
.AppleDB
.AppleDesktop
Network Trash Folder
Temporary Items
.apdisk

.vscode/*
!.vscode/settings.json
!.vscode/tasks.json
!.vscode/launch.json
!.vscode/extensions.json

# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# Runtime data
pids
*.pid
*.seed
*.pid.lock

# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov

# Coverage directory used by tools like istanbul
coverage

# nyc test coverage
.nyc_output

# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt

# Bower dependency directory (https://bower.io/)
bower_components

# node-waf configuration
.lock-wscript

# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release

# Dependency directories
node_modules/
jspm_packages/

# Typescript v1 declaration files
typings/

# Optional npm cache directory
.npm

# Optional eslint cache
.eslintcache

# Optional REPL history
.node_repl_history

# Output of 'npm pack'
*.tgz

# Yarn Integrity file
.yarn-integrity

# dotenv environment variables file
.env

# next.js build output
.next
7 changes: 7 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"workbench.colorCustomizations": {
"activityBar.background": "#402811",
"titleBar.activeBackground": "#5A3818",
"titleBar.activeForeground": "#FDFAF7"
}
}
73 changes: 71 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,75 @@
## Product List

# Product List

This project has been created by a student at Parsity, an online software engineering course. The work in this repository is wholly of the student based on a sample starter project that can be accessed by looking at the repository that this project forks.

If you have any questions about this project or the program in general, visit [parsity.io](https://parsity.io/) or email hello@parsity.io.

## Requirements

Install dependencies

npm install

Install required packages

npm install react-bootstrap bootstrap axios react-icons

##Database - MongoDB

Create a .env file in your backend directory:
envPORT=8000
MONGO_URI=mongodb://localhost/products

OR use your own MongoDB connection string:

MONGO_URI=mongodb+srv://username:password@cluster.mongodb.net/products

## Running the Application

Start the Backend Server
bash# From root directory

npm run dev

Server will run on http://localhost:8000

Start the Frontend Application
bash# From frontend directory
npm run dev

App will run on http://localhost:3000

### Configuration

Port Configuration

Backend: Runs on process.env.PORT || 8000
Frontend: Must run on port 3000 (CORS is configured for this port)

### Database Configuration

The MongoDB connection string is set to:
javascriptconst connectString = process.env.MONGO_URI || "mongodb://localhost/products"
You can either:

Use the default local MongoDB setup
Set your own MONGO_URI in the .env file

## Troubleshooting

Common Issues:
CORS Error:

Ensure frontend runs on port 3000
Check CORS configuration in backend

Database Connection:

Verify MongoDB is running locally
Check MONGO_URI environment variable

Port Already in Use:

Change PORT in .env file

to see products, go to http://localhost:8000/api/products
15 changes: 15 additions & 0 deletions backend/config/db.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
const mongoose = require("mongoose");

const connectDB = async () => {
const connectString = process.env.MONGO_URI || "mongodb://localhost/products";

try {
const conn = await mongoose.connect(connectString);
console.log(`MongoDB connected. Connected to database: ${connectString}`);
} catch (error) {
console.error(`Error: ${error.message}`);
process.exit(1);
}
};

module.exports = connectDB;
153 changes: 153 additions & 0 deletions backend/controllers/products.controllers.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@

const Product = require("../models/product.model.js");
const Review = require('../models/review.model.js')


//gets all products
const getProducts = async (req, res) => {
try {
const productsPerPage = 9;
const page = parseInt(req.query.page) || 1;
const category = req.query.category || null;

Choose a reason for hiding this comment

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

why the null fallback? undefined is not good enough?

const priceSort = req.query.price;
const search = req.query.search || null;


let filterProducts = {};

Choose a reason for hiding this comment

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

not really clear the label, should be more like filterQuery or something more specific


if (category) {
filterProducts.category = { $regex: `^${category}$`, $options: "i" };

Choose a reason for hiding this comment

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

dry

}

if (search) {
filterProducts.name = { $regex: search, $options: "i" };

Choose a reason for hiding this comment

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

dry

Choose a reason for hiding this comment

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

you can store this as a generic const and just use it twice

}

let sort = {};

Choose a reason for hiding this comment

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

can be const


if (priceSort === "lowest") {

Choose a reason for hiding this comment

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

use consts, will help with DRY

sort.price = 1;
} else if (priceSort === "highest") {
sort.price = -1;
}

const count = await Product.countDocuments(filterProducts);

const products = await Product.find(filterProducts)
.sort(sort)
.skip((page - 1) * productsPerPage)

Choose a reason for hiding this comment

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

store in a const or a function to give context for this skip

.limit(productsPerPage);

res.status(200).json({
products,
count,
page,
totalPages: Math.ceil(count / productsPerPage),
});
} catch (error) {
res.status(500).json({ message: error.message });
}
};

const getProductById = async (req, res) => {
try {
const { id } = req.params;
const product = await Product.findById(id);

if (!product) {
return res.status(404).json({ message: "Product not found" });
}
res.status(200).json(product);
} catch (error) {
res.status(500).json({ message: error.message });
}
};

const deleteProductById = async (req, res) => {
try {
const { id } = req.params;
const product = await Product.findByIdAndDelete(id);
if (!product) {
return res.status(404).json({ message: "Product not found" });
}
res.status(200).json({ message: "Product has been deleted" });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const createNewProduct = async (req, res) => {
try {
const newProduct = await Product.create(req.body);
res.status(200).json(newProduct);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const getReviewsOfAProduct = async (req, res) => {
try {
const { productId } = req.params;

const product = await Product.findById(productId);

//if no product
if (!product) {
return res.status(404).json({ message: "Product not found" });
}

const reviews = await Review.find({ product: productId }).limit(4);

//if yes prodcut but no reviews
if (reviews.length === 0) {
return res.status(404).json({ message: "No reviews available" });
}
res.status(200).json(reviews);
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const createReviewForAProduct = async (req, res) => {
try {
const { productId } = req.params;
const { userName, text } = req.body;

if (!userName || !text) {
return res.status(400).json({
message: "username and text are required.",
});
}

const product = await Product.findById(productId);

if (!product) {
return res.status(404).json({ message: "Product not found" });
}

const newReview = await Review.create({
userName,
text,
product: productId,
});

res.status(201).json({ message: "Review added", review: newReview });
} catch (error) {
res.status(500).json({ message: error.message });
}
};
const deleteReviewById = async (req, res) => {
try {
const { reviewId } = req.params;

const review = await Review.findByIdAndDelete(reviewId);

if (!review) {
return res.status(404).json({ message: "Review not found" });
}

res.status(200).json({ message: "review has been deleted" });
} catch (error) {
res.status(500).json({ message: error.message });
}
};


module.exports = { deleteProductById, deleteReviewById, getProductById, getProducts, getReviewsOfAProduct, createNewProduct, createReviewForAProduct}
Loading