Магазин на JavaScript, часть 9 из 19. Рейтинг товара, приложение клиента, структура проекта
13.12.2021
Теги: Backend • Express.js • Frontend • JavaScript • Node.js • ORM • React.js • Web-разработка • БазаДанных • ИнтернетМагазин • КаталогТоваров • Корзина • Фреймворк
Рейтинг товара
Теперь разберемся с рейтингами товаров. Голосовать за товары могут только зарегистрированные пользователи. Пользователь может проголосовать за товар только один раз. Рейтинг товара вычисляется как сумма оценок, поделенная на количество голосований. Как обычно, создаем новые маршруты, контроллер и модель. Методов у модели и контроллера будет всего два — проголосовать за товар и получить рейтинг товара.
Новый маршруты, файл routes/rating.js
:
import express from 'express' import RatingController from '../controllers/Rating.js' import authMiddleware from '../middleware/authMiddleware.js' const router = new express.Router() router.get('/product/:productId([0-9]+)', RatingController.getOne) router.post('/product/:productId([0-9]+)/rate/:rate([1-5])', authMiddleware, RatingController.create) export default router
import express from 'express' import product from './product.js' import category from './category.js' import brand from './brand.js' import user from './user.js' import basket from './basket.js' import rating from './rating.js' const router = new express.Router() router.use('/product', product) router.use('/category', category) router.use('/brand', brand) router.use('/user', user) router.use('/basket', basket) router.use('/rating', rating) export default router
Контроллер controllers/Rating.js
:
import RatingModel from '../models/Rating.js' import AppError from '../errors/AppError.js' class Rating { async getOne(req, res, next) { try { const rating = await RatingModel.getOne(req.params.productId) res.json(rating) } catch(e) { next(AppError.badRequest(e.message)) } } async create(req, res, next) { try { const {productId, rate} = req.params const rating = await RatingModel.create(req.auth.userId, productId, rate) res.json(rating) } catch(e) { next(AppError.badRequest(e.message)) } } } export default new Rating()
Модель models/Rating.js
:
import { Rating as RatingMapping } from './mapping.js' import { Product as ProductMapping } from './mapping.js' import { User as UserMapping } from './mapping.js' import AppError from '../errors/AppError.js' class Rating { async getOne(productId) { const product = await ProductMapping.findByPk(productId) if (!product) { throw new Error('Товар не найден в БД') } const votes = await RatingMapping.count({where: {productId}}) if (votes) { const rates = await RatingMapping.sum('rate', {where: {productId}}) return {rates, votes, rating: rates/votes} } return {rates: 0, votes: 0, rating: 0} } async create(userId, productId, rate) { const product = await ProductMapping.findByPk(productId) if (!product) { throw new Error('Товар не найден в БД') } const user = await UserMapping.findByPk(userId) if (!user) { throw new Error('Пользователь не найден в БД') } const rating = await RatingMapping.create({userId, productId, rate}) return rating } } export default new Rating()
Для проверки создадим файл test/rating.http
:
### Получить рейтинг одного товара GET /api/rating/product/1 HTTP/1.1 Host: localhost:7000 ### Проголосовать за один товар POST /api/rating/product/1/rate/5 HTTP/1.1 Host: localhost:7000
Поскольку голосовать могут только авторизованные пользователи, для отправки POST-запроса требуется отправить заголовок Authorization
. Чтобы не заморачиваться с этим заголовком, временно отключим middleware.
import express from 'express' import RatingController from '../controllers/Rating.js' import authMiddleware from '../middleware/authMiddleware.js' const router = new express.Router() router.get('/product/:productId([0-9]+)', RatingController.getOne) router.post('/product/:productId([0-9]+)/rate/:rate([1-5])', /* authMiddleware, */ RatingController.create) export default router
И в классе контроллера будем подставлять идентификаторы пользователей, от имени которых будем голосовать:
import RatingModel from '../models/Rating.js' import AppError from '../errors/AppError.js' class Rating { /* .......... */ async create(req, res, next) { try { const {productId, rate} = req.params // const rating = await RatingModel.create(req.auth.userId, productId, rate) const rating = await RatingModel.create(1, productId, rate) res.json(rating) } catch(e) { next(AppError.badRequest(e.message)) } } } export default new Rating()
Приложение клиента
С серверной частью в основном закончили, переходим к разработке клиента. Теперь вся работа будет в директории shop/client
, которую мы создали в самом начале.
> cd shop/client
Создаем react-приложение:
> npx create-react-app . Need to install the following packages: create-react-app Ok to proceed? (y) y You are running `create-react-app` 4.0.3, which is behind the latest release (5.0.0). We no longer support global installation of Create React App. Please remove any global installs with one of the following commands: - npm uninstall -g create-react-app - yarn global remove create-react-app The latest instructions for creating a new app can be found here: https://create-react-app.dev/docs/getting-started/
Вместо установки получаем ошибку. Рекомендация удалить глобальную установку create-react-app
мне не помогла — у меня этот пакет и не был установлен глобально. Прочие рекомендации, найденные в интернете — обновить npm
, почистить кэш — тоже не помогли. Наконец, вроде наткнулся на решение.
> npx create-react-app@latest .
В директории src
оставляем только index.js
и App.js
, в директории public
оставляем только index.html
и favicon.ico
. Устанавливаем зависимости:
> npm install axios react-router-dom mobx mobx-react-lite
Для оформления будем использовать модуль react-bootstrap
:
> npm install react-bootstrap bootstrap
Из файла public/index.html
уберем все лишнее:
<!DOCTYPE html> <html lang="en"> <head> <meta charset="utf-8" /> <link rel="icon" href="%PUBLIC_URL%/favicon.ico" /> <meta name="viewport" content="width=device-width, initial-scale=1" /> <meta name="description" content="Web site created using create-react-app" /> <title>React App</title> </head> <body> <noscript>You need to enable JavaScript to run this app.</noscript> <div id="root"></div> </body> </html>
Импортируем стили Bootstrap в компоненте src/App.js
:
import 'bootstrap/dist/css/bootstrap.min.css' function App() { return ( <div> SHOP </div> ); } export default App;
Подготовительные работы закончены, запускаем приложение:
> npm start
Структура приложения
Создадим несколько директорий в src
: store
— для работы с mobx
, pages
— для страниц типа «Контакты» и «Доставка», components
— для вспомогательных компонентов типа панели навигации.
Теперь в src/pages
создадим файлы Login.js
— страница авторизации, Signup.js
— страница регистрации, Shop.js
— витрина магазина со списком всех товаров, Product.js
— страница просмотра товара, Basket.js
— корзина покупателя, Contacts.js
— страница контактов, Delivery.js
— страница доставки, Admin.js
— страница администратора магазина.
const Login = () => { return <h1>Авторизация</h1> } export default Login
const Signup = () => { return <h1>Регистрация</h1> } export default Signup
const Shop = () => { return <h1>Витрина</h1> } export default Shop
const Product = () => { return <h1>Товар</h1> } export default Product
const Basket = () => { return <h1>Корзина</h1> } export default Basket
const Contacts = () => { return <h1>Контакты</h1> } export default Contacts
const Delivery = () => { return <h1>Доставка</h1> } export default Delivery
const Admin = () => { return <h1>Панель управления</h1> } export default Admin
- Магазин на JavaScript, часть 19 из 19. Редактирование характеристик и рефакторинг приложения
- Магазин на JavaScript, часть 18 из 19. Панель управления: редактирование категорий и брендов
- Магазин на JavaScript, часть 17 из 19. Панель управления: список заказов, категорий и брендов
- Магазин на JavaScript, часть 15 из 19. Работа с заказами на сервере, оформление заказа
- Магазин на JavaScript, часть 14 из 19. Кнопка «Назад», страница товара, корзина покупателя
- Магазин на JavaScript, часть 13 из 19. Хранилище каталога, компонент витрины, кнопка «Назад»
- Магазин на JavaScript, часть12 из 19. Запросы на сервер, состояние приложения, Signup и Login
Поиск: Backend • Express.js • Frontend • JavaScript • Node.js • ORM • React.js • Web-разработка • База данных • Интернет магазин • Каталог товаров • Корзина • Фреймворк