Commit da105c4b authored by Egor Kremnev's avatar Egor Kremnev

add category model

parent 2c21f1d4
......@@ -4,11 +4,13 @@ const app = express();
const {port, db: dbConfig} = require('./config');
const createProductsRoutes = require('./routes/products');
const mongoose = require('mongoose');
const categoryRoutes = require('./routes/categories');
app.use(cors());
app.use(express.json());
app.use(express.static('public'));
app.use('/api/v1/products', createProductsRoutes());
app.use('/api/v1/categories', categoryRoutes);
const run = async () => {
await mongoose.connect(
......
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const CategorySchema = new Schema({
title: {
type: String,
required: [true, 'Заголовк обязателен'],
unique: [true, 'Заголовк должен быть уникален'],
},
description: String
});
const Category = mongoose.model('Category', CategorySchema);
module.exports = Category;
const mongoose = require('mongoose');
const Schema = mongoose.Schema;
const idValidator = require('mongoose-id-validator');
const ProductSchema = new Schema({
title: {
required: true,
required: [true, 'Заголовк обязателен'],
type: String
},
price: {
required: true,
required: [true, 'Цена обязателена'],
type: Number
},
description: String,
image: String
image: String,
category: {
type: Schema.Types.ObjectId,
ref: 'Category',
required: [true, 'Категория обязателена'],
}
});
ProductSchema.plugin(
idValidator,
{message: "Передайте корректный идентикатор категории"}
);
const Product = mongoose.model('Product', ProductSchema);
module.exports = Product;
......@@ -13,7 +13,8 @@
"express": "^4.18.2",
"fix-esm": "^1.0.1",
"mongodb": "^5.3.0",
"mongoose": "^7.0.4",
"mongoose": "^6.5.0",
"mongoose-id-validator": "^0.6.0",
"multer": "^1.4.5-lts.1",
"nanoid": "^4.0.2"
},
......@@ -542,6 +543,25 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/binary-extensions": {
"version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
......@@ -631,6 +651,29 @@
"node": ">=14.20.1"
}
},
"node_modules/buffer": {
"version": "5.7.1",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
"integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.1.13"
}
},
"node_modules/buffer-from": {
"version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
......@@ -726,6 +769,14 @@
"fsevents": "~2.3.2"
}
},
"node_modules/clone": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz",
"integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==",
"engines": {
"node": ">=0.8"
}
},
"node_modules/color-convert": {
"version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
......@@ -821,6 +872,14 @@
"ms": "2.0.0"
}
},
"node_modules/denque": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/denque/-/denque-2.1.0.tgz",
"integrity": "sha512-HVQE3AAb/pxF8fQAoiqpvg9i3evqug3hoiwakOyZAwJm+6vZehbkYXZ0l4JxS+I3QxM97v5aaRNhj8v5oBhekw==",
"engines": {
"node": ">=0.10"
}
},
"node_modules/depd": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
......@@ -1097,6 +1156,25 @@
"node": ">=0.10.0"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
]
},
"node_modules/ignore-by-default": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
......@@ -1196,12 +1274,9 @@
}
},
"node_modules/kareem": {
"version": "2.5.1",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz",
"integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==",
"engines": {
"node": ">=12.0.0"
}
"version": "2.4.1",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.4.1.tgz",
"integrity": "sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA=="
},
"node_modules/lru-cache": {
"version": "5.1.1",
......@@ -1341,56 +1416,61 @@
}
},
"node_modules/mongoose": {
"version": "7.0.4",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.0.4.tgz",
"integrity": "sha512-MEmQOOqQUvW1PJcji64NtA2EFGHrEvk9o4g//isVYSJW2+8Y8u49C2qFBKzn1t6/l9onQn012o/PcFqR6ixQpQ==",
"version": "6.5.0",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.5.0.tgz",
"integrity": "sha512-swOX8ZEbmCeJaEs29B1j67StBIhuOccNNkipbVhsnLYYCDpNE7heM9W54MFGwN5es9tGGoxINHSzOhJ9kTOZGg==",
"dependencies": {
"bson": "^5.0.1",
"kareem": "2.5.1",
"mongodb": "5.1.0",
"bson": "^4.6.5",
"kareem": "2.4.1",
"mongodb": "4.8.1",
"mpath": "0.9.0",
"mquery": "5.0.0",
"mquery": "4.0.3",
"ms": "2.1.3",
"sift": "16.0.1"
"sift": "16.0.0"
},
"engines": {
"node": ">=14.0.0"
"node": ">=12.0.0"
},
"funding": {
"type": "opencollective",
"url": "https://opencollective.com/mongoose"
}
},
"node_modules/mongoose-id-validator": {
"version": "0.6.0",
"resolved": "https://registry.npmjs.org/mongoose-id-validator/-/mongoose-id-validator-0.6.0.tgz",
"integrity": "sha512-y3b3/PkmaiMKSbKB8tsEEGUjgCgKQGpD2Ood7jaVEob3V2HWgnmNKCgiSQUpEtQuDU0lUnLJQ5JE9PH1Bytziw==",
"dependencies": {
"clone": "^1.0.2",
"traverse": "^0.6.6"
}
},
"node_modules/mongoose/node_modules/bson": {
"version": "4.7.2",
"resolved": "https://registry.npmjs.org/bson/-/bson-4.7.2.tgz",
"integrity": "sha512-Ry9wCtIZ5kGqkJoi6aD8KjxFZEx78guTQDnpXWiNthsxzrxAK/i8E6pCHAIZTbaEFWcOCvbecMukfK7XUvyLpQ==",
"dependencies": {
"buffer": "^5.6.0"
},
"engines": {
"node": ">=6.9.0"
}
},
"node_modules/mongoose/node_modules/mongodb": {
"version": "5.1.0",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.1.0.tgz",
"integrity": "sha512-qgKb7y+EI90y4weY3z5+lIgm8wmexbonz0GalHkSElQXVKtRuwqXuhXKccyvIjXCJVy9qPV82zsinY0W1FBnJw==",
"version": "4.8.1",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.8.1.tgz",
"integrity": "sha512-/NyiM3Ox9AwP5zrfT9TXjRKDJbXlLaUDQ9Rg//2lbg8D2A8GXV0VidYYnA/gfdK6uwbnL4FnAflH7FbGw3TS7w==",
"dependencies": {
"bson": "^5.0.1",
"mongodb-connection-string-url": "^2.6.0",
"socks": "^2.7.1"
"bson": "^4.6.5",
"denque": "^2.0.1",
"mongodb-connection-string-url": "^2.5.2",
"socks": "^2.6.2"
},
"engines": {
"node": ">=14.20.1"
"node": ">=12.9.0"
},
"optionalDependencies": {
"saslprep": "^1.0.3"
},
"peerDependencies": {
"@aws-sdk/credential-providers": "^3.201.0",
"mongodb-client-encryption": "^2.3.0",
"snappy": "^7.2.2"
},
"peerDependenciesMeta": {
"@aws-sdk/credential-providers": {
"optional": true
},
"mongodb-client-encryption": {
"optional": true
},
"snappy": {
"optional": true
}
}
},
"node_modules/mongoose/node_modules/ms": {
......@@ -1407,14 +1487,14 @@
}
},
"node_modules/mquery": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz",
"integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==",
"version": "4.0.3",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz",
"integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==",
"dependencies": {
"debug": "4.x"
},
"engines": {
"node": ">=14.0.0"
"node": ">=12.0.0"
}
},
"node_modules/mquery/node_modules/debug": {
......@@ -1818,9 +1898,9 @@
}
},
"node_modules/sift": {
"version": "16.0.1",
"resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz",
"integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ=="
"version": "16.0.0",
"resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz",
"integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ=="
},
"node_modules/simple-update-notifier": {
"version": "1.1.0",
......@@ -1965,6 +2045,14 @@
"node": ">=12"
}
},
"node_modules/traverse": {
"version": "0.6.7",
"resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz",
"integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==",
"funding": {
"url": "https://github.com/sponsors/ljharb"
}
},
"node_modules/type-is": {
"version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
......
......@@ -16,7 +16,8 @@
"express": "^4.18.2",
"fix-esm": "^1.0.1",
"mongodb": "^5.3.0",
"mongoose": "^7.0.4",
"mongoose": "^6.5.0",
"mongoose-id-validator": "^0.6.0",
"multer": "^1.4.5-lts.1",
"nanoid": "^4.0.2"
},
......
const router = require('express').Router();
const Category = require('../models/Category');
router.get('/', async (req, res) => {
try {
res.send(await Category.find());
} catch (e) {
res.sendStatus(502);
}
});
router.post('/', async (req, res) => {
const category = new Category(req.body);
try {
await category.save();
res.send(category);
} catch (e) {
res
.status(400)
.send(e);
}
});
module.exports = router;
......@@ -38,8 +38,18 @@ const createRoutes = () => {
});
router.get('/', async (req, res) => {
let query = {};
if (req.query.category) {
query.category = req.query.category;
}
if (req.query.title) {
query.title = req.query.title;
}
try {
res.send(await Product.find());
res.send(await Product.find(query).populate('category', "title"));
} catch (e) {
res.sendStatus(500);
}
......@@ -47,7 +57,7 @@ const createRoutes = () => {
router.get('/:id', async (req, res) => {
try {
const result = await Product.findById(req.params.id);
const result = await Product.findById(req.params.id).populate('category', "title description");
if (result) return res.send(result);
......
......@@ -18,6 +18,7 @@
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.3.5",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
......
......@@ -13,6 +13,7 @@
"@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0",
"axios": "^1.3.5",
"prop-types": "^15.8.1",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"react-redux": "^8.0.5",
......
import {Card, CardActions, CardContent, CardHeader, Grid, IconButton, CardMedia} from "@mui/material";
import {Link} from "react-router-dom";
import {PRODUCT_VIEW} from "../../../constants/routes";
import {PRODUCT_VIEW} from "../../../../constants/routes";
import ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import imageNotAvailable from '../../../assets/images/image_not_available.png';
import {uploadUrl} from "../../../constants/config";
import imageNotAvailable from '../../../../assets/images/image_not_available.png';
import {uploadUrl} from "../../../../constants/config";
import PropTypes from 'prop-types';
const ProductItem = ({product}) => {
const imagePath = product.image
? uploadUrl + '/' + product.image
/**
* @param {String} id
* @param {Number} price
* @param {String} title
* @param {String} image
* @returns {JSX.Element}
* @constructor
*/
const ProductItem = ({id, price, title, image}) => {
const imagePath = image
? uploadUrl + '/' + image
: imageNotAvailable;
return (
<Grid item xs={12} sm={12} md={6} lg={4}>
<Card>
<CardHeader title={product.title}/>
<CardHeader title={title}/>
<CardContent>
<CardMedia
image={imagePath}
title={product.title}
title={title}
sx={{maxWidth:200, height: 200}}
/>
<strong style={{marginLeft: '10px'}}>
Price: {product.price} KZT
Price: {price} KZT
</strong>
</CardContent>
<CardActions>
<IconButton component={Link} to={PRODUCT_VIEW.replace(':id', product._id)}>
<IconButton component={Link} to={PRODUCT_VIEW.replace(':id', id)}>
<ArrowForwardIosIcon/>
</IconButton>
</CardActions>
......@@ -34,4 +43,11 @@ const ProductItem = ({product}) => {
);
};
ProductItem.propTypes = {
id: PropTypes.string.isRequired,
title: PropTypes.string.isRequired,
price: PropTypes.number.isRequired,
image: PropTypes.string
};
export default ProductItem;
import ProductItem from "./ProductItem/ProductItem";
import {Grid} from "@mui/material";
const ProductList = ({products}) => {
return <Grid
container
item
direction="row"
justifyContent="space-between"
alignItems="center"
>
{
products.map(product => (
<ProductItem
key={product._id}
id={product._id}
title={product.title}
price={product.price}
image={product.image}
/>
))
}
</Grid>;
};
export default ProductList;
......@@ -4,7 +4,7 @@ import {PRODUCT_ADD} from "../../constants/routes";
import {useDispatch, useSelector} from "react-redux";
import {fetchProducts} from "../../store/actions/productsActions";
import {useEffect} from "react";
import ProductItem from "../../components/Product/ProductItem/ProductItem";
import ProductList from "../../components/Product/ProductList/ProductList";
const Products = () => {
const dispatch = useDispatch();
......@@ -34,22 +34,7 @@ const Products = () => {
</Button>
</Grid>
</Grid>
<Grid
container
item
direction="row"
justifyContent="space-between"
alignItems="center"
>
{
products.map(product => (
<ProductItem
key={product._id}
product={product}
/>
))
}
</Grid>
<ProductList products={products} />
</Grid>
);
};
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment