feat: Реализован полный CRUD для админ-панели и улучшена функциональность

- Portfolio CRUD: добавление, редактирование, удаление, переключение публикации
- Services CRUD: полное управление услугами с возможностью активации/деактивации
- Banner system: новая модель Banner с CRUD операциями и аналитикой кликов
- Telegram integration: расширенные настройки бота, обнаружение чатов, отправка сообщений
- Media management: улучшенная загрузка файлов с оптимизацией изображений и превью
- UI improvements: обновлённые админ-панели с rich-text редактором и drag&drop загрузкой
- Database: добавлена таблица banners с полями для баннеров и аналитики
This commit is contained in:
2025-10-22 20:32:16 +09:00
parent 150891b29d
commit 9477ff6de0
69 changed files with 11451 additions and 2321 deletions

View File

@@ -1,7 +1,7 @@
const express = require('express');
const router = express.Router();
const Service = require('../models/Service');
const Portfolio = require('../models/Portfolio');
const { Service, Portfolio } = require('../models');
const { Op } = require('sequelize');
// Get all services
router.get('/', async (req, res) => {
@@ -9,19 +9,20 @@ router.get('/', async (req, res) => {
const category = req.query.category;
const featured = req.query.featured;
let query = { isActive: true };
let whereClause = { isActive: true };
if (category && category !== 'all') {
query.category = category;
whereClause.category = category;
}
if (featured === 'true') {
query.featured = true;
whereClause.featured = true;
}
const services = await Service.find(query)
.populate('portfolio', 'title images')
.sort({ featured: -1, order: 1 });
const services = await Service.findAll({
where: whereClause,
order: [['featured', 'DESC'], ['order', 'ASC']]
});
res.json({
success: true,
@@ -39,8 +40,7 @@ router.get('/', async (req, res) => {
// Get single service
router.get('/:id', async (req, res) => {
try {
const service = await Service.findById(req.params.id)
.populate('portfolio', 'title shortDescription images category');
const service = await Service.findByPk(req.params.id);
if (!service || !service.isActive) {
return res.status(404).json({
@@ -50,13 +50,14 @@ router.get('/:id', async (req, res) => {
}
// Get related services
const relatedServices = await Service.find({
_id: { $ne: service._id },
category: service.category,
isActive: true
})
.select('name shortDescription icon pricing')
.limit(3);
const relatedServices = await Service.findAll({
where: {
id: { [Op.ne]: service.id },
category: service.category,
isActive: true
},
limit: 3
});
res.json({
success: true,
@@ -107,21 +108,21 @@ router.get('/search/:term', async (req, res) => {
const searchTerm = req.params.term;
const limit = parseInt(req.query.limit) || 10;
const services = await Service.find({
$and: [
{ isActive: true },
{
$or: [
{ name: { $regex: searchTerm, $options: 'i' } },
{ description: { $regex: searchTerm, $options: 'i' } },
{ tags: { $in: [new RegExp(searchTerm, 'i')] } }
]
}
]
})
.select('name shortDescription icon pricing category')
.sort({ featured: -1, order: 1 })
.limit(limit);
const services = await Service.findAll({
where: {
[Op.and]: [
{ isActive: true },
{
[Op.or]: [
{ name: { [Op.iLike]: `%${searchTerm}%` } },
{ description: { [Op.iLike]: `%${searchTerm}%` } },
{ tags: { [Op.contains]: [searchTerm] } }
]
}
]
},
limit: limit
});
res.json({
success: true,