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:
@@ -1,6 +1,7 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const Portfolio = require('../models/Portfolio');
|
||||
const { Portfolio } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Get all portfolio items
|
||||
router.get('/', async (req, res) => {
|
||||
@@ -13,28 +14,37 @@ router.get('/', async (req, res) => {
|
||||
const featured = req.query.featured;
|
||||
|
||||
// Build query
|
||||
let query = { isPublished: true };
|
||||
let whereClause = { isPublished: true };
|
||||
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
whereClause.category = category;
|
||||
}
|
||||
|
||||
if (featured === 'true') {
|
||||
query.featured = true;
|
||||
whereClause.featured = true;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
query.$text = { $search: search };
|
||||
whereClause = {
|
||||
...whereClause,
|
||||
[Op.or]: [
|
||||
{ title: { [Op.iLike]: `%${search}%` } },
|
||||
{ description: { [Op.iLike]: `%${search}%` } },
|
||||
{ shortDescription: { [Op.iLike]: `%${search}%` } }
|
||||
]
|
||||
};
|
||||
}
|
||||
|
||||
// Get portfolio items
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.find(query)
|
||||
.sort({ featured: -1, publishedAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit)
|
||||
.select('title shortDescription category technologies images status publishedAt viewCount'),
|
||||
Portfolio.countDocuments(query)
|
||||
Portfolio.findAll({
|
||||
where: whereClause,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit,
|
||||
attributes: ['id', 'title', 'shortDescription', 'category', 'technologies', 'images', 'status', 'publishedAt', 'viewCount']
|
||||
}),
|
||||
Portfolio.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
@@ -63,7 +73,7 @@ router.get('/', async (req, res) => {
|
||||
// Get single portfolio item
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
@@ -77,13 +87,15 @@ router.get('/:id', async (req, res) => {
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.find({
|
||||
_id: { $ne: portfolio._id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
})
|
||||
.select('title shortDescription images')
|
||||
.limit(4);
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
attributes: ['id', 'title', 'shortDescription', 'images'],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
@@ -131,7 +143,7 @@ router.get('/meta/categories', async (req, res) => {
|
||||
// Like portfolio item
|
||||
router.post('/:id/like', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
@@ -162,21 +174,22 @@ router.get('/search/:term', async (req, res) => {
|
||||
const searchTerm = req.params.term;
|
||||
const limit = parseInt(req.query.limit) || 10;
|
||||
|
||||
const portfolio = await Portfolio.find({
|
||||
$and: [
|
||||
{ isPublished: true },
|
||||
{
|
||||
$or: [
|
||||
{ title: { $regex: searchTerm, $options: 'i' } },
|
||||
{ description: { $regex: searchTerm, $options: 'i' } },
|
||||
{ technologies: { $in: [new RegExp(searchTerm, 'i')] } }
|
||||
]
|
||||
}
|
||||
]
|
||||
})
|
||||
.select('title shortDescription category images')
|
||||
.sort({ featured: -1, publishedAt: -1 })
|
||||
.limit(limit);
|
||||
const portfolio = await Portfolio.findAll({
|
||||
where: {
|
||||
[Op.and]: [
|
||||
{ isPublished: true },
|
||||
{
|
||||
[Op.or]: [
|
||||
{ title: { [Op.iLike]: `%${searchTerm}%` } },
|
||||
{ description: { [Op.iLike]: `%${searchTerm}%` } },
|
||||
{ technologies: { [Op.contains]: [searchTerm] } }
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
attributes: ['id', 'title', 'shortDescription', 'images', 'category'],
|
||||
limit: limit
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
|
||||
Reference in New Issue
Block a user