feat: профиль — просмотр, смена имени, email и пароля
Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -0,0 +1,163 @@
|
||||
const express = require('express');
|
||||
const bcrypt = require('bcryptjs');
|
||||
const { query, formatPrice } = require('../db');
|
||||
const { getCart, cartCount } = require('../cart');
|
||||
const { requireAuth } = require('../middleware/auth');
|
||||
const { ROLE_LABELS } = require('../constants/roles');
|
||||
const { asyncHandler } = require('../utils/asyncHandler');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
router.use((req, res, next) => {
|
||||
const cart = getCart(req);
|
||||
res.locals.cartCount = cartCount(cart);
|
||||
res.locals.formatPrice = formatPrice;
|
||||
next();
|
||||
});
|
||||
|
||||
async function loadAccountUser(userId) {
|
||||
const { rows } = await query(
|
||||
'SELECT id, email, name, role, created_at FROM users WHERE id = $1',
|
||||
[userId]
|
||||
);
|
||||
return rows[0];
|
||||
}
|
||||
|
||||
async function verifyPassword(userId, password) {
|
||||
const { rows } = await query('SELECT password_hash FROM users WHERE id = $1', [
|
||||
userId,
|
||||
]);
|
||||
if (!rows[0]) return false;
|
||||
return bcrypt.compareSync(password || '', rows[0].password_hash);
|
||||
}
|
||||
|
||||
function accountRender(res, options) {
|
||||
const { user, orderCount, error, success, activeTab } = options;
|
||||
res.render('account/index', {
|
||||
title: 'Личный кабинет',
|
||||
user,
|
||||
orderCount,
|
||||
roleLabels: ROLE_LABELS,
|
||||
error: error || null,
|
||||
success: success || null,
|
||||
activeTab: activeTab || 'profile',
|
||||
});
|
||||
}
|
||||
|
||||
router.get(
|
||||
'/',
|
||||
requireAuth,
|
||||
asyncHandler(async (req, res) => {
|
||||
const user = await loadAccountUser(req.session.userId);
|
||||
const countResult = await query(
|
||||
'SELECT COUNT(*)::int AS n FROM orders WHERE user_id = $1',
|
||||
[user.id]
|
||||
);
|
||||
accountRender(res, {
|
||||
user,
|
||||
orderCount: countResult.rows[0].n,
|
||||
success: req.query.success ? decodeURIComponent(String(req.query.success)) : null,
|
||||
error: req.query.error ? decodeURIComponent(String(req.query.error)) : null,
|
||||
activeTab: req.query.tab || 'profile',
|
||||
});
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/profile',
|
||||
requireAuth,
|
||||
asyncHandler(async (req, res) => {
|
||||
const name = (req.body.name || '').trim();
|
||||
if (!name) {
|
||||
return res.redirect('/account?tab=profile&error=' + encodeURIComponent('Укажите имя'));
|
||||
}
|
||||
await query('UPDATE users SET name = $1 WHERE id = $2', [name, req.session.userId]);
|
||||
res.redirect('/account?tab=profile&success=' + encodeURIComponent('Имя обновлено'));
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/email',
|
||||
requireAuth,
|
||||
asyncHandler(async (req, res) => {
|
||||
const newEmail = (req.body.email || '').trim().toLowerCase();
|
||||
const { current_password } = req.body;
|
||||
|
||||
if (!newEmail) {
|
||||
return res.redirect(
|
||||
'/account?tab=email&error=' + encodeURIComponent('Укажите новый email')
|
||||
);
|
||||
}
|
||||
|
||||
const emailRe = /^[^\s@]+@[^\s@]+\.[^\s@]+$/;
|
||||
if (!emailRe.test(newEmail)) {
|
||||
return res.redirect(
|
||||
'/account?tab=email&error=' + encodeURIComponent('Некорректный email')
|
||||
);
|
||||
}
|
||||
|
||||
const user = await loadAccountUser(req.session.userId);
|
||||
if (newEmail === user.email) {
|
||||
return res.redirect(
|
||||
'/account?tab=email&error=' + encodeURIComponent('Это уже ваш текущий email')
|
||||
);
|
||||
}
|
||||
|
||||
if (!(await verifyPassword(req.session.userId, current_password))) {
|
||||
return res.redirect(
|
||||
'/account?tab=email&error=' + encodeURIComponent('Неверный текущий пароль')
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
await query('UPDATE users SET email = $1 WHERE id = $2', [
|
||||
newEmail,
|
||||
req.session.userId,
|
||||
]);
|
||||
res.redirect('/account?tab=email&success=' + encodeURIComponent('Email изменён'));
|
||||
} catch (err) {
|
||||
if (err.code === '23505') {
|
||||
return res.redirect(
|
||||
'/account?tab=email&error=' + encodeURIComponent('Этот email уже занят')
|
||||
);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
router.post(
|
||||
'/password',
|
||||
requireAuth,
|
||||
asyncHandler(async (req, res) => {
|
||||
const { current_password, password, password2 } = req.body;
|
||||
|
||||
if (!(await verifyPassword(req.session.userId, current_password))) {
|
||||
return res.redirect(
|
||||
'/account?tab=password&error=' + encodeURIComponent('Неверный текущий пароль')
|
||||
);
|
||||
}
|
||||
|
||||
if (!password || password.length < 6) {
|
||||
return res.redirect(
|
||||
'/account?tab=password&error=' +
|
||||
encodeURIComponent('Новый пароль не менее 6 символов')
|
||||
);
|
||||
}
|
||||
|
||||
if (password !== password2) {
|
||||
return res.redirect(
|
||||
'/account?tab=password&error=' + encodeURIComponent('Пароли не совпадают')
|
||||
);
|
||||
}
|
||||
|
||||
const hash = bcrypt.hashSync(password, 10);
|
||||
await query('UPDATE users SET password_hash = $1 WHERE id = $2', [
|
||||
hash,
|
||||
req.session.userId,
|
||||
]);
|
||||
res.redirect('/account?tab=password&success=' + encodeURIComponent('Пароль изменён'));
|
||||
})
|
||||
);
|
||||
|
||||
module.exports = router;
|
||||
Reference in New Issue
Block a user