Commit da105c4b authored by Egor Kremnev's avatar Egor Kremnev

add category model

parent 2c21f1d4
...@@ -4,11 +4,13 @@ const app = express(); ...@@ -4,11 +4,13 @@ const app = express();
const {port, db: dbConfig} = require('./config'); const {port, db: dbConfig} = require('./config');
const createProductsRoutes = require('./routes/products'); const createProductsRoutes = require('./routes/products');
const mongoose = require('mongoose'); const mongoose = require('mongoose');
const categoryRoutes = require('./routes/categories');
app.use(cors()); app.use(cors());
app.use(express.json()); app.use(express.json());
app.use(express.static('public')); app.use(express.static('public'));
app.use('/api/v1/products', createProductsRoutes()); app.use('/api/v1/products', createProductsRoutes());
app.use('/api/v1/categories', categoryRoutes);
const run = async () => { const run = async () => {
await mongoose.connect( 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 mongoose = require('mongoose');
const Schema = mongoose.Schema; const Schema = mongoose.Schema;
const idValidator = require('mongoose-id-validator');
const ProductSchema = new Schema({ const ProductSchema = new Schema({
title: { title: {
required: true, required: [true, 'Заголовк обязателен'],
type: String type: String
}, },
price: { price: {
required: true, required: [true, 'Цена обязателена'],
type: Number type: Number
}, },
description: String, 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); const Product = mongoose.model('Product', ProductSchema);
module.exports = Product; module.exports = Product;
...@@ -13,7 +13,8 @@ ...@@ -13,7 +13,8 @@
"express": "^4.18.2", "express": "^4.18.2",
"fix-esm": "^1.0.1", "fix-esm": "^1.0.1",
"mongodb": "^5.3.0", "mongodb": "^5.3.0",
"mongoose": "^7.0.4", "mongoose": "^6.5.0",
"mongoose-id-validator": "^0.6.0",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"nanoid": "^4.0.2" "nanoid": "^4.0.2"
}, },
...@@ -542,6 +543,25 @@ ...@@ -542,6 +543,25 @@
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"dev": true "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": { "node_modules/binary-extensions": {
"version": "2.2.0", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
...@@ -631,6 +651,29 @@ ...@@ -631,6 +651,29 @@
"node": ">=14.20.1" "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": { "node_modules/buffer-from": {
"version": "1.1.2", "version": "1.1.2",
"resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz",
...@@ -726,6 +769,14 @@ ...@@ -726,6 +769,14 @@
"fsevents": "~2.3.2" "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": { "node_modules/color-convert": {
"version": "1.9.3", "version": "1.9.3",
"resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
...@@ -821,6 +872,14 @@ ...@@ -821,6 +872,14 @@
"ms": "2.0.0" "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": { "node_modules/depd": {
"version": "2.0.0", "version": "2.0.0",
"resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz",
...@@ -1097,6 +1156,25 @@ ...@@ -1097,6 +1156,25 @@
"node": ">=0.10.0" "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": { "node_modules/ignore-by-default": {
"version": "1.0.1", "version": "1.0.1",
"resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz",
...@@ -1196,12 +1274,9 @@ ...@@ -1196,12 +1274,9 @@
} }
}, },
"node_modules/kareem": { "node_modules/kareem": {
"version": "2.5.1", "version": "2.4.1",
"resolved": "https://registry.npmjs.org/kareem/-/kareem-2.5.1.tgz", "resolved": "https://registry.npmjs.org/kareem/-/kareem-2.4.1.tgz",
"integrity": "sha512-7jFxRVm+jD+rkq3kY0iZDJfsO2/t4BBPeEb2qKn2lR/9KhuksYk5hxzfRYWMPV8P/x2d0kHD306YyWLzjjH+uA==", "integrity": "sha512-aJ9opVoXroQUPfovYP5kaj2lM7Jn02Gw13bL0lg9v0V7SaUc0qavPs0Eue7d2DcC3NjqI6QAUElXNsuZSeM+EA=="
"engines": {
"node": ">=12.0.0"
}
}, },
"node_modules/lru-cache": { "node_modules/lru-cache": {
"version": "5.1.1", "version": "5.1.1",
...@@ -1341,56 +1416,61 @@ ...@@ -1341,56 +1416,61 @@
} }
}, },
"node_modules/mongoose": { "node_modules/mongoose": {
"version": "7.0.4", "version": "6.5.0",
"resolved": "https://registry.npmjs.org/mongoose/-/mongoose-7.0.4.tgz", "resolved": "https://registry.npmjs.org/mongoose/-/mongoose-6.5.0.tgz",
"integrity": "sha512-MEmQOOqQUvW1PJcji64NtA2EFGHrEvk9o4g//isVYSJW2+8Y8u49C2qFBKzn1t6/l9onQn012o/PcFqR6ixQpQ==", "integrity": "sha512-swOX8ZEbmCeJaEs29B1j67StBIhuOccNNkipbVhsnLYYCDpNE7heM9W54MFGwN5es9tGGoxINHSzOhJ9kTOZGg==",
"dependencies": { "dependencies": {
"bson": "^5.0.1", "bson": "^4.6.5",
"kareem": "2.5.1", "kareem": "2.4.1",
"mongodb": "5.1.0", "mongodb": "4.8.1",
"mpath": "0.9.0", "mpath": "0.9.0",
"mquery": "5.0.0", "mquery": "4.0.3",
"ms": "2.1.3", "ms": "2.1.3",
"sift": "16.0.1" "sift": "16.0.0"
}, },
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=12.0.0"
}, },
"funding": { "funding": {
"type": "opencollective", "type": "opencollective",
"url": "https://opencollective.com/mongoose" "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": { "node_modules/mongoose/node_modules/mongodb": {
"version": "5.1.0", "version": "4.8.1",
"resolved": "https://registry.npmjs.org/mongodb/-/mongodb-5.1.0.tgz", "resolved": "https://registry.npmjs.org/mongodb/-/mongodb-4.8.1.tgz",
"integrity": "sha512-qgKb7y+EI90y4weY3z5+lIgm8wmexbonz0GalHkSElQXVKtRuwqXuhXKccyvIjXCJVy9qPV82zsinY0W1FBnJw==", "integrity": "sha512-/NyiM3Ox9AwP5zrfT9TXjRKDJbXlLaUDQ9Rg//2lbg8D2A8GXV0VidYYnA/gfdK6uwbnL4FnAflH7FbGw3TS7w==",
"dependencies": { "dependencies": {
"bson": "^5.0.1", "bson": "^4.6.5",
"mongodb-connection-string-url": "^2.6.0", "denque": "^2.0.1",
"socks": "^2.7.1" "mongodb-connection-string-url": "^2.5.2",
"socks": "^2.6.2"
}, },
"engines": { "engines": {
"node": ">=14.20.1" "node": ">=12.9.0"
}, },
"optionalDependencies": { "optionalDependencies": {
"saslprep": "^1.0.3" "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": { "node_modules/mongoose/node_modules/ms": {
...@@ -1407,14 +1487,14 @@ ...@@ -1407,14 +1487,14 @@
} }
}, },
"node_modules/mquery": { "node_modules/mquery": {
"version": "5.0.0", "version": "4.0.3",
"resolved": "https://registry.npmjs.org/mquery/-/mquery-5.0.0.tgz", "resolved": "https://registry.npmjs.org/mquery/-/mquery-4.0.3.tgz",
"integrity": "sha512-iQMncpmEK8R8ncT8HJGsGc9Dsp8xcgYMVSbs5jgnm1lFHTZqMJTUWTDx1LBO8+mK3tPNZWFLBghQEIOULSTHZg==", "integrity": "sha512-J5heI+P08I6VJ2Ky3+33IpCdAvlYGTSUjwTPxkAr8i8EoduPMBX2OY/wa3IKZIQl7MU4SbFk8ndgSKyB/cl1zA==",
"dependencies": { "dependencies": {
"debug": "4.x" "debug": "4.x"
}, },
"engines": { "engines": {
"node": ">=14.0.0" "node": ">=12.0.0"
} }
}, },
"node_modules/mquery/node_modules/debug": { "node_modules/mquery/node_modules/debug": {
...@@ -1818,9 +1898,9 @@ ...@@ -1818,9 +1898,9 @@
} }
}, },
"node_modules/sift": { "node_modules/sift": {
"version": "16.0.1", "version": "16.0.0",
"resolved": "https://registry.npmjs.org/sift/-/sift-16.0.1.tgz", "resolved": "https://registry.npmjs.org/sift/-/sift-16.0.0.tgz",
"integrity": "sha512-Wv6BjQ5zbhW7VFefWusVP33T/EM0vYikCaQ2qR8yULbsilAT8/wQaXvuQ3ptGLpoKx+lihJE3y2UTgKDyyNHZQ==" "integrity": "sha512-ILTjdP2Mv9V1kIxWMXeMTIRbOBrqKc4JAXmFMnFq3fKeyQ2Qwa3Dw1ubcye3vR+Y6ofA0b9gNDr/y2t6eUeIzQ=="
}, },
"node_modules/simple-update-notifier": { "node_modules/simple-update-notifier": {
"version": "1.1.0", "version": "1.1.0",
...@@ -1965,6 +2045,14 @@ ...@@ -1965,6 +2045,14 @@
"node": ">=12" "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": { "node_modules/type-is": {
"version": "1.6.18", "version": "1.6.18",
"resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz",
......
...@@ -16,7 +16,8 @@ ...@@ -16,7 +16,8 @@
"express": "^4.18.2", "express": "^4.18.2",
"fix-esm": "^1.0.1", "fix-esm": "^1.0.1",
"mongodb": "^5.3.0", "mongodb": "^5.3.0",
"mongoose": "^7.0.4", "mongoose": "^6.5.0",
"mongoose-id-validator": "^0.6.0",
"multer": "^1.4.5-lts.1", "multer": "^1.4.5-lts.1",
"nanoid": "^4.0.2" "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 = () => { ...@@ -38,8 +38,18 @@ const createRoutes = () => {
}); });
router.get('/', async (req, res) => { 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 { try {
res.send(await Product.find()); res.send(await Product.find(query).populate('category', "title"));
} catch (e) { } catch (e) {
res.sendStatus(500); res.sendStatus(500);
} }
...@@ -47,7 +57,7 @@ const createRoutes = () => { ...@@ -47,7 +57,7 @@ const createRoutes = () => {
router.get('/:id', async (req, res) => { router.get('/:id', async (req, res) => {
try { 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); if (result) return res.send(result);
......
...@@ -18,6 +18,7 @@ ...@@ -18,6 +18,7 @@
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"axios": "^1.3.5", "axios": "^1.3.5",
"prop-types": "^15.8.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
......
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
"@testing-library/react": "^13.4.0", "@testing-library/react": "^13.4.0",
"@testing-library/user-event": "^13.5.0", "@testing-library/user-event": "^13.5.0",
"axios": "^1.3.5", "axios": "^1.3.5",
"prop-types": "^15.8.1",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-redux": "^8.0.5", "react-redux": "^8.0.5",
......
import {Card, CardActions, CardContent, CardHeader, Grid, IconButton, CardMedia} from "@mui/material"; import {Card, CardActions, CardContent, CardHeader, Grid, IconButton, CardMedia} from "@mui/material";
import {Link} from "react-router-dom"; 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 ArrowForwardIosIcon from '@mui/icons-material/ArrowForwardIos';
import imageNotAvailable from '../../../assets/images/image_not_available.png'; import imageNotAvailable from '../../../../assets/images/image_not_available.png';
import {uploadUrl} from "../../../constants/config"; import {uploadUrl} from "../../../../constants/config";
import PropTypes from 'prop-types';
const ProductItem = ({product}) => { /**
const imagePath = product.image * @param {String} id
? uploadUrl + '/' + product.image * @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; : imageNotAvailable;
return ( return (
<Grid item xs={12} sm={12} md={6} lg={4}> <Grid item xs={12} sm={12} md={6} lg={4}>
<Card> <Card>
<CardHeader title={product.title}/> <CardHeader title={title}/>
<CardContent> <CardContent>
<CardMedia <CardMedia
image={imagePath} image={imagePath}
title={product.title} title={title}
sx={{maxWidth:200, height: 200}} sx={{maxWidth:200, height: 200}}
/> />
<strong style={{marginLeft: '10px'}}> <strong style={{marginLeft: '10px'}}>
Price: {product.price} KZT Price: {price} KZT
</strong> </strong>
</CardContent> </CardContent>
<CardActions> <CardActions>
<IconButton component={Link} to={PRODUCT_VIEW.replace(':id', product._id)}> <IconButton component={Link} to={PRODUCT_VIEW.replace(':id', id)}>
<ArrowForwardIosIcon/> <ArrowForwardIosIcon/>
</IconButton> </IconButton>
</CardActions> </CardActions>
...@@ -34,4 +43,11 @@ const ProductItem = ({product}) => { ...@@ -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; 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"; ...@@ -4,7 +4,7 @@ import {PRODUCT_ADD} from "../../constants/routes";
import {useDispatch, useSelector} from "react-redux"; import {useDispatch, useSelector} from "react-redux";
import {fetchProducts} from "../../store/actions/productsActions"; import {fetchProducts} from "../../store/actions/productsActions";
import {useEffect} from "react"; import {useEffect} from "react";
import ProductItem from "../../components/Product/ProductItem/ProductItem"; import ProductList from "../../components/Product/ProductList/ProductList";
const Products = () => { const Products = () => {
const dispatch = useDispatch(); const dispatch = useDispatch();
...@@ -34,22 +34,7 @@ const Products = () => { ...@@ -34,22 +34,7 @@ const Products = () => {
</Button> </Button>
</Grid> </Grid>
</Grid> </Grid>
<Grid <ProductList products={products} />
container
item
direction="row"
justifyContent="space-between"
alignItems="center"
>
{
products.map(product => (
<ProductItem
key={product._id}
product={product}
/>
))
}
</Grid>
</Grid> </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