From 6d975fcdd2262ae5d1009555acb67852a05a20bd Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 6 Apr 2022 10:46:31 +0200 Subject: [PATCH 01/11] Adding product categories --- restapi/products/Product.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/restapi/products/Product.ts b/restapi/products/Product.ts index 22aafeac..763c735d 100644 --- a/restapi/products/Product.ts +++ b/restapi/products/Product.ts @@ -32,6 +32,11 @@ export const product = new Schema( type: String, required: false, }, + category: { + type:String, + required: true, + enum: ['Clothes', 'Decoration', 'Electronics', 'Miscellaneous'] + } }, { versionKey: false, From f3d91ba5ef3ad7f3edb134a09c37877faeb6806b Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 6 Apr 2022 19:16:31 +0200 Subject: [PATCH 02/11] Updating prodyct type and tests --- restapi/products/product.http | 6 +++--- webapp/src/shared/shareddtypes.ts | 1 + webapp/src/tests/cart/ShoppingCart.test.tsx | 4 +++- webapp/src/tests/cart/ShoppingCartTable.test.tsx | 5 +++++ webapp/src/tests/products/ProductBox.test.tsx | 10 ++++++---- webapp/src/tests/products/ProductDetails.test.tsx | 6 ++++-- webapp/src/tests/products/ProductList.test.tsx | 9 +++++---- 7 files changed, 27 insertions(+), 14 deletions(-) diff --git a/restapi/products/product.http b/restapi/products/product.http index 4bcfacde..1f1411e7 100644 --- a/restapi/products/product.http +++ b/restapi/products/product.http @@ -3,7 +3,7 @@ GET {{url}}/products/list ### -GET {{url}}/products/findByCode/1 +GET {{url}}/products/findByCode/0007 ### POST {{url}}/products/create @@ -21,9 +21,9 @@ Content-Type: application/json DELETE {{url}}/products/delete/1 ### -POST {{url}}/products/update/1347 +POST {{url}}/products/update/0045 Content-Type: application/json { - "name": "Camiseta del Sporting chacho" + "category": "Miscellaneous" } diff --git a/webapp/src/shared/shareddtypes.ts b/webapp/src/shared/shareddtypes.ts index cb973ef0..2b8dec41 100644 --- a/webapp/src/shared/shareddtypes.ts +++ b/webapp/src/shared/shareddtypes.ts @@ -16,6 +16,7 @@ export type Product = { price: number; stock: number; image: string; + category: string; }; export type NotificationType = { diff --git a/webapp/src/tests/cart/ShoppingCart.test.tsx b/webapp/src/tests/cart/ShoppingCart.test.tsx index 9ed394cd..e1ea2d0e 100644 --- a/webapp/src/tests/cart/ShoppingCart.test.tsx +++ b/webapp/src/tests/cart/ShoppingCart.test.tsx @@ -1,7 +1,7 @@ import { render } from "@testing-library/react"; +import { BrowserRouter as Router } from "react-router-dom"; import ShoppingCart from "../../components/cart/ShoppingCart"; import { CartItem } from "../../shared/shareddtypes"; -import { BrowserRouter as Router } from "react-router-dom"; //Test for the ShoppingCart component, receives a list of cart items and it is rendered properly. test("A list of two cart items is rendered", async () => { @@ -14,6 +14,7 @@ test("A list of two cart items is rendered", async () => { price: 10, stock: 20, image: "", + category: 'Electronics' }, amount: 1, }, @@ -25,6 +26,7 @@ test("A list of two cart items is rendered", async () => { price: 15, stock: 10, image: "", + category: 'Clothes' }, amount: 2, }, diff --git a/webapp/src/tests/cart/ShoppingCartTable.test.tsx b/webapp/src/tests/cart/ShoppingCartTable.test.tsx index ddb9b1f2..0aab086b 100644 --- a/webapp/src/tests/cart/ShoppingCartTable.test.tsx +++ b/webapp/src/tests/cart/ShoppingCartTable.test.tsx @@ -13,6 +13,7 @@ test("A list of two cart items is rendered", async () => { price: 10, stock: 20, image: "", + category: 'Electronics' }, amount: 1, }, @@ -24,6 +25,7 @@ test("A list of two cart items is rendered", async () => { price: 15, stock: 10, image: "", + category: 'Clothes' }, amount: 2, }, @@ -73,6 +75,7 @@ test("A list of one cart item with amount 0 is not rendered", async () => { price: 10, stock: 20, image: "", + category: 'Electronics' }, amount: 0, }, @@ -102,6 +105,7 @@ test("The increment button is disabled when the amount is equal or higher to the price: 10, stock: 20, image: "", + category: 'Electronics' }, amount: 20, }, @@ -130,6 +134,7 @@ test("Increment and decrement buttons work well.", async () => { price: 10, stock: 20, image: "", + category: 'Electronics' }, amount: 1, }, diff --git a/webapp/src/tests/products/ProductBox.test.tsx b/webapp/src/tests/products/ProductBox.test.tsx index 5e176ea5..6facf9d5 100644 --- a/webapp/src/tests/products/ProductBox.test.tsx +++ b/webapp/src/tests/products/ProductBox.test.tsx @@ -1,9 +1,8 @@ -import React from "react"; - import { fireEvent, render } from "@testing-library/react"; -import ProductBox from "../../components/products/ProductBox"; -import { Product, CartItem } from "../../shared/shareddtypes"; import { BrowserRouter as Router } from "react-router-dom"; +import ProductBox from "../../components/products/ProductBox"; +import { CartItem, Product } from "../../shared/shareddtypes"; + test("A product is rendered", async () => { const product: Product = { @@ -13,6 +12,7 @@ test("A product is rendered", async () => { price: 10, stock: 20, image: "", + category: 'Electronics' }; const { getByText, container } = render( @@ -41,6 +41,7 @@ test("A product is added to the cart", async () => { price: 10, stock: 20, image: "", + category: 'Electronics' }; const cart: CartItem[] = []; @@ -69,6 +70,7 @@ test("URL to product details works", async () => { price: 10, stock: 20, image: "", + category: 'Electronics' }; const cart: CartItem[] = []; diff --git a/webapp/src/tests/products/ProductDetails.test.tsx b/webapp/src/tests/products/ProductDetails.test.tsx index 9eb6d2d9..7750d5ef 100644 --- a/webapp/src/tests/products/ProductDetails.test.tsx +++ b/webapp/src/tests/products/ProductDetails.test.tsx @@ -1,6 +1,6 @@ -import { fireEvent, prettyDOM, render } from "@testing-library/react"; +import { fireEvent, render } from "@testing-library/react"; import ProductDetails from "../../components/products/ProductDetails"; -import { Product, CartItem } from "../../shared/shareddtypes"; +import { Product } from "../../shared/shareddtypes"; //Test that the product details page renders correctly test("Renders product details page", () => { @@ -11,6 +11,7 @@ test("Renders product details page", () => { price: 9.99, stock: 15, image: "", + category: 'Electronics' }; const { getByText, getAllByText } = render( @@ -38,6 +39,7 @@ test("Adds product to cart", () => { price: 9.99, stock: 15, image: "", + category: 'Electronics' }; const onAdd = jest.fn(); diff --git a/webapp/src/tests/products/ProductList.test.tsx b/webapp/src/tests/products/ProductList.test.tsx index 41aaa987..f6770cc3 100644 --- a/webapp/src/tests/products/ProductList.test.tsx +++ b/webapp/src/tests/products/ProductList.test.tsx @@ -1,9 +1,8 @@ -import React from "react"; - -import { fireEvent, render } from "@testing-library/react"; +import { render } from "@testing-library/react"; +import { BrowserRouter as Router } from "react-router-dom"; import ProductList from "../../components/products/ProductList"; import { Product } from "../../shared/shareddtypes"; -import { BrowserRouter as Router } from "react-router-dom"; + test("A list of products is rendered", async () => { const products: Product[] = [ @@ -14,6 +13,7 @@ test("A list of products is rendered", async () => { price: 10, stock: 20, image: "", + category: 'Electronics' }, { code: "9998", @@ -22,6 +22,7 @@ test("A list of products is rendered", async () => { price: 15, stock: 10, image: "", + category: 'Electronics' }, ]; From e65ec150f88a1b3252312c52143f3e0fe11f85b5 Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 6 Apr 2022 19:16:54 +0200 Subject: [PATCH 03/11] Solving bug with product reference --- webapp/src/helpers/ShoppingCartHelper.ts | 13 ++++++++++--- 1 file changed, 10 insertions(+), 3 deletions(-) diff --git a/webapp/src/helpers/ShoppingCartHelper.ts b/webapp/src/helpers/ShoppingCartHelper.ts index 634405ab..276fb793 100644 --- a/webapp/src/helpers/ShoppingCartHelper.ts +++ b/webapp/src/helpers/ShoppingCartHelper.ts @@ -1,5 +1,4 @@ import { v4 as uuidv4 } from "uuid"; - import { createOrder } from "../api/api"; import { CartItem, Order, Product } from "../shared/shareddtypes"; @@ -25,8 +24,16 @@ export async function saveOrder( let productCosts: number = calculateTotal(products, 0); var orderProducts: Product[] = []; products.forEach((item) => { - item.product.stock = item.amount; - orderProducts.push(item.product); + let p: Product = { + code: item.product.code, + name: item.product.name, + description: item.product.description, + price: item.product.price, + stock: item.amount, + image: item.product.image, + category: item.product.category + }; + orderProducts.push(p); }); let order: Order = { From 7a35862122809e6273aa49548a9195b998ff06f5 Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 6 Apr 2022 19:17:07 +0200 Subject: [PATCH 04/11] Adding category to upload and delete product --- .../dashboard/products/DeleteProduct.tsx | 30 +++++++++---- .../dashboard/products/UploadProduct.tsx | 42 +++++++++++++++---- 2 files changed, 56 insertions(+), 16 deletions(-) diff --git a/webapp/src/components/dashboard/products/DeleteProduct.tsx b/webapp/src/components/dashboard/products/DeleteProduct.tsx index 0fe25b4c..ce4f93eb 100644 --- a/webapp/src/components/dashboard/products/DeleteProduct.tsx +++ b/webapp/src/components/dashboard/products/DeleteProduct.tsx @@ -1,23 +1,20 @@ -import React, { useState } from "react"; - import { - Box, - Card, + Alert, Box, Button, Card, Container, MenuItem, Paper, Snackbar, Stack, styled, - TextField, - Alert, - Button, + TextField } from "@mui/material"; - +import React, { useState } from "react"; import { deleteProduct } from "../../../api/api"; import { checkImageExists } from "../../../helpers/ImageHelper"; import { NotificationType, Product } from "../../../shared/shareddtypes"; + + const DEF_IMAGE: string = require("../../../images/not-found.png"); const Img = styled("img")({ @@ -39,6 +36,7 @@ export default function DeleteProduct(props: DeleteProductProps): JSX.Element { const [description, setDescription] = useState(""); const [stock, setStock] = useState(""); const [price, setPrice] = useState(""); + const [category, setCategory] = useState(""); const [dialogOpen, setDialogOpen] = useState(0); const [image, setImage] = useState(DEF_IMAGE); const [notification, setNotification] = useState({ @@ -54,6 +52,7 @@ export default function DeleteProduct(props: DeleteProductProps): JSX.Element { setCode(p.code); setName(p.name); setDescription(p.description); + setCategory(p.category) setPrice(p.price.toString()); setStock(p.stock.toString()); setImage(checkImageExists(p.image)); @@ -87,6 +86,7 @@ export default function DeleteProduct(props: DeleteProductProps): JSX.Element { setCode(""); setName(""); setDescription(""); + setCategory("") setStock(""); setPrice(""); setImage(DEF_IMAGE); @@ -114,7 +114,7 @@ export default function DeleteProduct(props: DeleteProductProps): JSX.Element { > {products.map((product) => ( - {product.name + " (" + product.description + ")"} + {product.code + " - " + product.name} ))} @@ -163,6 +163,18 @@ export default function DeleteProduct(props: DeleteProductProps): JSX.Element { variant="outlined" /> + setCategory(event.target.value)} + /> + (DEF_IMAGE); const getCode = async () => { @@ -76,6 +76,7 @@ export default function UploadImage(props: UploadProductProps): JSX.Element { setDescription(""); setStock(""); setPrice(""); + setCategory(""); }; const checkFields = () => { @@ -99,6 +100,7 @@ export default function UploadImage(props: UploadProductProps): JSX.Element { description: description, price: Number(price), stock: Number(stock), + category: category, }); if (created) { emptyFields(); @@ -184,6 +186,32 @@ export default function UploadImage(props: UploadProductProps): JSX.Element { onChange={(event) => setDescription(event.target.value)} /> + setCategory(event.target.value)} + > + + Clothes + + + Decoration + + + Electronics + + + Miscellaneous + + + Date: Wed, 6 Apr 2022 19:37:55 +0200 Subject: [PATCH 05/11] Adding address to order --- webapp/src/components/checkout/Checkout.tsx | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/webapp/src/components/checkout/Checkout.tsx b/webapp/src/components/checkout/Checkout.tsx index 5db9b03d..2769a806 100644 --- a/webapp/src/components/checkout/Checkout.tsx +++ b/webapp/src/components/checkout/Checkout.tsx @@ -1,21 +1,21 @@ -import * as React from "react"; - import Container from "@mui/material/Container"; import Paper from "@mui/material/Paper"; -import Stepper from "@mui/material/Stepper"; import Step from "@mui/material/Step"; import StepLabel from "@mui/material/StepLabel"; +import Stepper from "@mui/material/Stepper"; import Typography from "@mui/material/Typography"; - +import * as React from "react"; import { updateProduct } from "../../api/api"; -import { CartItem } from "../../shared/shareddtypes"; import { saveOrder } from "../../helpers/ShoppingCartHelper"; - -import ShippingAddress from "./ShippingAddress"; -import ShippingMethod from "./ShippingMethod"; -import Review from "./Review"; +import { CartItem } from "../../shared/shareddtypes"; import Billing from "./Billing"; import OrderConfirmation from "./OrderConfirmation"; +import Review from "./Review"; +import ShippingAddress from "./ShippingAddress"; +import ShippingMethod from "./ShippingMethod"; + + + function getSteps() { return ["Address", "Shipping method", "Review", "Billing", "Confirm"]; @@ -59,7 +59,7 @@ export default function Checkout(props: any) { props.productsCart, costs, props.userEmail, - "Get address not implemented yet" + address ); props.deleteCart(); }; From 3fc699fd164c34fc057ecb9f1ba46b89a326df6c Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 6 Apr 2022 20:48:36 +0200 Subject: [PATCH 06/11] sendingEmail --- restapi/utils/PDFHelper.ts | 126 +------------------------------------ 1 file changed, 2 insertions(+), 124 deletions(-) diff --git a/restapi/utils/PDFHelper.ts b/restapi/utils/PDFHelper.ts index 86104829..7d834c01 100644 --- a/restapi/utils/PDFHelper.ts +++ b/restapi/utils/PDFHelper.ts @@ -1,5 +1,6 @@ import { orderModel } from "../orders/Order"; import { userModel } from "../users/User"; +import { sendInvoiceEmail } from "./emailSender"; const fs = require("fs"); const PDFGenerator = require("pdfkit"); @@ -30,128 +31,5 @@ export const createPDF = async (code: string) => { const ig = new InvoiceGenerator(invoiceData); ig.generate(); - - /*var html = fs.readFileSync(process.cwd() + "/utils/template.html", "utf-8"); - const parse = require("node-html-parser").parse; - - const options: CreateOptions = { - format: "A4", - orientation: "portrait", - }; - - const orderFound = await orderModel.findOne({ - orderCode: code, - }); - - const root = parse(html); - const body = root.querySelector("body"); - let aux = - '
' + - '
' + - '

DeDe

' + - '' + - "
" + - "
" + - "
" + - '
' + - '
' + - '
Send to:
' + - '

' + - orderFound.userEmail + - "

" + - '
' + - orderFound.userAddress + - "
" + - "
" + - '
' + - "

Order invoice" + - "

" + - '
' + - orderFound.date + - "
" + - "
" + - "
" + - '' + - "
" + - "" + - "" + - '' + - '' + - '' + - '' + - '' + - "" + - "" + - ""; - - for (let i: number = 0; i < orderFound.products.length; i++) { - let j: number = i + 1; - aux = - aux + - "" + - '" + - '" + - '" + - '" + - '" + - ""; - } - - aux = - aux + - "" + - "" + - "" + - '' + - '' + - "" + - "" + - "" + - '' + - '' + - "" + - "" + - "" + - '' + - '' + - "" + - "" + - "" + - "
#DESCRIPTIONUNIT PRICEQUANTITYTOTAL
' + - j + - "' + - "

" + - orderFound.products[i].name + - "

" + - orderFound.products[i].description + - "
' + - orderFound.products[i].price + - "€' + - orderFound.products[i].stock + - "' + - orderFound.products[i].stock * orderFound.products[i].price + - "€
SUBTOTAL" + - orderFound.subtotalPrice + - "€
SHIPPING PRICE" + - orderFound.shippingPrice + - "€
TOTAL PRICE" + - orderFound.totalPrice + - "€
" + - "
" + - '"; - - body.insertAdjacentHTML("beforeend", aux); - - pdf - .create(root.toString(), options) - .toFile("./pdf/" + orderFound.orderCode + ".pdf", function (err, res) { - if (err) console.log(err); - else { - sendInvoiceEmail(orderFound.userEmail, orderFound.orderCode); - } - });*/ + sendInvoiceEmail(orderFound.userEmail, orderFound.orderCode); }; From 924a01c94e49306f35a60fdbf9638fbc81887794 Mon Sep 17 00:00:00 2001 From: Pablo Date: Wed, 6 Apr 2022 23:38:40 +0200 Subject: [PATCH 07/11] Sending html order resume to email --- restapi/utils/PDFHelper.ts | 113 +++++++++++++++++++++++++++++++++-- restapi/utils/emailSender.ts | 20 ++++--- 2 files changed, 121 insertions(+), 12 deletions(-) diff --git a/restapi/utils/PDFHelper.ts b/restapi/utils/PDFHelper.ts index 7d834c01..96c6db5f 100644 --- a/restapi/utils/PDFHelper.ts +++ b/restapi/utils/PDFHelper.ts @@ -1,5 +1,4 @@ import { orderModel } from "../orders/Order"; -import { userModel } from "../users/User"; import { sendInvoiceEmail } from "./emailSender"; const fs = require("fs"); @@ -12,7 +11,7 @@ export const createPDF = async (code: string) => { const orderFound = await orderModel.findOne({ orderCode: code, }); - const user = await userModel.findOne({ email: orderFound.userEmail }); + /*const user = await userModel.findOne({ email: orderFound.userEmail }); const invoiceData = { addresses: { shipping: { @@ -30,6 +29,112 @@ export const createPDF = async (code: string) => { }; const ig = new InvoiceGenerator(invoiceData); - ig.generate(); - sendInvoiceEmail(orderFound.userEmail, orderFound.orderCode); + ig.generate();*/ + var html = fs.readFileSync(process.cwd() + "/utils/template.html", "utf-8"); + const parse = require("node-html-parser").parse; + + const root = parse(html); + const body = root.querySelector("body"); + let aux = + '
' + + '
' + + '

DeDe

' + + '

' + + "
" + + "
" + + "
" + + '
' + + '
' + + '
Send to:
' + + '

' + + orderFound.userEmail + + "

" + + '
' + + orderFound.userAddress + + "
" + + "
" + + '
' + + "

Order invoice" + + "

" + + '
' + + orderFound.date + + "
" + + "
" + + "
" + + '' + + "
" + + "" + + "" + + '' + + '' + + '' + + '' + + '' + + "" + + "" + + ""; + + for (let i: number = 0; i < orderFound.products.length; i++) { + let j: number = i + 1; + aux = + aux + + "" + + '" + + '" + + '" + + '" + + '" + + ""; + } + + aux = + aux + + "" + + "" + + "" + + '' + + '' + + "" + + "" + + "" + + '' + + '' + + "" + + "" + + "" + + '' + + '' + + "" + + "" + + "" + + "
#DESCRIPTIONUNIT PRICEQUANTITYTOTAL
' + + j + + "' + + "

" + + orderFound.products[i].name + + "

" + + orderFound.products[i].description + + "
' + + orderFound.products[i].price + + "€' + + orderFound.products[i].stock + + "' + + orderFound.products[i].stock * orderFound.products[i].price + + "€
SUBTOTAL" + + orderFound.subtotalPrice + + "€
SHIPPING PRICE" + + orderFound.shippingPrice + + "€
TOTAL PRICE" + + orderFound.totalPrice + + "€
" + + "
" + + '"; + + body.insertAdjacentHTML("beforeend", aux); + + sendInvoiceEmail(orderFound.userEmail, orderFound.orderCode, root.toString()); }; diff --git a/restapi/utils/emailSender.ts b/restapi/utils/emailSender.ts index 0fa4f7e6..49ee0923 100644 --- a/restapi/utils/emailSender.ts +++ b/restapi/utils/emailSender.ts @@ -1,4 +1,3 @@ - const nodemailer = require("nodemailer"); const { v4: uuidv4 } = require("uuid"); const fs = require("fs"); @@ -12,10 +11,11 @@ sgMail.setApiKey( export const sendInvoiceEmail: Function = ( email: string, - orderCode: string + orderCode: string, + message: string ) => { - const pathToAttachment = `${__dirname}/pdf/` + orderCode + ".pdf"; - const attachment = fs.readFileSync(pathToAttachment).toString("base64"); + /*const pathToAttachment = `${__dirname}/pdf/` + orderCode + ".pdf"; + const attachment = fs.readFileSync(pathToAttachment).toString("base64");*/ const mailOptions = { to: email, @@ -23,20 +23,24 @@ export const sendInvoiceEmail: Function = ( subject: "DeDe Order Invoice", html: "

Thank you for trusting in DeDe and buying with us.

Here you have the receipt of your purchase." + - "

We hope to see you soon :)

", - attachments: [ + "

We hope to see you soon :)






" + + message, + /*attachments: [ { content: attachment, filename: orderCode + ".pdf", type: "application/pdf", disposition: "attachment", }, - ], + ],*/ }; sgMail.send(mailOptions); }; -export const sendVerificationEmail: Function = async (email: string, uniqueString: string) => { +export const sendVerificationEmail: Function = async ( + email: string, + uniqueString: string +) => { const currentUrl = "http://localhost:5000"; const mailOptions = { From 6d14dc566ab067d86394ef92039bc6e67fb47f0b Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 7 Apr 2022 00:09:56 +0200 Subject: [PATCH 08/11] Adding filter and order requests --- restapi/products/ProductController.ts | 29 +++++++++++++++++++++++++++ restapi/products/ProductRoutes.ts | 8 ++++++-- restapi/products/product.http | 6 ++++++ 3 files changed, 41 insertions(+), 2 deletions(-) diff --git a/restapi/products/ProductController.ts b/restapi/products/ProductController.ts index 4672a410..3846e9bf 100644 --- a/restapi/products/ProductController.ts +++ b/restapi/products/ProductController.ts @@ -84,3 +84,32 @@ export const updateProduct: RequestHandler = async (req, res) => { res.status(403).json(); } }; + +export const filterBy: RequestHandler = async (req, res) => { + const category = req.params.category; + let products; + + if ( + category !== "Clothes" && + category !== "Decoration" && + category !== "Electronics" && + category !== "Miscellaneous" + ) { + products = await productModel.find(); + } else { + products = await productModel.find({ category: category }); + } + + return res.json(products); +}; + +export const orderBy: RequestHandler = async (req, res) => { + let mode = req.params.mode; + let products; + if (mode !== "asc" && mode !== "desc") { + products = await productModel.find(); + } else { + products = await productModel.find().sort({ price: mode }); + } + return res.json(products); +}; diff --git a/restapi/products/ProductRoutes.ts b/restapi/products/ProductRoutes.ts index 9c1256cb..08244d1c 100644 --- a/restapi/products/ProductRoutes.ts +++ b/restapi/products/ProductRoutes.ts @@ -1,7 +1,7 @@ -import express, { Request, Response, Router } from "express"; +import express, { Router } from "express"; +import multer from "../utils/multer"; import * as ProdctController from "./ProductController"; -import multer from "../utils/multer"; const api: Router = express.Router(); @@ -9,6 +9,10 @@ api.get("/products", ProdctController.getProducts); api.get("/products/findByCode/:code", ProdctController.getProduct); +api.get('/products/filter/:category', ProdctController.filterBy) + +api.get('/products/order/:mode', ProdctController.orderBy) + api.post("/products", multer.single("image"), ProdctController.createProduct); api.post("/products/delete/:code", ProdctController.deleteProduct); diff --git a/restapi/products/product.http b/restapi/products/product.http index 1f1411e7..a233cd92 100644 --- a/restapi/products/product.http +++ b/restapi/products/product.http @@ -27,3 +27,9 @@ Content-Type: application/json { "category": "Miscellaneous" } + +### +GET {{url}}/products/order/desc + +### +GET {{url}}/products/filter/Electronics From 359276c3eab5faf718489d0e0cbedee988902b9b Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 7 Apr 2022 00:10:08 +0200 Subject: [PATCH 09/11] Add ProductCategories enum --- webapp/src/shared/shareddtypes.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/webapp/src/shared/shareddtypes.ts b/webapp/src/shared/shareddtypes.ts index 2b8dec41..17ae53da 100644 --- a/webapp/src/shared/shareddtypes.ts +++ b/webapp/src/shared/shareddtypes.ts @@ -19,6 +19,13 @@ export type Product = { category: string; }; +export const ProductCategories = { + Clothes: "Clothes", + Decoration: "Decoration", + Electronics: "Electronics", + Misccellaneous: "Miscellaneous", +}; + export type NotificationType = { severity: AlertColor; message: string; From d65b9ecc464449b00471449335453cea6fe547b5 Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 7 Apr 2022 00:19:40 +0200 Subject: [PATCH 10/11] Filter and order in same method. Webapp requesst also added --- restapi/products/ProductController.ts | 29 +++++----- restapi/products/ProductRoutes.ts | 4 +- restapi/products/product.http | 2 +- webapp/src/api/api.ts | 82 +++++++++++++++++---------- 4 files changed, 68 insertions(+), 49 deletions(-) diff --git a/restapi/products/ProductController.ts b/restapi/products/ProductController.ts index 3846e9bf..58e281bc 100644 --- a/restapi/products/ProductController.ts +++ b/restapi/products/ProductController.ts @@ -85,7 +85,8 @@ export const updateProduct: RequestHandler = async (req, res) => { } }; -export const filterBy: RequestHandler = async (req, res) => { +export const filterAndOrderBy: RequestHandler = async (req, res) => { + let mode = req.params.mode; const category = req.params.category; let products; @@ -95,21 +96,19 @@ export const filterBy: RequestHandler = async (req, res) => { category !== "Electronics" && category !== "Miscellaneous" ) { - products = await productModel.find(); - } else { - products = await productModel.find({ category: category }); - } - - return res.json(products); -}; - -export const orderBy: RequestHandler = async (req, res) => { - let mode = req.params.mode; - let products; - if (mode !== "asc" && mode !== "desc") { - products = await productModel.find(); + if (mode !== "asc" && mode !== "desc") { + products = await productModel.find(); + } else { + products = await productModel.find().sort({ price: mode }); + } } else { - products = await productModel.find().sort({ price: mode }); + if (mode !== "asc" && mode !== "desc") { + products = await productModel.find({ category: category }); + } else { + products = await productModel + .find({ category: category }) + .sort({ price: mode }); + } } return res.json(products); }; diff --git a/restapi/products/ProductRoutes.ts b/restapi/products/ProductRoutes.ts index 08244d1c..ccd6567b 100644 --- a/restapi/products/ProductRoutes.ts +++ b/restapi/products/ProductRoutes.ts @@ -9,9 +9,7 @@ api.get("/products", ProdctController.getProducts); api.get("/products/findByCode/:code", ProdctController.getProduct); -api.get('/products/filter/:category', ProdctController.filterBy) - -api.get('/products/order/:mode', ProdctController.orderBy) +api.get('/products/filter&order/:category&:mode', ProdctController.filterAndOrderBy) api.post("/products", multer.single("image"), ProdctController.createProduct); diff --git a/restapi/products/product.http b/restapi/products/product.http index a233cd92..125dfe74 100644 --- a/restapi/products/product.http +++ b/restapi/products/product.http @@ -32,4 +32,4 @@ Content-Type: application/json GET {{url}}/products/order/desc ### -GET {{url}}/products/filter/Electronics +GET {{url}}/products/filter&order/Electronics&asc diff --git a/webapp/src/api/api.ts b/webapp/src/api/api.ts index 07d96049..97510551 100644 --- a/webapp/src/api/api.ts +++ b/webapp/src/api/api.ts @@ -1,7 +1,9 @@ -import { Order, Review, User, Product } from "../shared/shareddtypes"; +import { Order, Product, Review, User } from "../shared/shareddtypes"; const apiEndPoint = process.env.REACT_APP_API_URI || "http://localhost:5000"; +// USERS + export async function addUser(user: User): Promise { let response = await fetch(apiEndPoint + "/users", { method: "POST", @@ -44,6 +46,8 @@ export async function getUser(userEmail: String): Promise { return response.json(); } +// PRODUCTS + export async function getProducts(): Promise { let response = await fetch(apiEndPoint + "/products/"); return response.json(); @@ -56,35 +60,6 @@ export async function getProduct(productCode: string): Promise { return response.json(); } -export async function getPlaces( - x: number, - y: number, - radiusMeters: number, - maxResults: number -): Promise { - const url = - "https://api.geoapify.com/v2/places?categories=commercial&filter=circle:" + - x + - "," + - y + - "," + - radiusMeters + - "&bias=proximity:" + - x + - "," + - y + - "&limit=" + - maxResults + - "&apiKey=" + - process.env.REACT_APP_GEOAPIFY_KEY; - - let places; - await fetch(url, { - method: "GET", - }).then((response) => (places = response.json())); - return places; -} - export async function updateProduct(product: Product) { await fetch(apiEndPoint + "/products/update/" + product.code, { method: "POST", @@ -133,6 +108,20 @@ export async function deleteProduct(code: string) { }); } +// Mode must be desc or asc. If not default order +// Category must be Clothes, Decoration, Elecrtonics or Miscellaneous. If not all categories +export async function filterProductsByCategory( + category: string, + mode: string +): Promise { + let response = await fetch( + apiEndPoint + "/products/filter&order/" + category + "&" + mode + ); + return response.json(); +} + +// ORDERS + export async function createOrder(body: any) { await fetch(apiEndPoint + "/orders", { method: "POST", @@ -182,6 +171,8 @@ export async function getOrders(): Promise { return response.json(); } +// REVIEWS + export async function getReviewsByCode(code: string): Promise { let response = await fetch(apiEndPoint + "/reviews/listByCode/" + code); return response.json(); @@ -215,3 +206,34 @@ export async function addReview(review: Review): Promise { return true; } else return false; } + +// PLACES + +export async function getPlaces( + x: number, + y: number, + radiusMeters: number, + maxResults: number +): Promise { + const url = + "https://api.geoapify.com/v2/places?categories=commercial&filter=circle:" + + x + + "," + + y + + "," + + radiusMeters + + "&bias=proximity:" + + x + + "," + + y + + "&limit=" + + maxResults + + "&apiKey=" + + process.env.REACT_APP_GEOAPIFY_KEY; + + let places; + await fetch(url, { + method: "GET", + }).then((response) => (places = response.json())); + return places; +} From b4ce073492db48c7e9f0070de6753de0e1e70c26 Mon Sep 17 00:00:00 2001 From: Pablo Date: Thu, 7 Apr 2022 00:47:59 +0200 Subject: [PATCH 11/11] Adding and updating tests --- restapi/tests/orders.test.ts | 1 + restapi/tests/products.test.ts | 86 ++++++++++++++++++++++++++++++---- 2 files changed, 79 insertions(+), 8 deletions(-) diff --git a/restapi/tests/orders.test.ts b/restapi/tests/orders.test.ts index 531cffb0..d4bafd5c 100644 --- a/restapi/tests/orders.test.ts +++ b/restapi/tests/orders.test.ts @@ -71,6 +71,7 @@ describe("orders", () => { "Do you wanna show your friends that you are THE GREATEST IMPOSTER? Then this shirt is for you!", stock: 1, image: "0001.png", + category: "Clothes", }, ], date: new Date(), diff --git a/restapi/tests/products.test.ts b/restapi/tests/products.test.ts index fc0637b4..b7266c78 100644 --- a/restapi/tests/products.test.ts +++ b/restapi/tests/products.test.ts @@ -71,6 +71,7 @@ describe("prodcuts", () => { name: "Super SUS T-Shirt", price: 9.5, image: "0001.png", + category: "Clothes", }) ); }); @@ -91,6 +92,7 @@ describe("prodcuts", () => { price: 0.99, description: "Another test product", stock: 0, + category: "Clothes", }); expect(response.statusCode).toBe(403); }); @@ -110,6 +112,7 @@ describe("prodcuts", () => { price: 0.99, description: "Another test product", stock: 0, + category: "Clothes", }); expect(response.statusCode).toBe(200); expect(response.body.name).toBe("testProduct"); @@ -128,6 +131,7 @@ describe("prodcuts", () => { price: 0.99, description: "A failure insert test product", stock: 0, + category: "Clothes", }); expect(response.statusCode).toBe(409); }); @@ -144,6 +148,23 @@ describe("prodcuts", () => { expect(response.statusCode).toBe(412); }); + it("Can't create a product with incorrect or missing category", async () => { + let userToken = await getToken(); + const response: Response = await request(app) + .post("/products") + .set("token", userToken) + .set("email", "test") + .send({ + code: uuidv4(), + name: "testFailProduct", + price: 0.99, + description: "A failure insert test product", + stock: 0, + category: "Nothing", + }); + expect(response.statusCode).toBe(412); + }); + it("Can update a product correctly", async () => { let userToken = await getToken(); const response: Response = await request(app) @@ -151,23 +172,25 @@ describe("prodcuts", () => { .set("token", userToken) .set("email", "test") .send({ - stock: 1000, + stock: 10, }); expect(response.statusCode).toBe(200); - expect(response.body.stock).toBe(1000); + expect(response.body.stock).toBe(10); }); it("Can't update a product without being admin or manager", async () => { - const token: Response = await request(app).post("/users/requestToken/").send({ - email: "test1", - password: "test", - }); + const token: Response = await request(app) + .post("/users/requestToken/") + .send({ + email: "test1", + password: "test", + }); const response: Response = await request(app) .post("/products/update/" + productCode) .set("token", token.body) .set("email", "test1") .send({ - stock: 1000, + stock: 10, }); expect(response.statusCode).toBe(403); }); @@ -176,7 +199,7 @@ describe("prodcuts", () => { const response: Response = await request(app) .post("/products/update/" + productCode) .send({ - stock: 1000, + stock: 10, }); expect(response.statusCode).toBe(403); }); @@ -197,6 +220,53 @@ describe("prodcuts", () => { .send(); expect(response.statusCode).toBe(403); }); + + /* + Testing filtering and ordering products + */ + it("Can filter by category", async () => { + const response: Response = await request(app) + .get("/products/filter&order/Electronics&A") + .send(); + expect(response.statusCode).toBe(200); + expect(response.body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ category: "Electronics" }), + ]) + ); + }); + + it("Can filter by category and order by price", async () => { + const response: Response = await request(app) + .get("/products/filter&order/Electronics&asc") + .send(); + expect(response.statusCode).toBe(200); + expect(response.body).toEqual( + expect.arrayContaining([ + expect.objectContaining({ category: "Electronics" }), + ]) + ); + expect(response.body[0].price).toBe(20.99); + }); + + it("Can order by price", async () => { + const response: Response = await request(app) + .get("/products/filter&order/Category&asc") + .send(); + expect(response.statusCode).toBe(200); + expect(response.body[0].price).toBe(1.99); + }); + + it("Check normal order when 'wrong' input", async () => { + const response: Response = await request(app) + .get("/products/filter&order/a&a") + .send(); + expect(response.statusCode).toBe(200); + expect(response.body[0].price).toBe(12.95); + expect(response.body[0].code).toBe("1234"); + expect(response.body[1].price).toBe(9.5); + expect(response.body[1].code).toBe("0001"); + }); }); async function getToken() {