-
Notifications
You must be signed in to change notification settings - Fork 167
eval submission #148
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Open
jordanccox
wants to merge
70
commits into
projectshft:master
Choose a base branch
from
jordanccox:master
base: master
Could not load branches
Branch not found: {{ refName }}
Loading
Could not load tags
Nothing to show
Loading
Are you sure you want to change the base?
Some commits from the old base branch may be removed from the timeline,
and old review comments may become outdated.
Open
eval submission #148
Changes from all commits
Commits
Show all changes
70 commits
Select commit
Hold shift + click to select a range
36b45db
project set up
jordanccox cbfc0f4
further setup
jordanccox 0b430b9
pagination working for products pages
jordanccox 1a611cc
controllers and routes added
jordanccox c06a8fc
swagger done
jordanccox a5233ca
add review schema
jordanccox b4b44c4
fake reviews added
jordanccox 69d3524
figure out how to populate reviews in products for future
jordanccox d981f19
product schema validation added
jordanccox 2ae7945
updated error response
jordanccox ce42f53
refactor validation product schema to its own method
jordanccox 95a4d62
todos added
jordanccox 99f3236
post products complete and get products/:productId started
jordanccox 637967e
remove __v from getProductById result
jordanccox 2a1b7fb
deleteProductById route works
jordanccox 078b49a
reviews search working
jordanccox 5ad5ce7
post new review functionality working
jordanccox d836b2f
all routes working
jordanccox 68b22ba
product filters and sorting works
jordanccox 4a8c1b7
refactor products function
jordanccox fd23b6e
readme
jordanccox c5eea6b
clean up code
jordanccox e94771b
clean up code
jordanccox 77d2b9f
commenting for clarity
jordanccox d124c48
update commenting
jordanccox 22ba282
updated validations and error handling
jordanccox a98fa46
cors support added
jordanccox d65e463
unused code removed
jordanccox f4aa4e9
update localhost
jordanccox 273e5c8
Revert "update localhost"
jordanccox 70721fb
move to backend folder
jordanccox 82a7280
create frontend folder
jordanccox dde6087
readme
jordanccox 6dabc44
readme
jordanccox 563842a
updater readme
jordanccox 0e394dd
setup frontend dev environment
jordanccox e755459
css styling
jordanccox 59dbe60
more css
jordanccox 40ed6f7
css
jordanccox 521b164
css
jordanccox deb804f
responsive navbar done
jordanccox edd3b55
dropdown functionality working
jordanccox 323b87d
work on footer
jordanccox 957576d
fix footer breaking nav issue
jordanccox f91789b
update search bar style
jordanccox b0eee89
progress on cards
jordanccox 6aeb9b4
product layout complete
jordanccox 006747a
product styled
jordanccox 8c6ce3c
footer design complete
jordanccox a3f179a
init react-router
jordanccox f4896b2
basic product fetch works
jordanccox 0099f25
footer positioned correctly
jordanccox a2d9e60
work on fetch
jordanccox 4611116
add count results functionality
jordanccox 6244dd2
solved results count
jordanccox 07f1ff1
footer links added
jordanccox ae170a5
pagination working
jordanccox ad16bcd
styling fixes
jordanccox ecb6412
update readme
jordanccox 9e9b052
page selection working
jordanccox 2c60eb5
pagination working
jordanccox 1810bc5
categories route
jordanccox cee3ba7
category search working
jordanccox 74ac109
fix pagination
jordanccox 5b1bd02
application features working
jordanccox fdcad50
readme update
jordanccox cd44bfd
update readme
jordanccox c036918
improve ui
jordanccox 7edd097
clear search added
jordanccox d89f301
styling categories
jordanccox File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,6 +1,37 @@ | ||
| ## Product List | ||
|
|
||
| This is full stack web app built for an evaluation for [Parsity Coding School](https://parsity.io/). | ||
|
|
||
| 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. | ||
| ### About the Backend | ||
|
|
||
| If you have any questions about this project or the program in general, visit [parsity.io](https://parsity.io/) or email hello@parsity.io. | ||
| The API is built for a mock e-commerce store that sells various "products" -- each product is randomly generated using [Faker](https://fakerjs.dev/). | ||
|
|
||
| This project was built with TypeScript, Node.js, Express, and MongoDB. | ||
|
|
||
| ### About the Frontend | ||
|
|
||
| The frontend for this project was built using React. It displays an e-commerce storefront that allows the user to view all the products upon first load and then filter results by category, price, and a custom search query. | ||
|
|
||
| ### Run API Locally | ||
|
|
||
| 1. To run locally, you will need to set up a MongoDB database on your computer. This project was created with MongoDB 5.0, but it should be compatible with later versions as well. Follow [this link](https://www.mongodb.com/docs/manual/installation/) for installation instructions on your operating system of choice. Ensure that as a part of your installation, you specify a data directory. Test that your database is working using the [MongoDB Shell](https://www.mongodb.com/docs/mongodb-shell/). Once you know the database is set up correctly, move on to the next step. | ||
|
|
||
| 2. Fork and clone this repo. | ||
|
|
||
| 3. `cd` into the repo on your local computer, then `cd` into `backend` and `npm install` to install dependencies. | ||
|
|
||
| 4. `npm start` to launch the development server on `localhost:8000`. | ||
|
|
||
| 5. Copy and paste the contents of `swagger/swagger.yaml` into the [Swagger Editor](https://editor.swagger.io/). This is the documentation for the API routes. | ||
|
|
||
| IMPORTANT: Before you are able to use any of the routes, you will need to generate fake data. You can do so by navigating first to `localhost:8000/generate-fake-data` then to `localhost:8000/generate-fake-reviews`. | ||
|
|
||
| For testing `GET` routes, you can simply type the URL into your web browser -- for example, `localhost:8000/products`. For `POST` and `DELETE` routes, you will need to use a CLI tool like [curl](https://en.wikipedia.org/wiki/CURL) or [download the desktop agent for Postman](https://www.postman.com/downloads/). | ||
|
|
||
| ### Run the Frontend Locally | ||
|
|
||
| 1. To run the frontend, first ensure that you have followed the previous steps to get the API working. Launch the API by `cd`ing into the `backend` folder and running `npm start`. | ||
|
|
||
| 2. Once the server is running, `cd` into the `frontend` folder and launch the frontend server by running `npm run dev`. | ||
|
|
||
| 3. Navigate to `localhost:5173` in your browser. If everything was set up correctly, you should be able to interact with your product data with the UI. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1 @@ | ||
| node_modules |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,73 @@ | ||
| import { faker } from "@faker-js/faker"; | ||
| import Product from "../models/product.js"; | ||
| import Review from "../models/review.js"; | ||
| import { Request, Response } from "express"; | ||
|
|
||
| // Create fake data for API testing | ||
|
|
||
| /** | ||
| * Creates 90 fake products | ||
| * @param req Client request to server | ||
| * @param resp Server response to client | ||
| * @returns Promise<void> | ||
| */ | ||
| const createProductData = async ( | ||
| req: Request, | ||
| res: Response | ||
| ): Promise<void> => { | ||
| for (let i = 0; i < 90; i++) { | ||
| const product = new Product(); | ||
|
|
||
| product.category = faker.commerce.department(); | ||
| product.name = faker.commerce.productName(); | ||
| product.price = Number(faker.commerce.price()); | ||
| product.image = "https://via.placeholder.com/250?text=Product+Image"; | ||
| product.reviews = []; | ||
|
|
||
| try { | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. great job using try catch |
||
| await product.save(); | ||
| } catch (err) { | ||
| throw err; | ||
| } | ||
| } | ||
|
|
||
| res.end(); | ||
| }; | ||
|
|
||
| /** | ||
| * Adds 0 to 5 fake reviews per product in database. Replaces current product.reviews array so that review list will never start out greater than 5 reviews. | ||
| * @param req Client request to server | ||
| * @param resp Server response to client | ||
| * @returns Promise<void> | ||
| */ | ||
| const createReviews = async (req: Request, res: Response): Promise<void> => { | ||
| const productsList = await Product.find(); | ||
|
|
||
| productsList.forEach(async (product) => { | ||
| const randomNumber = Math.floor(Math.random() * 6); | ||
|
|
||
| const reviewsList = []; | ||
|
|
||
| for (let i = 0; i < randomNumber; i++) { | ||
| const review = new Review(); | ||
|
|
||
| review.userName = faker.internet.userName(); | ||
| review.text = faker.lorem.paragraph({ min: 1, max: 3 }); | ||
| review.product = product._id; | ||
|
|
||
| const newReview = await review.save(); | ||
| reviewsList.push(newReview); | ||
| } | ||
|
|
||
| try { | ||
| product.reviews = reviewsList.map((review) => review._id); | ||
| await product.save(); | ||
| } catch (err) { | ||
| throw err; | ||
| } | ||
| }); | ||
|
|
||
| res.end(); | ||
| }; | ||
|
|
||
| export { createProductData, createReviews }; | ||
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,201 @@ | ||
| import Product from "../models/product.js"; | ||
| import { Request, Response } from "express"; | ||
| import mongoose from "mongoose"; | ||
| import { | ||
| validateProductSchema, | ||
| validateId, | ||
| validateQuery, | ||
| } from "../models/model_validations.js"; | ||
| import { AggregationMatch, AggregationSort } from "../types/types.js"; | ||
|
|
||
| /** | ||
| * Retrieves list of products in groups of 9. Optional query, category, and price queries can be used to filter and sort results. | ||
| * @param req Client request to server | ||
| * @param res Server response to client | ||
| * @returns void || Promise<Response> | ||
| */ | ||
| const getProducts = async (req: Request, res: Response) => { | ||
| let page = 0; | ||
|
|
||
| // Set page number if included in query params | ||
| if (!isNaN(Number(req.query.page))) { | ||
| page = Number(req.query.page); | ||
| } | ||
|
|
||
| const numToSkip = page * 9; | ||
|
|
||
| const aggMatch: AggregationMatch = { $match: {} }; | ||
| const aggSort: AggregationSort = { $sort: {} }; | ||
|
|
||
| // Filter by category | ||
| const category = req.query.category; | ||
| if (typeof category === "string") { | ||
| const isValidCategory = validateQuery(category); | ||
| if (isValidCategory.error) { | ||
| return res.status(400).send({ | ||
| responseStatus: 400, | ||
| responseMessage: isValidCategory.error.details[0].message, | ||
| }); | ||
| } | ||
| aggMatch.$match.category = new RegExp(category, "gi"); | ||
| } | ||
|
|
||
| // Search term | ||
| const search = req.query.query; | ||
| if (typeof search === "string") { | ||
| const isValidSearch = validateQuery(search); | ||
| if (isValidSearch.error) { | ||
| return res.status(400).send({ | ||
| responseStatus: 400, | ||
| responseMessage: isValidSearch.error.details[0].message, | ||
| }); | ||
| } | ||
| aggMatch.$match.name = new RegExp(search, "gi"); | ||
| } | ||
|
|
||
| // Sort by price | ||
| const price = req.query.price; | ||
| if (typeof price === "string") { | ||
| switch (price) { | ||
| case "lowest": | ||
| aggSort.$sort.price = 1; | ||
| break; | ||
| case "highest": | ||
| aggSort.$sort.price = -1; | ||
| break; | ||
| default: | ||
|
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. nice to see you included a default case |
||
| return res.status(400).send({ | ||
| responseStatus: res.statusCode, | ||
| responseMessage: | ||
| "The price field must be either 'lowest' or 'highest'", | ||
| }); | ||
| } | ||
|
|
||
| const sortedProducts = await Product.aggregate([ | ||
| aggMatch, | ||
| aggSort as any, | ||
| { $unset: "__v" }, | ||
| ]) | ||
| .skip(numToSkip) | ||
| .limit(9); | ||
| const count = await Product.countDocuments(aggMatch.$match); | ||
|
|
||
| return res.status(200).send({ | ||
| responseStatus: res.statusCode, | ||
| responseMessage: sortedProducts, | ||
| resultsFound: count, | ||
| }); | ||
| } | ||
|
|
||
| // Send products unsorted by default | ||
| const filteredProducts = await Product.aggregate([ | ||
| aggMatch, | ||
| { $unset: "__v" }, | ||
| ]) | ||
| .skip(numToSkip) | ||
| .limit(9); | ||
| const count = await Product.countDocuments(aggMatch.$match); | ||
| res.status(200).send({ | ||
| responseStatus: res.statusCode, | ||
| responseMessage: filteredProducts, | ||
| resultsFound: count, | ||
| }); | ||
| }; | ||
|
|
||
| /** | ||
| * Creates a new product and adds it to the database | ||
| * @param req Client request to server | ||
| * @param res Server response to client | ||
| * @returns Promise<Response> || void | ||
| */ | ||
| const createNewProduct = async (req: Request, res: Response) => { | ||
| const validationResult = validateProductSchema(req); | ||
|
|
||
| if (validationResult.error) { | ||
| return res.status(400).send({ | ||
| responseStatus: res.statusCode, | ||
| responseMessage: validationResult.error.details[0].message, | ||
| }); | ||
| } | ||
|
|
||
| const product = new Product(req.body); | ||
|
|
||
| const newProduct = (await product.save()) as mongoose.Document; | ||
|
|
||
| // Clone product to remove version key __v from response | ||
| const { __v, ...otherProps } = newProduct.toObject(); | ||
| const productClone = { ...otherProps }; | ||
|
|
||
| res.status(200).send({ | ||
| responseStatus: res.statusCode, | ||
| responseMessage: productClone, | ||
| }); | ||
| }; | ||
|
|
||
| /** | ||
| * Retrieves a single product by its id | ||
| * @param req Client request to server | ||
| * @param res Server response to client | ||
| * @returns Promise<Response> || void | ||
| */ | ||
| const getProductById = async (req: Request, res: Response) => { | ||
| const id = req.params.productId; | ||
|
|
||
| const validationResult = validateId(id); | ||
|
|
||
| if (validationResult.error) { | ||
| return res.status(400).send({ | ||
| responseStatus: res.statusCode, | ||
| responseMessage: validationResult.error.details[0].message, | ||
| }); | ||
| } | ||
|
|
||
| const product = await Product.findById(id, { __v: 0 }); | ||
|
|
||
| if (!product) { | ||
| return res.status(404).send({ | ||
| responseStatus: res.statusCode, | ||
| responseMessage: "No product found matching id", | ||
| }); | ||
| } | ||
|
|
||
| res.status(200).send({ | ||
| responseStatus: res.statusCode, | ||
| responseMessage: product, | ||
| }); | ||
| }; | ||
|
|
||
| /** | ||
| * Deletes a single product by its id | ||
| * @param req Client request to server | ||
| * @param res Server response to client | ||
| * @returns Promise<Response> || void | ||
| */ | ||
| const deleteProductById = async (req: Request, res: Response) => { | ||
| const id = req.params.productId; | ||
|
|
||
| const validationResult = validateId(id); | ||
|
|
||
| if (validationResult.error) { | ||
| return res.status(400).send({ | ||
| responseStatus: res.statusCode, | ||
| responseMessage: validationResult.error.details[0].message, | ||
| }); | ||
| } | ||
|
|
||
| const deletedProduct = await Product.findByIdAndDelete(id); | ||
|
|
||
| if (!deletedProduct) { | ||
| return res.status(404).send({ | ||
| responseStatus: res.statusCode, | ||
| responseMessage: "No product found matching id", | ||
| }); | ||
| } | ||
|
|
||
| res.status(200).send({ | ||
| responseStatus: res.statusCode, | ||
| responseMessage: deletedProduct, | ||
| }); | ||
| }; | ||
|
|
||
| export { getProducts, createNewProduct, getProductById, deleteProductById }; | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
nice work on function documentation