AdminLTE3

This commit is contained in:
2025-10-26 22:14:47 +09:00
parent 291fc63a4c
commit 9974811a3e
226 changed files with 88284 additions and 3406 deletions

View File

@@ -0,0 +1,496 @@
const express = require('express');
const router = express.Router();
const multer = require('multer');
const path = require('path');
const { Portfolio, Service, Contact, User } = require('../../models');
// Multer configuration for file uploads
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'public/uploads/');
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
}
});
const upload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('Only image files are allowed!'), false);
}
},
limits: {
fileSize: 10 * 1024 * 1024 // 10MB
}
});
// Authentication middleware
const requireAuth = (req, res, next) => {
if (!req.session.user) {
return res.status(401).json({ success: false, message: 'Authentication required' });
}
next();
};
// Portfolio API Routes
router.post('/portfolio', requireAuth, upload.array('images', 10), async (req, res) => {
try {
const {
title,
shortDescription,
description,
category,
clientName,
projectUrl,
githubUrl,
technologies,
featured,
isPublished
} = req.body;
// Process uploaded images
const images = req.files ? req.files.map((file, index) => ({
url: `/uploads/${file.filename}`,
alt: `${title} image ${index + 1}`,
isPrimary: index === 0
})) : [];
// Parse technologies
let techArray = [];
if (technologies) {
try {
techArray = JSON.parse(technologies);
} catch (e) {
techArray = technologies.split(',').map(t => t.trim());
}
}
const portfolio = await Portfolio.create({
title,
shortDescription,
description,
category,
clientName,
projectUrl: projectUrl || null,
githubUrl: githubUrl || null,
technologies: techArray,
images,
featured: featured === 'on',
isPublished: isPublished === 'on',
status: 'completed',
publishedAt: isPublished === 'on' ? new Date() : null
});
res.json({ success: true, portfolio });
} catch (error) {
console.error('Portfolio creation error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
router.patch('/portfolio/:id', requireAuth, upload.array('images', 10), async (req, res) => {
try {
const portfolio = await Portfolio.findByPk(req.params.id);
if (!portfolio) {
return res.status(404).json({ success: false, message: 'Portfolio not found' });
}
const updates = { ...req.body };
// Handle checkboxes
updates.featured = updates.featured === 'on';
updates.isPublished = updates.isPublished === 'on';
// Process technologies
if (updates.technologies) {
try {
updates.technologies = JSON.parse(updates.technologies);
} catch (e) {
updates.technologies = updates.technologies.split(',').map(t => t.trim());
}
}
// Process new images
if (req.files && req.files.length > 0) {
const newImages = req.files.map((file, index) => ({
url: `/uploads/${file.filename}`,
alt: `${updates.title || portfolio.title} image ${index + 1}`,
isPrimary: index === 0 && (!portfolio.images || portfolio.images.length === 0)
}));
updates.images = [...(portfolio.images || []), ...newImages];
}
await portfolio.update(updates);
res.json({ success: true, portfolio });
} catch (error) {
console.error('Portfolio update error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
router.delete('/portfolio/:id', requireAuth, async (req, res) => {
try {
const portfolio = await Portfolio.findByPk(req.params.id);
if (!portfolio) {
return res.status(404).json({ success: false, message: 'Portfolio not found' });
}
await portfolio.destroy();
res.json({ success: true });
} catch (error) {
console.error('Portfolio deletion error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
// Services API Routes
router.post('/services', requireAuth, async (req, res) => {
try {
const {
name,
description,
shortDescription,
icon,
category,
features,
pricing,
estimatedTime,
isActive,
featured,
tags
} = req.body;
// Parse arrays
let featuresArray = [];
let tagsArray = [];
let pricingObj = {};
if (features) {
try {
featuresArray = JSON.parse(features);
} catch (e) {
featuresArray = features.split(',').map(f => f.trim());
}
}
if (tags) {
try {
tagsArray = JSON.parse(tags);
} catch (e) {
tagsArray = tags.split(',').map(t => t.trim());
}
}
if (pricing) {
try {
pricingObj = JSON.parse(pricing);
} catch (e) {
pricingObj = { basePrice: pricing };
}
}
const service = await Service.create({
name,
description,
shortDescription,
icon,
category,
features: featuresArray,
pricing: pricingObj,
estimatedTime,
isActive: isActive === 'on',
featured: featured === 'on',
tags: tagsArray
});
res.json({ success: true, service });
} catch (error) {
console.error('Service creation error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
router.put('/services/:id', requireAuth, async (req, res) => {
try {
const service = await Service.findByPk(req.params.id);
if (!service) {
return res.status(404).json({ success: false, message: 'Service not found' });
}
const {
name,
description,
shortDescription,
icon,
category,
features,
pricing,
estimatedTime,
isActive,
featured,
tags,
order,
seo
} = req.body;
// Parse arrays and objects
let featuresArray = [];
let tagsArray = [];
let pricingObj = {};
let seoObj = {};
if (features) {
if (Array.isArray(features)) {
featuresArray = features;
} else {
try {
featuresArray = JSON.parse(features);
} catch (e) {
featuresArray = features.split(',').map(f => f.trim());
}
}
}
if (tags) {
if (Array.isArray(tags)) {
tagsArray = tags;
} else {
try {
tagsArray = JSON.parse(tags);
} catch (e) {
tagsArray = tags.split(',').map(t => t.trim());
}
}
}
if (pricing) {
if (typeof pricing === 'object') {
pricingObj = pricing;
} else {
try {
pricingObj = JSON.parse(pricing);
} catch (e) {
pricingObj = { basePrice: pricing };
}
}
}
if (seo) {
if (typeof seo === 'object') {
seoObj = seo;
} else {
try {
seoObj = JSON.parse(seo);
} catch (e) {
seoObj = {};
}
}
}
const updateData = {
name,
description,
shortDescription,
icon,
category,
features: featuresArray,
pricing: pricingObj,
estimatedTime,
isActive: Boolean(isActive),
featured: Boolean(featured),
tags: tagsArray,
order: order ? parseInt(order) : 0,
seo: seoObj
};
// Remove undefined values
Object.keys(updateData).forEach(key => {
if (updateData[key] === undefined) {
delete updateData[key];
}
});
await service.update(updateData);
res.json({ success: true, service });
} catch (error) {
console.error('Service update error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
router.delete('/services/:id', requireAuth, async (req, res) => {
try {
const service = await Service.findByPk(req.params.id);
if (!service) {
return res.status(404).json({ success: false, message: 'Service not found' });
}
await service.destroy();
res.json({ success: true });
} catch (error) {
console.error('Service deletion error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
// Contacts API Routes
router.patch('/contacts/:id', requireAuth, async (req, res) => {
try {
const contact = await Contact.findByPk(req.params.id);
if (!contact) {
return res.status(404).json({ success: false, message: 'Contact not found' });
}
await contact.update(req.body);
res.json({ success: true, contact });
} catch (error) {
console.error('Contact update error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
router.delete('/contacts/:id', requireAuth, async (req, res) => {
try {
const contact = await Contact.findByPk(req.params.id);
if (!contact) {
return res.status(404).json({ success: false, message: 'Contact not found' });
}
await contact.destroy();
res.json({ success: true });
} catch (error) {
console.error('Contact deletion error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
// Telegram notification for contact
router.post('/contacts/:id/telegram', requireAuth, async (req, res) => {
try {
const contact = await Contact.findByPk(req.params.id);
if (!contact) {
return res.status(404).json({ success: false, message: 'Contact not found' });
}
// Send Telegram notification
const telegramService = require('../../services/telegram');
const result = await telegramService.sendContactNotification(contact);
if (result.success) {
res.json({ success: true });
} else {
res.status(500).json({ success: false, message: result.message || result.error });
}
} catch (error) {
console.error('Telegram notification error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
// Test Telegram connection
router.post('/telegram/test', requireAuth, async (req, res) => {
try {
const { botToken, chatId } = req.body;
// Temporarily set up telegram service with provided credentials
const axios = require('axios');
// Test bot info
const botResponse = await axios.get(`https://api.telegram.org/bot${botToken}/getMe`);
// Test sending a message
const testMessage = '✅ Telegram bot подключен успешно!\n\nЭто тестовое сообщение от SmartSolTech Admin Panel.';
await axios.post(`https://api.telegram.org/bot${botToken}/sendMessage`, {
chat_id: chatId,
text: testMessage,
parse_mode: 'Markdown'
});
res.json({
success: true,
bot: botResponse.data.result,
message: 'Test message sent successfully'
});
} catch (error) {
console.error('Telegram test error:', error);
let message = 'Connection failed';
if (error.response?.data?.description) {
message = error.response.data.description;
} else if (error.message) {
message = error.message;
}
res.status(400).json({ success: false, message });
}
});
// Settings API
const { SiteSettings } = require('../../models');
router.get('/settings', requireAuth, async (req, res) => {
try {
const settings = await SiteSettings.findOne() || {};
res.json({ success: true, settings });
} catch (error) {
console.error('Settings fetch error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
router.post('/settings', requireAuth, upload.fields([
{ name: 'logo', maxCount: 1 },
{ name: 'favicon', maxCount: 1 }
]), async (req, res) => {
try {
let settings = await SiteSettings.findOne();
if (!settings) {
settings = await SiteSettings.create({});
}
const updates = {};
// Handle nested objects
Object.keys(req.body).forEach(key => {
if (key.includes('.')) {
const [parent, child] = key.split('.');
if (!updates[parent]) updates[parent] = {};
updates[parent][child] = req.body[key];
} else {
updates[key] = req.body[key];
}
});
// Handle file uploads
if (req.files.logo) {
updates.logo = `/uploads/${req.files.logo[0].filename}`;
}
if (req.files.favicon) {
updates.favicon = `/uploads/${req.files.favicon[0].filename}`;
}
// Update existing settings with new values
Object.keys(updates).forEach(key => {
if (typeof updates[key] === 'object' && updates[key] !== null) {
settings[key] = { ...settings[key], ...updates[key] };
} else {
settings[key] = updates[key];
}
});
await settings.save();
res.json({ success: true, settings });
} catch (error) {
console.error('Settings update error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
module.exports = router;

View File

@@ -0,0 +1,496 @@
const express = require('express');
const router = express.Router();
const multer = require('multer');
const path = require('path');
const { Portfolio, Service, Contact, User } = require('../../models');
// Multer configuration for file uploads
const storage = multer.diskStorage({
destination: (req, file, cb) => {
cb(null, 'public/uploads/');
},
filename: (req, file, cb) => {
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
cb(null, file.fieldname + '-' + uniqueSuffix + path.extname(file.originalname));
}
});
const upload = multer({
storage: storage,
fileFilter: (req, file, cb) => {
if (file.mimetype.startsWith('image/')) {
cb(null, true);
} else {
cb(new Error('Only image files are allowed!'), false);
}
},
limits: {
fileSize: 10 * 1024 * 1024 // 10MB
}
});
// Authentication middleware
const requireAuth = (req, res, next) => {
if (!req.session.user) {
return res.status(401).json({ success: false, message: 'Authentication required' });
}
next();
};
// Portfolio API Routes
router.post('/portfolio', requireAuth, upload.array('images', 10), async (req, res) => {
try {
const {
title,
shortDescription,
description,
category,
clientName,
projectUrl,
githubUrl,
technologies,
featured,
isPublished
} = req.body;
// Process uploaded images
const images = req.files ? req.files.map((file, index) => ({
url: `/uploads/${file.filename}`,
alt: `${title} image ${index + 1}`,
isPrimary: index === 0
})) : [];
// Parse technologies
let techArray = [];
if (technologies) {
try {
techArray = JSON.parse(technologies);
} catch (e) {
techArray = technologies.split(',').map(t => t.trim());
}
}
const portfolio = await Portfolio.create({
title,
shortDescription,
description,
category,
clientName,
projectUrl: projectUrl || null,
githubUrl: githubUrl || null,
technologies: techArray,
images,
featured: featured === 'on',
isPublished: isPublished === 'on',
status: 'completed',
publishedAt: isPublished === 'on' ? new Date() : null
});
res.json({ success: true, portfolio });
} catch (error) {
console.error('Portfolio creation error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
router.patch('/portfolio/:id', requireAuth, upload.array('images', 10), async (req, res) => {
try {
const portfolio = await Portfolio.findByPk(req.params.id);
if (!portfolio) {
return res.status(404).json({ success: false, message: 'Portfolio not found' });
}
const updates = { ...req.body };
// Handle checkboxes
updates.featured = updates.featured === 'on';
updates.isPublished = updates.isPublished === 'on';
// Process technologies
if (updates.technologies) {
try {
updates.technologies = JSON.parse(updates.technologies);
} catch (e) {
updates.technologies = updates.technologies.split(',').map(t => t.trim());
}
}
// Process new images
if (req.files && req.files.length > 0) {
const newImages = req.files.map((file, index) => ({
url: `/uploads/${file.filename}`,
alt: `${updates.title || portfolio.title} image ${index + 1}`,
isPrimary: index === 0 && (!portfolio.images || portfolio.images.length === 0)
}));
updates.images = [...(portfolio.images || []), ...newImages];
}
await portfolio.update(updates);
res.json({ success: true, portfolio });
} catch (error) {
console.error('Portfolio update error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
router.delete('/portfolio/:id', requireAuth, async (req, res) => {
try {
const portfolio = await Portfolio.findByPk(req.params.id);
if (!portfolio) {
return res.status(404).json({ success: false, message: 'Portfolio not found' });
}
await portfolio.destroy();
res.json({ success: true });
} catch (error) {
console.error('Portfolio deletion error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
// Services API Routes
router.post('/services', requireAuth, async (req, res) => {
try {
const {
name,
description,
shortDescription,
icon,
category,
features,
pricing,
estimatedTime,
isActive,
featured,
tags
} = req.body;
// Parse arrays
let featuresArray = [];
let tagsArray = [];
let pricingObj = {};
if (features) {
try {
featuresArray = JSON.parse(features);
} catch (e) {
featuresArray = features.split(',').map(f => f.trim());
}
}
if (tags) {
try {
tagsArray = JSON.parse(tags);
} catch (e) {
tagsArray = tags.split(',').map(t => t.trim());
}
}
if (pricing) {
try {
pricingObj = JSON.parse(pricing);
} catch (e) {
pricingObj = { basePrice: pricing };
}
}
const service = await Service.create({
name,
description,
shortDescription,
icon,
category,
features: featuresArray,
pricing: pricingObj,
estimatedTime,
isActive: isActive === 'on',
featured: featured === 'on',
tags: tagsArray
});
res.json({ success: true, service });
} catch (error) {
console.error('Service creation error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
router.put('/services/:id', requireAuth, async (req, res) => {
try {
const service = await Service.findByPk(req.params.id);
if (!service) {
return res.status(404).json({ success: false, message: 'Service not found' });
}
const {
name,
description,
shortDescription,
icon,
category,
features,
pricing,
estimatedTime,
isActive,
featured,
tags,
order,
seo
} = req.body;
// Parse arrays and objects
let featuresArray = [];
let tagsArray = [];
let pricingObj = {};
let seoObj = {};
if (features) {
if (Array.isArray(features)) {
featuresArray = features;
} else {
try {
featuresArray = JSON.parse(features);
} catch (e) {
featuresArray = features.split(',').map(f => f.trim());
}
}
}
if (tags) {
if (Array.isArray(tags)) {
tagsArray = tags;
} else {
try {
tagsArray = JSON.parse(tags);
} catch (e) {
tagsArray = tags.split(',').map(t => t.trim());
}
}
}
if (pricing) {
if (typeof pricing === 'object') {
pricingObj = pricing;
} else {
try {
pricingObj = JSON.parse(pricing);
} catch (e) {
pricingObj = { basePrice: pricing };
}
}
}
if (seo) {
if (typeof seo === 'object') {
seoObj = seo;
} else {
try {
seoObj = JSON.parse(seo);
} catch (e) {
seoObj = {};
}
}
}
const updateData = {
name,
description,
shortDescription,
icon,
category,
features: featuresArray,
pricing: pricingObj,
estimatedTime,
isActive: Boolean(isActive),
featured: Boolean(featured),
tags: tagsArray,
order: order ? parseInt(order) : 0,
seo: seoObj
};
// Remove undefined values
Object.keys(updateData).forEach(key => {
if (updateData[key] === undefined) {
delete updateData[key];
}
});
await service.update(updateData);
res.json({ success: true, service });
} catch (error) {
console.error('Service update error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
router.delete('/services/:id', requireAuth, async (req, res) => {
try {
const service = await Service.findByPk(req.params.id);
if (!service) {
return res.status(404).json({ success: false, message: 'Service not found' });
}
await service.destroy();
res.json({ success: true });
} catch (error) {
console.error('Service deletion error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
// Contacts API Routes
router.patch('/contacts/:id', requireAuth, async (req, res) => {
try {
const contact = await Contact.findByPk(req.params.id);
if (!contact) {
return res.status(404).json({ success: false, message: 'Contact not found' });
}
await contact.update(req.body);
res.json({ success: true, contact });
} catch (error) {
console.error('Contact update error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
router.delete('/contacts/:id', requireAuth, async (req, res) => {
try {
const contact = await Contact.findByPk(req.params.id);
if (!contact) {
return res.status(404).json({ success: false, message: 'Contact not found' });
}
await contact.destroy();
res.json({ success: true });
} catch (error) {
console.error('Contact deletion error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
// Telegram notification for contact
router.post('/contacts/:id/telegram', requireAuth, async (req, res) => {
try {
const contact = await Contact.findByPk(req.params.id);
if (!contact) {
return res.status(404).json({ success: false, message: 'Contact not found' });
}
// Send Telegram notification
const telegramService = require('../../services/telegram');
const result = await telegramService.sendContactNotification(contact);
if (result.success) {
res.json({ success: true });
} else {
res.status(500).json({ success: false, message: result.message || result.error });
}
} catch (error) {
console.error('Telegram notification error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
// Test Telegram connection
router.post('/telegram/test', requireAuth, async (req, res) => {
try {
const { botToken, chatId } = req.body;
// Temporarily set up telegram service with provided credentials
const axios = require('axios');
// Test bot info
const botResponse = await axios.get(`https://api.telegram.org/bot${botToken}/getMe`);
// Test sending a message
const testMessage = '✅ Telegram bot подключен успешно!\n\nЭто тестовое сообщение от SmartSolTech Admin Panel.';
await axios.post(`https://api.telegram.org/bot${botToken}/sendMessage`, {
chat_id: chatId,
text: testMessage,
parse_mode: 'Markdown'
});
res.json({
success: true,
bot: botResponse.data.result,
message: 'Test message sent successfully'
});
} catch (error) {
console.error('Telegram test error:', error);
let message = 'Connection failed';
if (error.response?.data?.description) {
message = error.response.data.description;
} else if (error.message) {
message = error.message;
}
res.status(400).json({ success: false, message });
}
});
// Settings API
const { SiteSettings } = require('../../models');
router.get('/settings', requireAuth, async (req, res) => {
try {
const settings = await SiteSettings.findOne() || {};
res.json({ success: true, settings });
} catch (error) {
console.error('Settings fetch error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
router.post('/settings', requireAuth, upload.fields([
{ name: 'logo', maxCount: 1 },
{ name: 'favicon', maxCount: 1 }
]), async (req, res) => {
try {
let settings = await SiteSettings.findOne();
if (!settings) {
settings = await SiteSettings.create({});
}
const updates = {};
// Handle nested objects
Object.keys(req.body).forEach(key => {
if (key.includes('.')) {
const [parent, child] = key.split('.');
if (!updates[parent]) updates[parent] = {};
updates[parent][child] = req.body[key];
} else {
updates[key] = req.body[key];
}
});
// Handle file uploads
if (req.files.logo) {
updates.logo = `/uploads/${req.files.logo[0].filename}`;
}
if (req.files.favicon) {
updates.favicon = `/uploads/${req.files.favicon[0].filename}`;
}
// Update existing settings with new values
Object.keys(updates).forEach(key => {
if (typeof updates[key] === 'object' && updates[key] !== null) {
settings[key] = { ...settings[key], ...updates[key] };
} else {
settings[key] = updates[key];
}
});
await settings.save();
res.json({ success: true, settings });
} catch (error) {
console.error('Settings update error:', error);
res.status(500).json({ success: false, message: error.message });
}
});
module.exports = router;