some fixes
This commit is contained in:
383
.history/routes/admin_20251019202120.js
Normal file
383
.history/routes/admin_20251019202120.js
Normal file
@@ -0,0 +1,383 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({ email, isActive: true });
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.countDocuments({ isPublished: true }),
|
||||
Service.countDocuments({ isActive: true }),
|
||||
Contact.countDocuments(),
|
||||
Contact.find().sort({ createdAt: -1 }).limit(5),
|
||||
Portfolio.find({ isPublished: true }).sort({ createdAt: -1 }).limit(5)
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.countDocuments({ isRead: false })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Portfolio.countDocuments()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Service.countDocuments()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findById(req.params.id)
|
||||
.populate('portfolio', 'title');
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.find({ isPublished: true })
|
||||
.select('title category');
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let query = {};
|
||||
if (status && status !== 'all') {
|
||||
query.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.find(query)
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Contact.countDocuments(query)
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findById(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || new SiteSettings();
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
388
.history/routes/admin_20251019202126.js
Normal file
388
.history/routes/admin_20251019202126.js
Normal file
@@ -0,0 +1,388 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.countDocuments({ isPublished: true }),
|
||||
Service.countDocuments({ isActive: true }),
|
||||
Contact.countDocuments(),
|
||||
Contact.find().sort({ createdAt: -1 }).limit(5),
|
||||
Portfolio.find({ isPublished: true }).sort({ createdAt: -1 }).limit(5)
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.countDocuments({ isRead: false })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Portfolio.countDocuments()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Service.countDocuments()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findById(req.params.id)
|
||||
.populate('portfolio', 'title');
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.find({ isPublished: true })
|
||||
.select('title category');
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let query = {};
|
||||
if (status && status !== 'all') {
|
||||
query.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.find(query)
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Contact.countDocuments(query)
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findById(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || new SiteSettings();
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
388
.history/routes/admin_20251019202132.js
Normal file
388
.history/routes/admin_20251019202132.js
Normal file
@@ -0,0 +1,388 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.countDocuments({ isPublished: true }),
|
||||
Service.countDocuments({ isActive: true }),
|
||||
Contact.countDocuments(),
|
||||
Contact.find().sort({ createdAt: -1 }).limit(5),
|
||||
Portfolio.find({ isPublished: true }).sort({ createdAt: -1 }).limit(5)
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.countDocuments({ isRead: false })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Portfolio.countDocuments()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Service.countDocuments()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findById(req.params.id)
|
||||
.populate('portfolio', 'title');
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.find({ isPublished: true })
|
||||
.select('title category');
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let query = {};
|
||||
if (status && status !== 'all') {
|
||||
query.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.find(query)
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Contact.countDocuments(query)
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findById(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || new SiteSettings();
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
395
.history/routes/admin_20251019202141.js
Normal file
395
.history/routes/admin_20251019202141.js
Normal file
@@ -0,0 +1,395 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.countDocuments({ isRead: false })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Portfolio.countDocuments()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Service.countDocuments()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findById(req.params.id)
|
||||
.populate('portfolio', 'title');
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.find({ isPublished: true })
|
||||
.select('title category');
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let query = {};
|
||||
if (status && status !== 'all') {
|
||||
query.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.find(query)
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Contact.countDocuments(query)
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findById(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || new SiteSettings();
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
395
.history/routes/admin_20251019202147.js
Normal file
395
.history/routes/admin_20251019202147.js
Normal file
@@ -0,0 +1,395 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.count({ where: { isRead: false } })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Portfolio.countDocuments()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Service.countDocuments()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findById(req.params.id)
|
||||
.populate('portfolio', 'title');
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.find({ isPublished: true })
|
||||
.select('title category');
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let query = {};
|
||||
if (status && status !== 'all') {
|
||||
query.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.find(query)
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Contact.countDocuments(query)
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findById(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || new SiteSettings();
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
395
.history/routes/admin_20251019202631.js
Normal file
395
.history/routes/admin_20251019202631.js
Normal file
@@ -0,0 +1,395 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.count({ where: { isRead: false } })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Portfolio.countDocuments()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Service.countDocuments()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findById(req.params.id)
|
||||
.populate('portfolio', 'title');
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.find({ isPublished: true })
|
||||
.select('title category');
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let query = {};
|
||||
if (status && status !== 'all') {
|
||||
query.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.find(query)
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Contact.countDocuments(query)
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findById(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || new SiteSettings();
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
396
.history/routes/admin_20251019204203.js
Normal file
396
.history/routes/admin_20251019204203.js
Normal file
@@ -0,0 +1,396 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.count({ where: { isRead: false } })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Service.countDocuments()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findById(req.params.id)
|
||||
.populate('portfolio', 'title');
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.find({ isPublished: true })
|
||||
.select('title category');
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let query = {};
|
||||
if (status && status !== 'all') {
|
||||
query.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.find(query)
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Contact.countDocuments(query)
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findById(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || new SiteSettings();
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
396
.history/routes/admin_20251019204212.js
Normal file
396
.history/routes/admin_20251019204212.js
Normal file
@@ -0,0 +1,396 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.count({ where: { isRead: false } })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.find()
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Service.countDocuments()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findById(req.params.id)
|
||||
.populate('portfolio', 'title');
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.find({ isPublished: true })
|
||||
.select('title category');
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let query = {};
|
||||
if (status && status !== 'all') {
|
||||
query.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.find(query)
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Contact.countDocuments(query)
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findById(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || new SiteSettings();
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
397
.history/routes/admin_20251019204224.js
Normal file
397
.history/routes/admin_20251019204224.js
Normal file
@@ -0,0 +1,397 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.count({ where: { isRead: false } })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findById(req.params.id)
|
||||
.populate('portfolio', 'title');
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.find({ isPublished: true })
|
||||
.select('title category');
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let query = {};
|
||||
if (status && status !== 'all') {
|
||||
query.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.find(query)
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Contact.countDocuments(query)
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findById(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || new SiteSettings();
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
396
.history/routes/admin_20251019204235.js
Normal file
396
.history/routes/admin_20251019204235.js
Normal file
@@ -0,0 +1,396 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.count({ where: { isRead: false } })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.find({ isPublished: true })
|
||||
.select('title category');
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let query = {};
|
||||
if (status && status !== 'all') {
|
||||
query.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.find(query)
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Contact.countDocuments(query)
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findById(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || new SiteSettings();
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
398
.history/routes/admin_20251019204245.js
Normal file
398
.history/routes/admin_20251019204245.js
Normal file
@@ -0,0 +1,398 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.count({ where: { isRead: false } })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let query = {};
|
||||
if (status && status !== 'all') {
|
||||
query.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.find(query)
|
||||
.sort({ createdAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Contact.countDocuments(query)
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findById(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || new SiteSettings();
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
400
.history/routes/admin_20251019204257.js
Normal file
400
.history/routes/admin_20251019204257.js
Normal file
@@ -0,0 +1,400 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.count({ where: { isRead: false } })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findById(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || new SiteSettings();
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
400
.history/routes/admin_20251019204307.js
Normal file
400
.history/routes/admin_20251019204307.js
Normal file
@@ -0,0 +1,400 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.count({ where: { isRead: false } })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || new SiteSettings();
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
400
.history/routes/admin_20251019204323.js
Normal file
400
.history/routes/admin_20251019204323.js
Normal file
@@ -0,0 +1,400 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.count({ where: { isRead: false } })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
400
.history/routes/admin_20251019204805.js
Normal file
400
.history/routes/admin_20251019204805.js
Normal file
@@ -0,0 +1,400 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.count({ where: { isRead: false } })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
399
.history/routes/admin_20251020044258.js
Normal file
399
.history/routes/admin_20251020044258.js
Normal file
@@ -0,0 +1,399 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'Invalid email or password'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.count({ where: { isRead: false } })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
398
.history/routes/admin_20251020044304.js
Normal file
398
.history/routes/admin_20251020044304.js
Normal file
@@ -0,0 +1,398 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: req.t('errors.invalid_credentials')
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login - SmartSolTech',
|
||||
layout: 'admin/layout',
|
||||
error: 'An error occurred. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.count({ where: { isRead: false } })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
397
.history/routes/admin_20251020044311.js
Normal file
397
.history/routes/admin_20251020044311.js
Normal file
@@ -0,0 +1,397 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: req.t('errors.invalid_credentials')
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: req.t('errors.server_error')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.count({ where: { isRead: false } })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
402
.history/routes/admin_20251020044317.js
Normal file
402
.history/routes/admin_20251020044317.js
Normal file
@@ -0,0 +1,402 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: req.t('errors.invalid_credentials')
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: req.t('errors.server_error')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard (default route)
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
res.redirect('/admin/dashboard');
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolio: portfolioCount,
|
||||
services: servicesCount,
|
||||
contacts: contactsCount,
|
||||
unreadContacts: await Contact.count({ where: { isRead: false } })
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Dashboard - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
402
.history/routes/admin_20251020044325.js
Normal file
402
.history/routes/admin_20251020044325.js
Normal file
@@ -0,0 +1,402 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: req.t('errors.invalid_credentials')
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: req.t('errors.server_error')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard (default route)
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
res.redirect('/admin/dashboard');
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolioCount: portfolioCount,
|
||||
servicesCount: servicesCount,
|
||||
contactsCount: contactsCount,
|
||||
usersCount: await User.count()
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: req.t('admin.dashboard'),
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
402
.history/routes/admin_20251020044351.js
Normal file
402
.history/routes/admin_20251020044351.js
Normal file
@@ -0,0 +1,402 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: req.t('errors.invalid_credentials')
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: req.t('errors.server_error')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard (default route)
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
res.redirect('/admin/dashboard');
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolioCount: portfolioCount,
|
||||
servicesCount: servicesCount,
|
||||
contactsCount: contactsCount,
|
||||
usersCount: await User.count()
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: req.t('admin.dashboard'),
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
422
.history/routes/admin_20251020225524.js
Normal file
422
.history/routes/admin_20251020225524.js
Normal file
@@ -0,0 +1,422 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: req.t('errors.invalid_credentials')
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: req.t('errors.server_error')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard (default route)
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
res.redirect('/admin/dashboard');
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolioCount: portfolioCount,
|
||||
servicesCount: servicesCount,
|
||||
contactsCount: contactsCount,
|
||||
usersCount: await User.count()
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: req.t('admin.dashboard'),
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Banner Editor
|
||||
router.get('/banner-editor', requireAuth, async (req, res) => {
|
||||
try {
|
||||
res.render('admin/banner-editor', {
|
||||
title: 'Редактор Баннеров - Admin Panel',
|
||||
layout: false, // Отключаем layout для этой страницы
|
||||
user: req.session.user,
|
||||
locale: req.getLocale() || 'ko',
|
||||
theme: req.session.theme || 'light'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Banner editor error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading banner editor'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
422
.history/routes/admin_20251020225526.js
Normal file
422
.history/routes/admin_20251020225526.js
Normal file
@@ -0,0 +1,422 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: req.t('errors.invalid_credentials')
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: req.t('errors.server_error')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard (default route)
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
res.redirect('/admin/dashboard');
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolioCount: portfolioCount,
|
||||
servicesCount: servicesCount,
|
||||
contactsCount: contactsCount,
|
||||
usersCount: await User.count()
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: req.t('admin.dashboard'),
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Banner Editor
|
||||
router.get('/banner-editor', requireAuth, async (req, res) => {
|
||||
try {
|
||||
res.render('admin/banner-editor', {
|
||||
title: 'Редактор Баннеров - Admin Panel',
|
||||
layout: false, // Отключаем layout для этой страницы
|
||||
user: req.session.user,
|
||||
locale: req.getLocale() || 'ko',
|
||||
theme: req.session.theme || 'light'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Banner editor error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading banner editor'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
422
.history/routes/admin_20251021214310.js
Normal file
422
.history/routes/admin_20251021214310.js
Normal file
@@ -0,0 +1,422 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: req.t('errors.invalid_credentials')
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: req.t('errors.server_error')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard (default route)
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
res.redirect('/admin/dashboard');
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolioCount: portfolioCount,
|
||||
servicesCount: servicesCount,
|
||||
contactsCount: contactsCount,
|
||||
usersCount: await User.count()
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: req.t('admin.dashboard'),
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Banner Editor
|
||||
router.get('/banner-editor', requireAuth, async (req, res) => {
|
||||
try {
|
||||
res.render('admin/banner-editor', {
|
||||
title: 'Редактор Баннеров - Admin Panel',
|
||||
layout: false, // Отключаем layout для этой страницы
|
||||
user: req.session.user,
|
||||
locale: req.getLocale() || 'ko',
|
||||
theme: req.session.theme || 'light'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Banner editor error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading banner editor'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
422
.history/routes/admin_20251021214318.js
Normal file
422
.history/routes/admin_20251021214318.js
Normal file
@@ -0,0 +1,422 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: req.t('admin.login'),
|
||||
error: req.t('errors.server_error')
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard (default route)
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
res.redirect('/admin/dashboard');
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolioCount: portfolioCount,
|
||||
servicesCount: servicesCount,
|
||||
contactsCount: contactsCount,
|
||||
usersCount: await User.count()
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: req.t('admin.dashboard'),
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Banner Editor
|
||||
router.get('/banner-editor', requireAuth, async (req, res) => {
|
||||
try {
|
||||
res.render('admin/banner-editor', {
|
||||
title: 'Редактор Баннеров - Admin Panel',
|
||||
layout: false, // Отключаем layout для этой страницы
|
||||
user: req.session.user,
|
||||
locale: req.getLocale() || 'ko',
|
||||
theme: req.session.theme || 'light'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Banner editor error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading banner editor'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
422
.history/routes/admin_20251021214326.js
Normal file
422
.history/routes/admin_20251021214326.js
Normal file
@@ -0,0 +1,422 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard (default route)
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
res.redirect('/admin/dashboard');
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolioCount: portfolioCount,
|
||||
servicesCount: servicesCount,
|
||||
contactsCount: contactsCount,
|
||||
usersCount: await User.count()
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: req.t('admin.dashboard'),
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Banner Editor
|
||||
router.get('/banner-editor', requireAuth, async (req, res) => {
|
||||
try {
|
||||
res.render('admin/banner-editor', {
|
||||
title: 'Редактор Баннеров - Admin Panel',
|
||||
layout: false, // Отключаем layout для этой страницы
|
||||
user: req.session.user,
|
||||
locale: req.getLocale() || 'ko',
|
||||
theme: req.session.theme || 'light'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Banner editor error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading banner editor'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
422
.history/routes/admin_20251021214335.js
Normal file
422
.history/routes/admin_20251021214335.js
Normal file
@@ -0,0 +1,422 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard (default route)
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
res.redirect('/admin/dashboard');
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolioCount: portfolioCount,
|
||||
servicesCount: servicesCount,
|
||||
contactsCount: contactsCount,
|
||||
usersCount: await User.count()
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Admin Dashboard',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Banner Editor
|
||||
router.get('/banner-editor', requireAuth, async (req, res) => {
|
||||
try {
|
||||
res.render('admin/banner-editor', {
|
||||
title: 'Редактор Баннеров - Admin Panel',
|
||||
layout: false, // Отключаем layout для этой страницы
|
||||
user: req.session.user,
|
||||
locale: req.getLocale() || 'ko',
|
||||
theme: req.session.theme || 'light'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Banner editor error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading banner editor'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
422
.history/routes/admin_20251021214352.js
Normal file
422
.history/routes/admin_20251021214352.js
Normal file
@@ -0,0 +1,422 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard (default route)
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
res.redirect('/admin/dashboard');
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolioCount: portfolioCount,
|
||||
servicesCount: servicesCount,
|
||||
contactsCount: contactsCount,
|
||||
usersCount: await User.count()
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Admin Dashboard',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Banner Editor
|
||||
router.get('/banner-editor', requireAuth, async (req, res) => {
|
||||
try {
|
||||
res.render('admin/banner-editor', {
|
||||
title: 'Редактор Баннеров - Admin Panel',
|
||||
layout: false, // Отключаем layout для этой страницы
|
||||
user: req.session.user,
|
||||
locale: req.getLocale() || 'ko',
|
||||
theme: req.session.theme || 'light'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Banner editor error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading banner editor'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
501
.history/routes/admin_20251022052409.js
Normal file
501
.history/routes/admin_20251022052409.js
Normal file
@@ -0,0 +1,501 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard (default route)
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
res.redirect('/admin/dashboard');
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolioCount: portfolioCount,
|
||||
servicesCount: servicesCount,
|
||||
contactsCount: contactsCount,
|
||||
usersCount: await User.count()
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Admin Dashboard',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Banner Editor
|
||||
router.get('/banner-editor', requireAuth, async (req, res) => {
|
||||
try {
|
||||
res.render('admin/banner-editor', {
|
||||
title: 'Редактор Баннеров - Admin Panel',
|
||||
layout: false, // Отключаем layout для этой страницы
|
||||
user: req.session.user,
|
||||
locale: req.getLocale() || 'ko',
|
||||
theme: req.session.theme || 'light'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Banner editor error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading banner editor'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Telegram bot configuration and testing
|
||||
router.get('/telegram', requireAuth, (req, res) => {
|
||||
const telegramService = require('../services/telegram');
|
||||
res.render('admin/telegram', {
|
||||
title: 'Telegram Bot - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
botConfigured: telegramService.isEnabled
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/telegram/test', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const telegramService = require('../services/telegram');
|
||||
const result = await telegramService.testConnection();
|
||||
|
||||
if (result.success) {
|
||||
const testMessage = `🤖 <b>Тест Telegram бота</b>\n\n` +
|
||||
`✅ Соединение успешно установлено!\n` +
|
||||
`⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` +
|
||||
`🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` +
|
||||
`Бот готов к отправке уведомлений! 🚀`;
|
||||
|
||||
await telegramService.sendMessage(testMessage);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Test message sent successfully!',
|
||||
botInfo: result.bot
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: result.message || 'Failed to connect to Telegram bot'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Telegram test error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error testing Telegram bot'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/telegram/send', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { message } = req.body;
|
||||
|
||||
if (!message || message.trim().length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Message is required'
|
||||
});
|
||||
}
|
||||
|
||||
const telegramService = require('../services/telegram');
|
||||
const result = await telegramService.sendMessage(message);
|
||||
|
||||
if (result.success) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Message sent successfully!'
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: result.error || 'Failed to send message'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Send Telegram message error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error sending message'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
501
.history/routes/admin_20251022052452.js
Normal file
501
.history/routes/admin_20251022052452.js
Normal file
@@ -0,0 +1,501 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard (default route)
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
res.redirect('/admin/dashboard');
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolioCount: portfolioCount,
|
||||
servicesCount: servicesCount,
|
||||
contactsCount: contactsCount,
|
||||
usersCount: await User.count()
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Admin Dashboard',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Banner Editor
|
||||
router.get('/banner-editor', requireAuth, async (req, res) => {
|
||||
try {
|
||||
res.render('admin/banner-editor', {
|
||||
title: 'Редактор Баннеров - Admin Panel',
|
||||
layout: false, // Отключаем layout для этой страницы
|
||||
user: req.session.user,
|
||||
locale: req.getLocale() || 'ko',
|
||||
theme: req.session.theme || 'light'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Banner editor error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading banner editor'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Telegram bot configuration and testing
|
||||
router.get('/telegram', requireAuth, (req, res) => {
|
||||
const telegramService = require('../services/telegram');
|
||||
res.render('admin/telegram', {
|
||||
title: 'Telegram Bot - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
botConfigured: telegramService.isEnabled
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/telegram/test', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const telegramService = require('../services/telegram');
|
||||
const result = await telegramService.testConnection();
|
||||
|
||||
if (result.success) {
|
||||
const testMessage = `🤖 <b>Тест Telegram бота</b>\n\n` +
|
||||
`✅ Соединение успешно установлено!\n` +
|
||||
`⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` +
|
||||
`🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` +
|
||||
`Бот готов к отправке уведомлений! 🚀`;
|
||||
|
||||
await telegramService.sendMessage(testMessage);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Test message sent successfully!',
|
||||
botInfo: result.bot
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: result.message || 'Failed to connect to Telegram bot'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Telegram test error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error testing Telegram bot'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/telegram/send', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { message } = req.body;
|
||||
|
||||
if (!message || message.trim().length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Message is required'
|
||||
});
|
||||
}
|
||||
|
||||
const telegramService = require('../services/telegram');
|
||||
const result = await telegramService.sendMessage(message);
|
||||
|
||||
if (result.success) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Message sent successfully!'
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: result.error || 'Failed to send message'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Send Telegram message error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error sending message'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
717
.history/routes/admin_20251022194710.js
Normal file
717
.history/routes/admin_20251022194710.js
Normal file
@@ -0,0 +1,717 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard (default route)
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
res.redirect('/admin/dashboard');
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolioCount: portfolioCount,
|
||||
servicesCount: servicesCount,
|
||||
contactsCount: contactsCount,
|
||||
usersCount: await User.count()
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Admin Dashboard',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Banner Editor
|
||||
router.get('/banner-editor', requireAuth, async (req, res) => {
|
||||
try {
|
||||
res.render('admin/banner-editor', {
|
||||
title: 'Редактор Баннеров - Admin Panel',
|
||||
layout: false, // Отключаем layout для этой страницы
|
||||
user: req.session.user,
|
||||
locale: req.getLocale() || 'ko',
|
||||
theme: req.session.theme || 'light'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Banner editor error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading banner editor'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
categories: [
|
||||
'web-development',
|
||||
'mobile-app',
|
||||
'ui-ux-design',
|
||||
'e-commerce',
|
||||
'enterprise',
|
||||
'other'
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// Create portfolio item
|
||||
router.post('/portfolio/add', requireAuth, [
|
||||
body('title').notEmpty().withMessage('제목을 입력해주세요'),
|
||||
body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'),
|
||||
body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'),
|
||||
body('category').notEmpty().withMessage('카테고리를 선택해주세요'),
|
||||
body('technologies').isArray({ min: 1 }).withMessage('최소 한 개의 기술을 입력해주세요'),
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '입력 데이터를 확인해주세요',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
technologies,
|
||||
demoUrl,
|
||||
githubUrl,
|
||||
clientName,
|
||||
duration,
|
||||
isPublished = false,
|
||||
featured = false
|
||||
} = req.body;
|
||||
|
||||
const portfolio = await Portfolio.create({
|
||||
title,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()),
|
||||
demoUrl: demoUrl || null,
|
||||
githubUrl: githubUrl || null,
|
||||
clientName: clientName || null,
|
||||
duration: duration ? parseInt(duration) : null,
|
||||
isPublished: Boolean(isPublished),
|
||||
featured: Boolean(featured),
|
||||
publishedAt: Boolean(isPublished) ? new Date() : null,
|
||||
status: Boolean(isPublished) ? 'published' : 'draft'
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '포트폴리오가 성공적으로 생성되었습니다',
|
||||
portfolio: {
|
||||
id: portfolio.id,
|
||||
title: portfolio.title,
|
||||
category: portfolio.category
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio creation error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '포트폴리오 생성 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
categories: [
|
||||
'web-development',
|
||||
'mobile-app',
|
||||
'ui-ux-design',
|
||||
'e-commerce',
|
||||
'enterprise',
|
||||
'other'
|
||||
]
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Update portfolio item
|
||||
router.put('/portfolio/:id', requireAuth, [
|
||||
body('title').notEmpty().withMessage('제목을 입력해주세요'),
|
||||
body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'),
|
||||
body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'),
|
||||
body('category').notEmpty().withMessage('카테고리를 선택해주세요')
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '입력 데이터를 확인해주세요',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
if (!portfolio) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '포트폴리오를 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
technologies,
|
||||
demoUrl,
|
||||
githubUrl,
|
||||
clientName,
|
||||
duration,
|
||||
isPublished,
|
||||
featured
|
||||
} = req.body;
|
||||
|
||||
// Update portfolio
|
||||
await portfolio.update({
|
||||
title,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()),
|
||||
demoUrl: demoUrl || null,
|
||||
githubUrl: githubUrl || null,
|
||||
clientName: clientName || null,
|
||||
duration: duration ? parseInt(duration) : null,
|
||||
isPublished: Boolean(isPublished),
|
||||
featured: Boolean(featured),
|
||||
publishedAt: Boolean(isPublished) && !portfolio.publishedAt ? new Date() : portfolio.publishedAt,
|
||||
status: Boolean(isPublished) ? 'published' : 'draft'
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '포트폴리오가 성공적으로 업데이트되었습니다',
|
||||
portfolio: {
|
||||
id: portfolio.id,
|
||||
title: portfolio.title,
|
||||
category: portfolio.category
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio update error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '포트폴리오 업데이트 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Delete portfolio item
|
||||
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: '포트폴리오를 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
await portfolio.destroy();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '포트폴리오가 성공적으로 삭제되었습니다'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio deletion error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '포트폴리오 삭제 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle portfolio publish status
|
||||
router.patch('/portfolio/:id/toggle-publish', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '포트폴리오를 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
const newStatus = !portfolio.isPublished;
|
||||
await portfolio.update({
|
||||
isPublished: newStatus,
|
||||
publishedAt: newStatus && !portfolio.publishedAt ? new Date() : portfolio.publishedAt,
|
||||
status: newStatus ? 'published' : 'draft'
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `포트폴리오가 ${newStatus ? '게시' : '비공개'}되었습니다`,
|
||||
isPublished: newStatus
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio toggle publish error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '상태 변경 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Telegram bot configuration and testing
|
||||
router.get('/telegram', requireAuth, (req, res) => {
|
||||
const telegramService = require('../services/telegram');
|
||||
res.render('admin/telegram', {
|
||||
title: 'Telegram Bot - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
botConfigured: telegramService.isEnabled
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/telegram/test', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const telegramService = require('../services/telegram');
|
||||
const result = await telegramService.testConnection();
|
||||
|
||||
if (result.success) {
|
||||
const testMessage = `🤖 <b>Тест Telegram бота</b>\n\n` +
|
||||
`✅ Соединение успешно установлено!\n` +
|
||||
`⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` +
|
||||
`🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` +
|
||||
`Бот готов к отправке уведомлений! 🚀`;
|
||||
|
||||
await telegramService.sendMessage(testMessage);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Test message sent successfully!',
|
||||
botInfo: result.bot
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: result.message || 'Failed to connect to Telegram bot'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Telegram test error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error testing Telegram bot'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/telegram/send', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { message } = req.body;
|
||||
|
||||
if (!message || message.trim().length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Message is required'
|
||||
});
|
||||
}
|
||||
|
||||
const telegramService = require('../services/telegram');
|
||||
const result = await telegramService.sendMessage(message);
|
||||
|
||||
if (result.success) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Message sent successfully!'
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: result.error || 'Failed to send message'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Send Telegram message error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error sending message'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
921
.history/routes/admin_20251022194741.js
Normal file
921
.history/routes/admin_20251022194741.js
Normal file
@@ -0,0 +1,921 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard (default route)
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
res.redirect('/admin/dashboard');
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolioCount: portfolioCount,
|
||||
servicesCount: servicesCount,
|
||||
contactsCount: contactsCount,
|
||||
usersCount: await User.count()
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Admin Dashboard',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Banner Editor
|
||||
router.get('/banner-editor', requireAuth, async (req, res) => {
|
||||
try {
|
||||
res.render('admin/banner-editor', {
|
||||
title: 'Редактор Баннеров - Admin Panel',
|
||||
layout: false, // Отключаем layout для этой страницы
|
||||
user: req.session.user,
|
||||
locale: req.getLocale() || 'ko',
|
||||
theme: req.session.theme || 'light'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Banner editor error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading banner editor'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
categories: [
|
||||
'web-development',
|
||||
'mobile-app',
|
||||
'ui-ux-design',
|
||||
'e-commerce',
|
||||
'enterprise',
|
||||
'other'
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// Create portfolio item
|
||||
router.post('/portfolio/add', requireAuth, [
|
||||
body('title').notEmpty().withMessage('제목을 입력해주세요'),
|
||||
body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'),
|
||||
body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'),
|
||||
body('category').notEmpty().withMessage('카테고리를 선택해주세요'),
|
||||
body('technologies').isArray({ min: 1 }).withMessage('최소 한 개의 기술을 입력해주세요'),
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '입력 데이터를 확인해주세요',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
technologies,
|
||||
demoUrl,
|
||||
githubUrl,
|
||||
clientName,
|
||||
duration,
|
||||
isPublished = false,
|
||||
featured = false
|
||||
} = req.body;
|
||||
|
||||
const portfolio = await Portfolio.create({
|
||||
title,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()),
|
||||
demoUrl: demoUrl || null,
|
||||
githubUrl: githubUrl || null,
|
||||
clientName: clientName || null,
|
||||
duration: duration ? parseInt(duration) : null,
|
||||
isPublished: Boolean(isPublished),
|
||||
featured: Boolean(featured),
|
||||
publishedAt: Boolean(isPublished) ? new Date() : null,
|
||||
status: Boolean(isPublished) ? 'published' : 'draft'
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '포트폴리오가 성공적으로 생성되었습니다',
|
||||
portfolio: {
|
||||
id: portfolio.id,
|
||||
title: portfolio.title,
|
||||
category: portfolio.category
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio creation error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '포트폴리오 생성 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
categories: [
|
||||
'web-development',
|
||||
'mobile-app',
|
||||
'ui-ux-design',
|
||||
'e-commerce',
|
||||
'enterprise',
|
||||
'other'
|
||||
]
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Update portfolio item
|
||||
router.put('/portfolio/:id', requireAuth, [
|
||||
body('title').notEmpty().withMessage('제목을 입력해주세요'),
|
||||
body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'),
|
||||
body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'),
|
||||
body('category').notEmpty().withMessage('카테고리를 선택해주세요')
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '입력 데이터를 확인해주세요',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
if (!portfolio) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '포트폴리오를 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
technologies,
|
||||
demoUrl,
|
||||
githubUrl,
|
||||
clientName,
|
||||
duration,
|
||||
isPublished,
|
||||
featured
|
||||
} = req.body;
|
||||
|
||||
// Update portfolio
|
||||
await portfolio.update({
|
||||
title,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()),
|
||||
demoUrl: demoUrl || null,
|
||||
githubUrl: githubUrl || null,
|
||||
clientName: clientName || null,
|
||||
duration: duration ? parseInt(duration) : null,
|
||||
isPublished: Boolean(isPublished),
|
||||
featured: Boolean(featured),
|
||||
publishedAt: Boolean(isPublished) && !portfolio.publishedAt ? new Date() : portfolio.publishedAt,
|
||||
status: Boolean(isPublished) ? 'published' : 'draft'
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '포트폴리오가 성공적으로 업데이트되었습니다',
|
||||
portfolio: {
|
||||
id: portfolio.id,
|
||||
title: portfolio.title,
|
||||
category: portfolio.category
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio update error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '포트폴리오 업데이트 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Delete portfolio item
|
||||
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: '포트폴리오를 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
await portfolio.destroy();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '포트폴리오가 성공적으로 삭제되었습니다'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio deletion error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '포트폴리오 삭제 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle portfolio publish status
|
||||
router.patch('/portfolio/:id/toggle-publish', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '포트폴리오를 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
const newStatus = !portfolio.isPublished;
|
||||
await portfolio.update({
|
||||
isPublished: newStatus,
|
||||
publishedAt: newStatus && !portfolio.publishedAt ? new Date() : portfolio.publishedAt,
|
||||
status: newStatus ? 'published' : 'draft'
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `포트폴리오가 ${newStatus ? '게시' : '비공개'}되었습니다`,
|
||||
isPublished: newStatus
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio toggle publish error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '상태 변경 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
serviceTypes: [
|
||||
'web-development',
|
||||
'mobile-app',
|
||||
'ui-ux-design',
|
||||
'e-commerce',
|
||||
'seo',
|
||||
'maintenance',
|
||||
'consultation',
|
||||
'other'
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// Create service
|
||||
router.post('/services/add', requireAuth, [
|
||||
body('name').notEmpty().withMessage('서비스명을 입력해주세요'),
|
||||
body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'),
|
||||
body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'),
|
||||
body('category').notEmpty().withMessage('카테고리를 선택해주세요'),
|
||||
body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요')
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '입력 데이터를 확인해주세요',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
name,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
basePrice,
|
||||
features,
|
||||
duration,
|
||||
isActive = true,
|
||||
featured = false
|
||||
} = req.body;
|
||||
|
||||
const service = await Service.create({
|
||||
name,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
basePrice: parseFloat(basePrice),
|
||||
features: features || [],
|
||||
duration: duration ? parseInt(duration) : null,
|
||||
isActive: Boolean(isActive),
|
||||
featured: Boolean(featured)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '서비스가 성공적으로 생성되었습니다',
|
||||
service: {
|
||||
id: service.id,
|
||||
name: service.name,
|
||||
category: service.category
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service creation error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서비스 생성 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['id', 'title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio,
|
||||
serviceTypes: [
|
||||
'web-development',
|
||||
'mobile-app',
|
||||
'ui-ux-design',
|
||||
'e-commerce',
|
||||
'seo',
|
||||
'maintenance',
|
||||
'consultation',
|
||||
'other'
|
||||
]
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Update service
|
||||
router.put('/services/:id', requireAuth, [
|
||||
body('name').notEmpty().withMessage('서비스명을 입력해주세요'),
|
||||
body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'),
|
||||
body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'),
|
||||
body('category').notEmpty().withMessage('카테고리를 선택해주세요'),
|
||||
body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요')
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '입력 데이터를 확인해주세요',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
if (!service) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '서비스를 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
name,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
basePrice,
|
||||
features,
|
||||
duration,
|
||||
isActive,
|
||||
featured
|
||||
} = req.body;
|
||||
|
||||
await service.update({
|
||||
name,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
basePrice: parseFloat(basePrice),
|
||||
features: features || [],
|
||||
duration: duration ? parseInt(duration) : null,
|
||||
isActive: Boolean(isActive),
|
||||
featured: Boolean(featured)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '서비스가 성공적으로 업데이트되었습니다',
|
||||
service: {
|
||||
id: service.id,
|
||||
name: service.name,
|
||||
category: service.category
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service update error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서비스 업데이트 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Delete service
|
||||
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: '서비스를 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
await service.destroy();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '서비스가 성공적으로 삭제되었습니다'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service deletion error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서비스 삭제 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle service active status
|
||||
router.patch('/services/:id/toggle-active', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '서비스를 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
const newStatus = !service.isActive;
|
||||
await service.update({ isActive: newStatus });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `서비스가 ${newStatus ? '활성화' : '비활성화'}되었습니다`,
|
||||
isActive: newStatus
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service toggle active error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '상태 변경 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Telegram bot configuration and testing
|
||||
router.get('/telegram', requireAuth, (req, res) => {
|
||||
const telegramService = require('../services/telegram');
|
||||
res.render('admin/telegram', {
|
||||
title: 'Telegram Bot - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
botConfigured: telegramService.isEnabled
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/telegram/test', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const telegramService = require('../services/telegram');
|
||||
const result = await telegramService.testConnection();
|
||||
|
||||
if (result.success) {
|
||||
const testMessage = `🤖 <b>Тест Telegram бота</b>\n\n` +
|
||||
`✅ Соединение успешно установлено!\n` +
|
||||
`⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` +
|
||||
`🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` +
|
||||
`Бот готов к отправке уведомлений! 🚀`;
|
||||
|
||||
await telegramService.sendMessage(testMessage);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Test message sent successfully!',
|
||||
botInfo: result.bot
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: result.message || 'Failed to connect to Telegram bot'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Telegram test error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error testing Telegram bot'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/telegram/send', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { message } = req.body;
|
||||
|
||||
if (!message || message.trim().length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Message is required'
|
||||
});
|
||||
}
|
||||
|
||||
const telegramService = require('../services/telegram');
|
||||
const result = await telegramService.sendMessage(message);
|
||||
|
||||
if (result.success) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Message sent successfully!'
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: result.error || 'Failed to send message'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Send Telegram message error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error sending message'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
921
.history/routes/admin_20251022194824.js
Normal file
921
.history/routes/admin_20251022194824.js
Normal file
@@ -0,0 +1,921 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User, Portfolio, Service, Contact, SiteSettings, Banner } = require('../models');
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.redirect('/admin/login');
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Admin login page
|
||||
router.get('/login', (req, res) => {
|
||||
if (req.session.user) {
|
||||
return res.redirect('/admin/dashboard');
|
||||
}
|
||||
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: null
|
||||
});
|
||||
});
|
||||
|
||||
// Admin login POST
|
||||
router.post('/login', async (req, res) => {
|
||||
try {
|
||||
const { email, password } = req.body;
|
||||
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user || !(await user.comparePassword(password))) {
|
||||
return res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
await user.updateLastLogin();
|
||||
|
||||
req.session.user = {
|
||||
id: user.id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.redirect('/admin/dashboard');
|
||||
} catch (error) {
|
||||
console.error('Admin login error:', error);
|
||||
res.render('admin/login', {
|
||||
title: 'Admin Login',
|
||||
error: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Admin logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
console.error('Logout error:', err);
|
||||
}
|
||||
res.redirect('/admin/login');
|
||||
});
|
||||
});
|
||||
|
||||
// Dashboard (default route)
|
||||
router.get('/', requireAuth, async (req, res) => {
|
||||
res.redirect('/admin/dashboard');
|
||||
});
|
||||
|
||||
// Dashboard
|
||||
router.get('/dashboard', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const [
|
||||
portfolioCount,
|
||||
servicesCount,
|
||||
contactsCount,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
] = await Promise.all([
|
||||
Portfolio.count({ where: { isPublished: true } }),
|
||||
Service.count({ where: { isActive: true } }),
|
||||
Contact.count(),
|
||||
Contact.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
}),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
order: [['createdAt', 'DESC']],
|
||||
limit: 5
|
||||
})
|
||||
]);
|
||||
|
||||
const stats = {
|
||||
portfolioCount: portfolioCount,
|
||||
servicesCount: servicesCount,
|
||||
contactsCount: contactsCount,
|
||||
usersCount: await User.count()
|
||||
};
|
||||
|
||||
res.render('admin/dashboard', {
|
||||
title: 'Admin Dashboard',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
stats,
|
||||
recentContacts,
|
||||
recentPortfolio
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Dashboard error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading dashboard'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Banner Editor
|
||||
router.get('/banner-editor', requireAuth, async (req, res) => {
|
||||
try {
|
||||
res.render('admin/banner-editor', {
|
||||
title: 'Редактор Баннеров - Admin Panel',
|
||||
layout: false, // Отключаем layout для этой страницы
|
||||
user: req.session.user,
|
||||
locale: req.getLocale() || 'ko',
|
||||
theme: req.session.theme || 'light'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Banner editor error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading banner editor'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio management
|
||||
router.get('/portfolio', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [portfolio, total] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/portfolio/list', {
|
||||
title: 'Portfolio Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add portfolio item
|
||||
router.get('/portfolio/add', requireAuth, (req, res) => {
|
||||
res.render('admin/portfolio/add', {
|
||||
title: 'Add Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
categories: [
|
||||
'web-development',
|
||||
'mobile-app',
|
||||
'ui-ux-design',
|
||||
'e-commerce',
|
||||
'enterprise',
|
||||
'other'
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// Create portfolio item
|
||||
router.post('/portfolio/add', requireAuth, [
|
||||
body('title').notEmpty().withMessage('제목을 입력해주세요'),
|
||||
body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'),
|
||||
body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'),
|
||||
body('category').notEmpty().withMessage('카테고리를 선택해주세요'),
|
||||
body('technologies').isArray({ min: 1 }).withMessage('최소 한 개의 기술을 입력해주세요'),
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '입력 데이터를 확인해주세요',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
technologies,
|
||||
demoUrl,
|
||||
githubUrl,
|
||||
clientName,
|
||||
duration,
|
||||
isPublished = false,
|
||||
featured = false
|
||||
} = req.body;
|
||||
|
||||
const portfolio = await Portfolio.create({
|
||||
title,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()),
|
||||
demoUrl: demoUrl || null,
|
||||
githubUrl: githubUrl || null,
|
||||
clientName: clientName || null,
|
||||
duration: duration ? parseInt(duration) : null,
|
||||
isPublished: Boolean(isPublished),
|
||||
featured: Boolean(featured),
|
||||
publishedAt: Boolean(isPublished) ? new Date() : null,
|
||||
status: Boolean(isPublished) ? 'published' : 'draft'
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '포트폴리오가 성공적으로 생성되었습니다',
|
||||
portfolio: {
|
||||
id: portfolio.id,
|
||||
title: portfolio.title,
|
||||
category: portfolio.category
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio creation error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '포트폴리오 생성 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Edit portfolio item
|
||||
router.get('/portfolio/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
res.render('admin/portfolio/edit', {
|
||||
title: 'Edit Portfolio Item - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
portfolio,
|
||||
categories: [
|
||||
'web-development',
|
||||
'mobile-app',
|
||||
'ui-ux-design',
|
||||
'e-commerce',
|
||||
'enterprise',
|
||||
'other'
|
||||
]
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Update portfolio item
|
||||
router.put('/portfolio/:id', requireAuth, [
|
||||
body('title').notEmpty().withMessage('제목을 입력해주세요'),
|
||||
body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'),
|
||||
body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'),
|
||||
body('category').notEmpty().withMessage('카테고리를 선택해주세요')
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '입력 데이터를 확인해주세요',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
if (!portfolio) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '포트폴리오를 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
title,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
technologies,
|
||||
demoUrl,
|
||||
githubUrl,
|
||||
clientName,
|
||||
duration,
|
||||
isPublished,
|
||||
featured
|
||||
} = req.body;
|
||||
|
||||
// Update portfolio
|
||||
await portfolio.update({
|
||||
title,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
technologies: Array.isArray(technologies) ? technologies : technologies.split(',').map(t => t.trim()),
|
||||
demoUrl: demoUrl || null,
|
||||
githubUrl: githubUrl || null,
|
||||
clientName: clientName || null,
|
||||
duration: duration ? parseInt(duration) : null,
|
||||
isPublished: Boolean(isPublished),
|
||||
featured: Boolean(featured),
|
||||
publishedAt: Boolean(isPublished) && !portfolio.publishedAt ? new Date() : portfolio.publishedAt,
|
||||
status: Boolean(isPublished) ? 'published' : 'draft'
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '포트폴리오가 성공적으로 업데이트되었습니다',
|
||||
portfolio: {
|
||||
id: portfolio.id,
|
||||
title: portfolio.title,
|
||||
category: portfolio.category
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio update error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '포트폴리오 업데이트 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Delete portfolio item
|
||||
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: '포트폴리오를 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
await portfolio.destroy();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '포트폴리오가 성공적으로 삭제되었습니다'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio deletion error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '포트폴리오 삭제 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle portfolio publish status
|
||||
router.patch('/portfolio/:id/toggle-publish', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '포트폴리오를 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
const newStatus = !portfolio.isPublished;
|
||||
await portfolio.update({
|
||||
isPublished: newStatus,
|
||||
publishedAt: newStatus && !portfolio.publishedAt ? new Date() : portfolio.publishedAt,
|
||||
status: newStatus ? 'published' : 'draft'
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `포트폴리오가 ${newStatus ? '게시' : '비공개'}되었습니다`,
|
||||
isPublished: newStatus
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio toggle publish error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '상태 변경 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services management
|
||||
router.get('/services', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
|
||||
const [services, total] = await Promise.all([
|
||||
Service.findAll({
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Service.count()
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/services/list', {
|
||||
title: 'Services Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
services,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Add service
|
||||
router.get('/services/add', requireAuth, (req, res) => {
|
||||
res.render('admin/services/add', {
|
||||
title: 'Add Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
serviceTypes: [
|
||||
'web-development',
|
||||
'mobile-app',
|
||||
'ui-ux-design',
|
||||
'e-commerce',
|
||||
'seo',
|
||||
'maintenance',
|
||||
'consultation',
|
||||
'other'
|
||||
]
|
||||
});
|
||||
});
|
||||
|
||||
// Create service
|
||||
router.post('/services/add', requireAuth, [
|
||||
body('name').notEmpty().withMessage('서비스명을 입력해주세요'),
|
||||
body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'),
|
||||
body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'),
|
||||
body('category').notEmpty().withMessage('카테고리를 선택해주세요'),
|
||||
body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요')
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '입력 데이터를 확인해주세요',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
name,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
basePrice,
|
||||
features,
|
||||
duration,
|
||||
isActive = true,
|
||||
featured = false
|
||||
} = req.body;
|
||||
|
||||
const service = await Service.create({
|
||||
name,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
basePrice: parseFloat(basePrice),
|
||||
features: features || [],
|
||||
duration: duration ? parseInt(duration) : null,
|
||||
isActive: Boolean(isActive),
|
||||
featured: Boolean(featured)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '서비스가 성공적으로 생성되었습니다',
|
||||
service: {
|
||||
id: service.id,
|
||||
name: service.name,
|
||||
category: service.category
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service creation error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서비스 생성 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Edit service
|
||||
router.get('/services/edit/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
const availablePortfolio = await Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['id', 'title', 'category']
|
||||
});
|
||||
|
||||
res.render('admin/services/edit', {
|
||||
title: 'Edit Service - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
service,
|
||||
availablePortfolio,
|
||||
serviceTypes: [
|
||||
'web-development',
|
||||
'mobile-app',
|
||||
'ui-ux-design',
|
||||
'e-commerce',
|
||||
'seo',
|
||||
'maintenance',
|
||||
'consultation',
|
||||
'other'
|
||||
]
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service edit error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Update service
|
||||
router.put('/services/:id', requireAuth, [
|
||||
body('name').notEmpty().withMessage('서비스명을 입력해주세요'),
|
||||
body('shortDescription').notEmpty().withMessage('간단한 설명을 입력해주세요'),
|
||||
body('description').notEmpty().withMessage('자세한 설명을 입력해주세요'),
|
||||
body('category').notEmpty().withMessage('카테고리를 선택해주세요'),
|
||||
body('basePrice').isNumeric().withMessage('기본 가격을 입력해주세요')
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: '입력 데이터를 확인해주세요',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
if (!service) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '서비스를 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
const {
|
||||
name,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
basePrice,
|
||||
features,
|
||||
duration,
|
||||
isActive,
|
||||
featured
|
||||
} = req.body;
|
||||
|
||||
await service.update({
|
||||
name,
|
||||
shortDescription,
|
||||
description,
|
||||
category,
|
||||
basePrice: parseFloat(basePrice),
|
||||
features: features || [],
|
||||
duration: duration ? parseInt(duration) : null,
|
||||
isActive: Boolean(isActive),
|
||||
featured: Boolean(featured)
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '서비스가 성공적으로 업데이트되었습니다',
|
||||
service: {
|
||||
id: service.id,
|
||||
name: service.name,
|
||||
category: service.category
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service update error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서비스 업데이트 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Delete service
|
||||
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: '서비스를 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
await service.destroy();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: '서비스가 성공적으로 삭제되었습니다'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service deletion error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '서비스 삭제 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Toggle service active status
|
||||
router.patch('/services/:id/toggle-active', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findByPk(req.params.id);
|
||||
|
||||
if (!service) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: '서비스를 찾을 수 없습니다'
|
||||
});
|
||||
}
|
||||
|
||||
const newStatus = !service.isActive;
|
||||
await service.update({ isActive: newStatus });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `서비스가 ${newStatus ? '활성화' : '비활성화'}되었습니다`,
|
||||
isActive: newStatus
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service toggle active error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: '상태 변경 중 오류가 발생했습니다'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contacts management
|
||||
router.get('/contacts', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 20;
|
||||
const skip = (page - 1) * limit;
|
||||
const status = req.query.status;
|
||||
|
||||
let whereClause = {};
|
||||
if (status && status !== 'all') {
|
||||
whereClause.status = status;
|
||||
}
|
||||
|
||||
const [contacts, total] = await Promise.all([
|
||||
Contact.findAll({
|
||||
where: whereClause,
|
||||
order: [['createdAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Contact.count({ where: whereClause })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('admin/contacts/list', {
|
||||
title: 'Contacts Management - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contacts,
|
||||
currentStatus: status || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contacts list error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contacts'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// View contact details
|
||||
router.get('/contacts/:id', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const contact = await Contact.findByPk(req.params.id);
|
||||
|
||||
if (!contact) {
|
||||
return res.status(404).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Contact not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Mark as read
|
||||
if (!contact.isRead) {
|
||||
contact.isRead = true;
|
||||
await contact.save();
|
||||
}
|
||||
|
||||
res.render('admin/contacts/view', {
|
||||
title: 'Contact Details - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
contact
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact view error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading contact'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Settings
|
||||
router.get('/settings', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || await SiteSettings.create({});
|
||||
|
||||
res.render('admin/settings', {
|
||||
title: 'Site Settings - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
settings
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Settings error:', error);
|
||||
res.status(500).render('admin/error', {
|
||||
title: 'Error - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
message: 'Error loading settings'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Media gallery
|
||||
router.get('/media', requireAuth, (req, res) => {
|
||||
res.render('admin/media', {
|
||||
title: 'Media Gallery - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user
|
||||
});
|
||||
});
|
||||
|
||||
// Telegram bot configuration and testing
|
||||
router.get('/telegram', requireAuth, (req, res) => {
|
||||
const telegramService = require('../services/telegram');
|
||||
res.render('admin/telegram', {
|
||||
title: 'Telegram Bot - Admin Panel',
|
||||
layout: 'admin/layout',
|
||||
user: req.session.user,
|
||||
botConfigured: telegramService.isEnabled
|
||||
});
|
||||
});
|
||||
|
||||
router.post('/telegram/test', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const telegramService = require('../services/telegram');
|
||||
const result = await telegramService.testConnection();
|
||||
|
||||
if (result.success) {
|
||||
const testMessage = `🤖 <b>Тест Telegram бота</b>\n\n` +
|
||||
`✅ Соединение успешно установлено!\n` +
|
||||
`⏰ Время тестирования: ${new Date().toLocaleString('ru-RU')}\n` +
|
||||
`🌐 Сайт: ${process.env.BASE_URL || 'http://localhost:3000'}\n\n` +
|
||||
`Бот готов к отправке уведомлений! 🚀`;
|
||||
|
||||
await telegramService.sendMessage(testMessage);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Test message sent successfully!',
|
||||
botInfo: result.bot
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: result.message || 'Failed to connect to Telegram bot'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Telegram test error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error testing Telegram bot'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
router.post('/telegram/send', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { message } = req.body;
|
||||
|
||||
if (!message || message.trim().length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Message is required'
|
||||
});
|
||||
}
|
||||
|
||||
const telegramService = require('../services/telegram');
|
||||
const result = await telegramService.sendMessage(message);
|
||||
|
||||
if (result.success) {
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Message sent successfully!'
|
||||
});
|
||||
} else {
|
||||
res.status(400).json({
|
||||
success: false,
|
||||
message: result.error || 'Failed to send message'
|
||||
});
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Send Telegram message error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error sending message'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
1241
.history/routes/admin_20251022194900.js
Normal file
1241
.history/routes/admin_20251022194900.js
Normal file
File diff suppressed because it is too large
Load Diff
1426
.history/routes/admin_20251022195039.js
Normal file
1426
.history/routes/admin_20251022195039.js
Normal file
File diff suppressed because it is too large
Load Diff
1440
.history/routes/admin_20251022195541.js
Normal file
1440
.history/routes/admin_20251022195541.js
Normal file
File diff suppressed because it is too large
Load Diff
1440
.history/routes/admin_20251022195905.js
Normal file
1440
.history/routes/admin_20251022195905.js
Normal file
File diff suppressed because it is too large
Load Diff
1458
.history/routes/admin_20251022205401.js
Normal file
1458
.history/routes/admin_20251022205401.js
Normal file
File diff suppressed because it is too large
Load Diff
1504
.history/routes/admin_20251022205440.js
Normal file
1504
.history/routes/admin_20251022205440.js
Normal file
File diff suppressed because it is too large
Load Diff
1552
.history/routes/admin_20251022205520.js
Normal file
1552
.history/routes/admin_20251022205520.js
Normal file
File diff suppressed because it is too large
Load Diff
1610
.history/routes/admin_20251022205549.js
Normal file
1610
.history/routes/admin_20251022205549.js
Normal file
File diff suppressed because it is too large
Load Diff
1610
.history/routes/admin_20251022211821.js
Normal file
1610
.history/routes/admin_20251022211821.js
Normal file
File diff suppressed because it is too large
Load Diff
284
.history/routes/api/admin_20251021213116.js
Normal file
284
.history/routes/api/admin_20251021213116.js
Normal file
@@ -0,0 +1,284 @@
|
||||
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.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 (we'll implement this with Telegram bot)
|
||||
const telegramService = require('../../services/telegram');
|
||||
await telegramService.sendContactNotification(contact);
|
||||
|
||||
res.json({ success: true });
|
||||
} catch (error) {
|
||||
console.error('Telegram notification error:', error);
|
||||
res.status(500).json({ success: false, message: error.message });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
388
.history/routes/api/admin_20251021213414.js
Normal file
388
.history/routes/api/admin_20251021213414.js
Normal file
@@ -0,0 +1,388 @@
|
||||
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.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;
|
||||
388
.history/routes/api/admin_20251021214113.js
Normal file
388
.history/routes/api/admin_20251021214113.js
Normal file
@@ -0,0 +1,388 @@
|
||||
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.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;
|
||||
201
.history/routes/auth_20251019204627.js
Normal file
201
.history/routes/auth_20251019204627.js
Normal file
@@ -0,0 +1,201 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User } = require('../models');
|
||||
|
||||
// Login validation rules
|
||||
const loginValidation = [
|
||||
body('email').isEmail().normalizeEmail(),
|
||||
body('password').isLength({ min: 6 })
|
||||
];
|
||||
|
||||
// Login
|
||||
router.post('/login', loginValidation, async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { email, password } = req.body;
|
||||
|
||||
// Find user
|
||||
const user = await User.findOne({ email, isActive: true });
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
// Check password
|
||||
const isValidPassword = await user.comparePassword(password);
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
// Update last login
|
||||
await user.updateLastLogin();
|
||||
|
||||
// Create JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: user._id, email: user.email, role: user.role },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
// Set session
|
||||
req.session.user = {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Login successful',
|
||||
token,
|
||||
user: {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
avatar: user.avatar
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'Could not log out'
|
||||
});
|
||||
}
|
||||
|
||||
res.clearCookie('connect.sid');
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Logout successful'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Check authentication status
|
||||
router.get('/me', async (req, res) => {
|
||||
try {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Not authenticated'
|
||||
});
|
||||
}
|
||||
|
||||
const user = await User.findById(req.session.user.id)
|
||||
.select('-password');
|
||||
|
||||
if (!user || !user.isActive) {
|
||||
req.session.destroy();
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'User not found or inactive'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
user: {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
avatar: user.avatar,
|
||||
lastLogin: user.lastLogin
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Auth check error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Change password
|
||||
router.put('/change-password', [
|
||||
body('currentPassword').isLength({ min: 6 }),
|
||||
body('newPassword').isLength({ min: 6 })
|
||||
], async (req, res) => {
|
||||
try {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Not authenticated'
|
||||
});
|
||||
}
|
||||
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
const user = await User.findById(req.session.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'User not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify current password
|
||||
const isValidPassword = await user.comparePassword(currentPassword);
|
||||
if (!isValidPassword) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Current password is incorrect'
|
||||
});
|
||||
}
|
||||
|
||||
// Update password
|
||||
user.password = newPassword;
|
||||
await user.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Password updated successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Change password error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
206
.history/routes/auth_20251019204633.js
Normal file
206
.history/routes/auth_20251019204633.js
Normal file
@@ -0,0 +1,206 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User } = require('../models');
|
||||
|
||||
// Login validation rules
|
||||
const loginValidation = [
|
||||
body('email').isEmail().normalizeEmail(),
|
||||
body('password').isLength({ min: 6 })
|
||||
];
|
||||
|
||||
// Login
|
||||
router.post('/login', loginValidation, async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { email, password } = req.body;
|
||||
|
||||
// Find user
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
// Check password
|
||||
const isValidPassword = await user.comparePassword(password);
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
// Update last login
|
||||
await user.updateLastLogin();
|
||||
|
||||
// Create JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: user._id, email: user.email, role: user.role },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
// Set session
|
||||
req.session.user = {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Login successful',
|
||||
token,
|
||||
user: {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
avatar: user.avatar
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'Could not log out'
|
||||
});
|
||||
}
|
||||
|
||||
res.clearCookie('connect.sid');
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Logout successful'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Check authentication status
|
||||
router.get('/me', async (req, res) => {
|
||||
try {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Not authenticated'
|
||||
});
|
||||
}
|
||||
|
||||
const user = await User.findById(req.session.user.id)
|
||||
.select('-password');
|
||||
|
||||
if (!user || !user.isActive) {
|
||||
req.session.destroy();
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'User not found or inactive'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
user: {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
avatar: user.avatar,
|
||||
lastLogin: user.lastLogin
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Auth check error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Change password
|
||||
router.put('/change-password', [
|
||||
body('currentPassword').isLength({ min: 6 }),
|
||||
body('newPassword').isLength({ min: 6 })
|
||||
], async (req, res) => {
|
||||
try {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Not authenticated'
|
||||
});
|
||||
}
|
||||
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
const user = await User.findById(req.session.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'User not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify current password
|
||||
const isValidPassword = await user.comparePassword(currentPassword);
|
||||
if (!isValidPassword) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Current password is incorrect'
|
||||
});
|
||||
}
|
||||
|
||||
// Update password
|
||||
user.password = newPassword;
|
||||
await user.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Password updated successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Change password error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
207
.history/routes/auth_20251019204648.js
Normal file
207
.history/routes/auth_20251019204648.js
Normal file
@@ -0,0 +1,207 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User } = require('../models');
|
||||
|
||||
// Login validation rules
|
||||
const loginValidation = [
|
||||
body('email').isEmail().normalizeEmail(),
|
||||
body('password').isLength({ min: 6 })
|
||||
];
|
||||
|
||||
// Login
|
||||
router.post('/login', loginValidation, async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { email, password } = req.body;
|
||||
|
||||
// Find user
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
// Check password
|
||||
const isValidPassword = await user.comparePassword(password);
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
// Update last login
|
||||
await user.updateLastLogin();
|
||||
|
||||
// Create JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: user._id, email: user.email, role: user.role },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
// Set session
|
||||
req.session.user = {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Login successful',
|
||||
token,
|
||||
user: {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
avatar: user.avatar
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'Could not log out'
|
||||
});
|
||||
}
|
||||
|
||||
res.clearCookie('connect.sid');
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Logout successful'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Check authentication status
|
||||
router.get('/me', async (req, res) => {
|
||||
try {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Not authenticated'
|
||||
});
|
||||
}
|
||||
|
||||
const user = await User.findByPk(req.session.user.id, {
|
||||
attributes: { exclude: ['password'] }
|
||||
});
|
||||
|
||||
if (!user || !user.isActive) {
|
||||
req.session.destroy();
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'User not found or inactive'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
user: {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
avatar: user.avatar,
|
||||
lastLogin: user.lastLogin
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Auth check error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Change password
|
||||
router.put('/change-password', [
|
||||
body('currentPassword').isLength({ min: 6 }),
|
||||
body('newPassword').isLength({ min: 6 })
|
||||
], async (req, res) => {
|
||||
try {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Not authenticated'
|
||||
});
|
||||
}
|
||||
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
const user = await User.findById(req.session.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'User not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify current password
|
||||
const isValidPassword = await user.comparePassword(currentPassword);
|
||||
if (!isValidPassword) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Current password is incorrect'
|
||||
});
|
||||
}
|
||||
|
||||
// Update password
|
||||
user.password = newPassword;
|
||||
await user.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Password updated successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Change password error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
207
.history/routes/auth_20251019204658.js
Normal file
207
.history/routes/auth_20251019204658.js
Normal file
@@ -0,0 +1,207 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User } = require('../models');
|
||||
|
||||
// Login validation rules
|
||||
const loginValidation = [
|
||||
body('email').isEmail().normalizeEmail(),
|
||||
body('password').isLength({ min: 6 })
|
||||
];
|
||||
|
||||
// Login
|
||||
router.post('/login', loginValidation, async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { email, password } = req.body;
|
||||
|
||||
// Find user
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
// Check password
|
||||
const isValidPassword = await user.comparePassword(password);
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
// Update last login
|
||||
await user.updateLastLogin();
|
||||
|
||||
// Create JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: user._id, email: user.email, role: user.role },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
// Set session
|
||||
req.session.user = {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Login successful',
|
||||
token,
|
||||
user: {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
avatar: user.avatar
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'Could not log out'
|
||||
});
|
||||
}
|
||||
|
||||
res.clearCookie('connect.sid');
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Logout successful'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Check authentication status
|
||||
router.get('/me', async (req, res) => {
|
||||
try {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Not authenticated'
|
||||
});
|
||||
}
|
||||
|
||||
const user = await User.findByPk(req.session.user.id, {
|
||||
attributes: { exclude: ['password'] }
|
||||
});
|
||||
|
||||
if (!user || !user.isActive) {
|
||||
req.session.destroy();
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'User not found or inactive'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
user: {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
avatar: user.avatar,
|
||||
lastLogin: user.lastLogin
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Auth check error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Change password
|
||||
router.put('/change-password', [
|
||||
body('currentPassword').isLength({ min: 6 }),
|
||||
body('newPassword').isLength({ min: 6 })
|
||||
], async (req, res) => {
|
||||
try {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Not authenticated'
|
||||
});
|
||||
}
|
||||
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
const user = await User.findByPk(req.session.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'User not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify current password
|
||||
const isValidPassword = await user.comparePassword(currentPassword);
|
||||
if (!isValidPassword) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Current password is incorrect'
|
||||
});
|
||||
}
|
||||
|
||||
// Update password
|
||||
user.password = newPassword;
|
||||
await user.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Password updated successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Change password error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
207
.history/routes/auth_20251019204806.js
Normal file
207
.history/routes/auth_20251019204806.js
Normal file
@@ -0,0 +1,207 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const { User } = require('../models');
|
||||
|
||||
// Login validation rules
|
||||
const loginValidation = [
|
||||
body('email').isEmail().normalizeEmail(),
|
||||
body('password').isLength({ min: 6 })
|
||||
];
|
||||
|
||||
// Login
|
||||
router.post('/login', loginValidation, async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { email, password } = req.body;
|
||||
|
||||
// Find user
|
||||
const user = await User.findOne({
|
||||
where: {
|
||||
email: email,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
if (!user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
// Check password
|
||||
const isValidPassword = await user.comparePassword(password);
|
||||
if (!isValidPassword) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Invalid credentials'
|
||||
});
|
||||
}
|
||||
|
||||
// Update last login
|
||||
await user.updateLastLogin();
|
||||
|
||||
// Create JWT token
|
||||
const token = jwt.sign(
|
||||
{ userId: user._id, email: user.email, role: user.role },
|
||||
process.env.JWT_SECRET,
|
||||
{ expiresIn: '7d' }
|
||||
);
|
||||
|
||||
// Set session
|
||||
req.session.user = {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Login successful',
|
||||
token,
|
||||
user: {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
avatar: user.avatar
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Login error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Logout
|
||||
router.post('/logout', (req, res) => {
|
||||
req.session.destroy(err => {
|
||||
if (err) {
|
||||
return res.status(500).json({
|
||||
success: false,
|
||||
message: 'Could not log out'
|
||||
});
|
||||
}
|
||||
|
||||
res.clearCookie('connect.sid');
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Logout successful'
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// Check authentication status
|
||||
router.get('/me', async (req, res) => {
|
||||
try {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Not authenticated'
|
||||
});
|
||||
}
|
||||
|
||||
const user = await User.findByPk(req.session.user.id, {
|
||||
attributes: { exclude: ['password'] }
|
||||
});
|
||||
|
||||
if (!user || !user.isActive) {
|
||||
req.session.destroy();
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'User not found or inactive'
|
||||
});
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
user: {
|
||||
id: user._id,
|
||||
email: user.email,
|
||||
name: user.name,
|
||||
role: user.role,
|
||||
avatar: user.avatar,
|
||||
lastLogin: user.lastLogin
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Auth check error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Change password
|
||||
router.put('/change-password', [
|
||||
body('currentPassword').isLength({ min: 6 }),
|
||||
body('newPassword').isLength({ min: 6 })
|
||||
], async (req, res) => {
|
||||
try {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Not authenticated'
|
||||
});
|
||||
}
|
||||
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { currentPassword, newPassword } = req.body;
|
||||
const user = await User.findByPk(req.session.user.id);
|
||||
|
||||
if (!user) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'User not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Verify current password
|
||||
const isValidPassword = await user.comparePassword(currentPassword);
|
||||
if (!isValidPassword) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Current password is incorrect'
|
||||
});
|
||||
}
|
||||
|
||||
// Update password
|
||||
user.password = newPassword;
|
||||
await user.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Password updated successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Change password error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Server error'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
312
.history/routes/calculator_20251019204545.js
Normal file
312
.history/routes/calculator_20251019204545.js
Normal file
@@ -0,0 +1,312 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Service } = require('../models');
|
||||
|
||||
// Get all services for calculator
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.select('name pricing category features estimatedTime')
|
||||
.sort({ category: 1, name: 1 });
|
||||
|
||||
const servicesByCategory = services.reduce((acc, service) => {
|
||||
if (!acc[service.category]) {
|
||||
acc[service.category] = [];
|
||||
}
|
||||
acc[service.category].push(service);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
services: servicesByCategory,
|
||||
allServices: services
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator services error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate project estimate
|
||||
router.post('/calculate', async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
selectedServices = [],
|
||||
projectComplexity = 'medium',
|
||||
timeline = 'standard',
|
||||
additionalFeatures = [],
|
||||
customRequirements = ''
|
||||
} = req.body;
|
||||
|
||||
if (!selectedServices.length) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Please select at least one service'
|
||||
});
|
||||
}
|
||||
|
||||
// Get selected services details
|
||||
const services = await Service.find({
|
||||
_id: { $in: selectedServices },
|
||||
isActive: true
|
||||
});
|
||||
|
||||
if (services.length !== selectedServices.length) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Some selected services are not available'
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate base cost
|
||||
let baseCost = 0;
|
||||
let totalTime = 0; // in days
|
||||
|
||||
services.forEach(service => {
|
||||
baseCost += service.pricing.basePrice;
|
||||
totalTime += (service.estimatedTime.min + service.estimatedTime.max) / 2;
|
||||
});
|
||||
|
||||
// Apply complexity multiplier
|
||||
const complexityMultipliers = {
|
||||
'simple': 0.7,
|
||||
'medium': 1.0,
|
||||
'complex': 1.5,
|
||||
'enterprise': 2.0
|
||||
};
|
||||
|
||||
const complexityMultiplier = complexityMultipliers[projectComplexity] || 1.0;
|
||||
|
||||
// Apply timeline multiplier
|
||||
const timelineMultipliers = {
|
||||
'rush': 1.8, // Less than 2 weeks
|
||||
'fast': 1.4, // 2-4 weeks
|
||||
'standard': 1.0, // 1-3 months
|
||||
'flexible': 0.8 // 3+ months
|
||||
};
|
||||
|
||||
const timelineMultiplier = timelineMultipliers[timeline] || 1.0;
|
||||
|
||||
// Additional features cost
|
||||
const featureCosts = {
|
||||
'seo-optimization': 300000,
|
||||
'analytics-setup': 150000,
|
||||
'social-integration': 200000,
|
||||
'payment-gateway': 500000,
|
||||
'multilingual': 400000,
|
||||
'admin-panel': 600000,
|
||||
'api-integration': 350000,
|
||||
'mobile-responsive': 250000,
|
||||
'ssl-certificate': 100000,
|
||||
'backup-system': 200000
|
||||
};
|
||||
|
||||
let additionalCost = 0;
|
||||
additionalFeatures.forEach(feature => {
|
||||
additionalCost += featureCosts[feature] || 0;
|
||||
});
|
||||
|
||||
// Custom requirements cost (estimated based on description length and complexity)
|
||||
let customCost = 0;
|
||||
if (customRequirements && customRequirements.length > 50) {
|
||||
customCost = Math.min(customRequirements.length * 1000, 1000000); // Max 1M KRW
|
||||
}
|
||||
|
||||
// Calculate final estimate
|
||||
const subtotal = baseCost * complexityMultiplier * timelineMultiplier;
|
||||
const total = subtotal + additionalCost + customCost;
|
||||
|
||||
// Calculate time estimate
|
||||
const timeMultiplier = complexityMultiplier * timelineMultiplier;
|
||||
const estimatedDays = Math.ceil(totalTime * timeMultiplier);
|
||||
const estimatedWeeks = Math.ceil(estimatedDays / 7);
|
||||
|
||||
// Create price ranges (±20%)
|
||||
const minPrice = Math.round(total * 0.8);
|
||||
const maxPrice = Math.round(total * 1.2);
|
||||
|
||||
// Breakdown
|
||||
const breakdown = {
|
||||
baseServices: Math.round(baseCost),
|
||||
complexityAdjustment: Math.round(baseCost * (complexityMultiplier - 1)),
|
||||
timelineAdjustment: Math.round(baseCost * complexityMultiplier * (timelineMultiplier - 1)),
|
||||
additionalFeatures: additionalCost,
|
||||
customRequirements: customCost,
|
||||
subtotal: Math.round(subtotal),
|
||||
total: Math.round(total)
|
||||
};
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
estimate: {
|
||||
total: Math.round(total),
|
||||
range: {
|
||||
min: minPrice,
|
||||
max: maxPrice,
|
||||
formatted: `₩${minPrice.toLocaleString()} - ₩${maxPrice.toLocaleString()}`
|
||||
},
|
||||
breakdown,
|
||||
timeline: {
|
||||
days: estimatedDays,
|
||||
weeks: estimatedWeeks,
|
||||
formatted: estimatedWeeks === 1 ? '1 week' : `${estimatedWeeks} weeks`
|
||||
},
|
||||
currency: 'KRW',
|
||||
confidence: calculateConfidence(services.length, projectComplexity, customRequirements),
|
||||
recommendations: generateRecommendations(projectComplexity, timeline, services)
|
||||
},
|
||||
selectedServices: services.map(s => ({
|
||||
id: s._id,
|
||||
name: s.name,
|
||||
category: s.category,
|
||||
basePrice: s.pricing.basePrice
|
||||
})),
|
||||
calculatedAt: new Date()
|
||||
};
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Calculator calculation error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error calculating estimate'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to calculate confidence level
|
||||
function calculateConfidence(serviceCount, complexity, customRequirements) {
|
||||
let confidence = 85; // Base confidence
|
||||
|
||||
// Adjust based on service count
|
||||
if (serviceCount === 1) confidence += 10;
|
||||
else if (serviceCount > 5) confidence -= 10;
|
||||
|
||||
// Adjust based on complexity
|
||||
if (complexity === 'simple') confidence += 10;
|
||||
else if (complexity === 'enterprise') confidence -= 15;
|
||||
|
||||
// Adjust based on custom requirements
|
||||
if (customRequirements && customRequirements.length > 200) {
|
||||
confidence -= 20;
|
||||
}
|
||||
|
||||
return Math.max(60, Math.min(95, confidence));
|
||||
}
|
||||
|
||||
// Helper function to generate recommendations
|
||||
function generateRecommendations(complexity, timeline, services) {
|
||||
const recommendations = [];
|
||||
|
||||
if (complexity === 'enterprise' && timeline === 'rush') {
|
||||
recommendations.push('Consider extending timeline for enterprise-level projects to ensure quality');
|
||||
}
|
||||
|
||||
if (services.length > 6) {
|
||||
recommendations.push('Consider breaking down into phases for better project management');
|
||||
}
|
||||
|
||||
if (timeline === 'flexible') {
|
||||
recommendations.push('Flexible timeline allows for better cost optimization and quality assurance');
|
||||
}
|
||||
|
||||
const hasDesign = services.some(s => s.category === 'design');
|
||||
const hasDevelopment = services.some(s => s.category === 'development');
|
||||
|
||||
if (hasDevelopment && !hasDesign) {
|
||||
recommendations.push('Consider adding UI/UX design services for better user experience');
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
// Get pricing guidelines
|
||||
router.get('/pricing-guide', async (req, res) => {
|
||||
try {
|
||||
const pricingGuide = {
|
||||
serviceCategories: {
|
||||
'development': {
|
||||
name: 'Web Development',
|
||||
priceRange: '₩500,000 - ₩5,000,000',
|
||||
description: 'Custom web applications and websites'
|
||||
},
|
||||
'design': {
|
||||
name: 'UI/UX Design',
|
||||
priceRange: '₩300,000 - ₩2,000,000',
|
||||
description: 'User interface and experience design'
|
||||
},
|
||||
'marketing': {
|
||||
name: 'Digital Marketing',
|
||||
priceRange: '₩200,000 - ₩1,500,000',
|
||||
description: 'SEO, social media, and online marketing'
|
||||
},
|
||||
'consulting': {
|
||||
name: 'Technical Consulting',
|
||||
priceRange: '₩150,000 - ₩1,000,000',
|
||||
description: 'Technology strategy and consultation'
|
||||
}
|
||||
},
|
||||
complexityFactors: {
|
||||
'simple': {
|
||||
name: 'Simple Project',
|
||||
multiplier: '0.7x',
|
||||
description: 'Basic functionality, standard design'
|
||||
},
|
||||
'medium': {
|
||||
name: 'Medium Project',
|
||||
multiplier: '1.0x',
|
||||
description: 'Moderate complexity, custom features'
|
||||
},
|
||||
'complex': {
|
||||
name: 'Complex Project',
|
||||
multiplier: '1.5x',
|
||||
description: 'Advanced features, integrations'
|
||||
},
|
||||
'enterprise': {
|
||||
name: 'Enterprise Project',
|
||||
multiplier: '2.0x',
|
||||
description: 'Large scale, high complexity'
|
||||
}
|
||||
},
|
||||
timelineImpact: {
|
||||
'rush': {
|
||||
name: 'Rush (< 2 weeks)',
|
||||
multiplier: '+80%',
|
||||
description: 'Requires overtime and priority handling'
|
||||
},
|
||||
'fast': {
|
||||
name: 'Fast (2-4 weeks)',
|
||||
multiplier: '+40%',
|
||||
description: 'Accelerated timeline'
|
||||
},
|
||||
'standard': {
|
||||
name: 'Standard (1-3 months)',
|
||||
multiplier: 'Standard',
|
||||
description: 'Normal project timeline'
|
||||
},
|
||||
'flexible': {
|
||||
name: 'Flexible (3+ months)',
|
||||
multiplier: '-20%',
|
||||
description: 'Extended timeline allows optimization'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
pricingGuide
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Pricing guide error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching pricing guide'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
314
.history/routes/calculator_20251019204551.js
Normal file
314
.history/routes/calculator_20251019204551.js
Normal file
@@ -0,0 +1,314 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Service } = require('../models');
|
||||
|
||||
// Get all services for calculator
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category', 'features', 'estimatedTime'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
});
|
||||
|
||||
const servicesByCategory = services.reduce((acc, service) => {
|
||||
if (!acc[service.category]) {
|
||||
acc[service.category] = [];
|
||||
}
|
||||
acc[service.category].push(service);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
services: servicesByCategory,
|
||||
allServices: services
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator services error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate project estimate
|
||||
router.post('/calculate', async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
selectedServices = [],
|
||||
projectComplexity = 'medium',
|
||||
timeline = 'standard',
|
||||
additionalFeatures = [],
|
||||
customRequirements = ''
|
||||
} = req.body;
|
||||
|
||||
if (!selectedServices.length) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Please select at least one service'
|
||||
});
|
||||
}
|
||||
|
||||
// Get selected services details
|
||||
const services = await Service.find({
|
||||
_id: { $in: selectedServices },
|
||||
isActive: true
|
||||
});
|
||||
|
||||
if (services.length !== selectedServices.length) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Some selected services are not available'
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate base cost
|
||||
let baseCost = 0;
|
||||
let totalTime = 0; // in days
|
||||
|
||||
services.forEach(service => {
|
||||
baseCost += service.pricing.basePrice;
|
||||
totalTime += (service.estimatedTime.min + service.estimatedTime.max) / 2;
|
||||
});
|
||||
|
||||
// Apply complexity multiplier
|
||||
const complexityMultipliers = {
|
||||
'simple': 0.7,
|
||||
'medium': 1.0,
|
||||
'complex': 1.5,
|
||||
'enterprise': 2.0
|
||||
};
|
||||
|
||||
const complexityMultiplier = complexityMultipliers[projectComplexity] || 1.0;
|
||||
|
||||
// Apply timeline multiplier
|
||||
const timelineMultipliers = {
|
||||
'rush': 1.8, // Less than 2 weeks
|
||||
'fast': 1.4, // 2-4 weeks
|
||||
'standard': 1.0, // 1-3 months
|
||||
'flexible': 0.8 // 3+ months
|
||||
};
|
||||
|
||||
const timelineMultiplier = timelineMultipliers[timeline] || 1.0;
|
||||
|
||||
// Additional features cost
|
||||
const featureCosts = {
|
||||
'seo-optimization': 300000,
|
||||
'analytics-setup': 150000,
|
||||
'social-integration': 200000,
|
||||
'payment-gateway': 500000,
|
||||
'multilingual': 400000,
|
||||
'admin-panel': 600000,
|
||||
'api-integration': 350000,
|
||||
'mobile-responsive': 250000,
|
||||
'ssl-certificate': 100000,
|
||||
'backup-system': 200000
|
||||
};
|
||||
|
||||
let additionalCost = 0;
|
||||
additionalFeatures.forEach(feature => {
|
||||
additionalCost += featureCosts[feature] || 0;
|
||||
});
|
||||
|
||||
// Custom requirements cost (estimated based on description length and complexity)
|
||||
let customCost = 0;
|
||||
if (customRequirements && customRequirements.length > 50) {
|
||||
customCost = Math.min(customRequirements.length * 1000, 1000000); // Max 1M KRW
|
||||
}
|
||||
|
||||
// Calculate final estimate
|
||||
const subtotal = baseCost * complexityMultiplier * timelineMultiplier;
|
||||
const total = subtotal + additionalCost + customCost;
|
||||
|
||||
// Calculate time estimate
|
||||
const timeMultiplier = complexityMultiplier * timelineMultiplier;
|
||||
const estimatedDays = Math.ceil(totalTime * timeMultiplier);
|
||||
const estimatedWeeks = Math.ceil(estimatedDays / 7);
|
||||
|
||||
// Create price ranges (±20%)
|
||||
const minPrice = Math.round(total * 0.8);
|
||||
const maxPrice = Math.round(total * 1.2);
|
||||
|
||||
// Breakdown
|
||||
const breakdown = {
|
||||
baseServices: Math.round(baseCost),
|
||||
complexityAdjustment: Math.round(baseCost * (complexityMultiplier - 1)),
|
||||
timelineAdjustment: Math.round(baseCost * complexityMultiplier * (timelineMultiplier - 1)),
|
||||
additionalFeatures: additionalCost,
|
||||
customRequirements: customCost,
|
||||
subtotal: Math.round(subtotal),
|
||||
total: Math.round(total)
|
||||
};
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
estimate: {
|
||||
total: Math.round(total),
|
||||
range: {
|
||||
min: minPrice,
|
||||
max: maxPrice,
|
||||
formatted: `₩${minPrice.toLocaleString()} - ₩${maxPrice.toLocaleString()}`
|
||||
},
|
||||
breakdown,
|
||||
timeline: {
|
||||
days: estimatedDays,
|
||||
weeks: estimatedWeeks,
|
||||
formatted: estimatedWeeks === 1 ? '1 week' : `${estimatedWeeks} weeks`
|
||||
},
|
||||
currency: 'KRW',
|
||||
confidence: calculateConfidence(services.length, projectComplexity, customRequirements),
|
||||
recommendations: generateRecommendations(projectComplexity, timeline, services)
|
||||
},
|
||||
selectedServices: services.map(s => ({
|
||||
id: s._id,
|
||||
name: s.name,
|
||||
category: s.category,
|
||||
basePrice: s.pricing.basePrice
|
||||
})),
|
||||
calculatedAt: new Date()
|
||||
};
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Calculator calculation error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error calculating estimate'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to calculate confidence level
|
||||
function calculateConfidence(serviceCount, complexity, customRequirements) {
|
||||
let confidence = 85; // Base confidence
|
||||
|
||||
// Adjust based on service count
|
||||
if (serviceCount === 1) confidence += 10;
|
||||
else if (serviceCount > 5) confidence -= 10;
|
||||
|
||||
// Adjust based on complexity
|
||||
if (complexity === 'simple') confidence += 10;
|
||||
else if (complexity === 'enterprise') confidence -= 15;
|
||||
|
||||
// Adjust based on custom requirements
|
||||
if (customRequirements && customRequirements.length > 200) {
|
||||
confidence -= 20;
|
||||
}
|
||||
|
||||
return Math.max(60, Math.min(95, confidence));
|
||||
}
|
||||
|
||||
// Helper function to generate recommendations
|
||||
function generateRecommendations(complexity, timeline, services) {
|
||||
const recommendations = [];
|
||||
|
||||
if (complexity === 'enterprise' && timeline === 'rush') {
|
||||
recommendations.push('Consider extending timeline for enterprise-level projects to ensure quality');
|
||||
}
|
||||
|
||||
if (services.length > 6) {
|
||||
recommendations.push('Consider breaking down into phases for better project management');
|
||||
}
|
||||
|
||||
if (timeline === 'flexible') {
|
||||
recommendations.push('Flexible timeline allows for better cost optimization and quality assurance');
|
||||
}
|
||||
|
||||
const hasDesign = services.some(s => s.category === 'design');
|
||||
const hasDevelopment = services.some(s => s.category === 'development');
|
||||
|
||||
if (hasDevelopment && !hasDesign) {
|
||||
recommendations.push('Consider adding UI/UX design services for better user experience');
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
// Get pricing guidelines
|
||||
router.get('/pricing-guide', async (req, res) => {
|
||||
try {
|
||||
const pricingGuide = {
|
||||
serviceCategories: {
|
||||
'development': {
|
||||
name: 'Web Development',
|
||||
priceRange: '₩500,000 - ₩5,000,000',
|
||||
description: 'Custom web applications and websites'
|
||||
},
|
||||
'design': {
|
||||
name: 'UI/UX Design',
|
||||
priceRange: '₩300,000 - ₩2,000,000',
|
||||
description: 'User interface and experience design'
|
||||
},
|
||||
'marketing': {
|
||||
name: 'Digital Marketing',
|
||||
priceRange: '₩200,000 - ₩1,500,000',
|
||||
description: 'SEO, social media, and online marketing'
|
||||
},
|
||||
'consulting': {
|
||||
name: 'Technical Consulting',
|
||||
priceRange: '₩150,000 - ₩1,000,000',
|
||||
description: 'Technology strategy and consultation'
|
||||
}
|
||||
},
|
||||
complexityFactors: {
|
||||
'simple': {
|
||||
name: 'Simple Project',
|
||||
multiplier: '0.7x',
|
||||
description: 'Basic functionality, standard design'
|
||||
},
|
||||
'medium': {
|
||||
name: 'Medium Project',
|
||||
multiplier: '1.0x',
|
||||
description: 'Moderate complexity, custom features'
|
||||
},
|
||||
'complex': {
|
||||
name: 'Complex Project',
|
||||
multiplier: '1.5x',
|
||||
description: 'Advanced features, integrations'
|
||||
},
|
||||
'enterprise': {
|
||||
name: 'Enterprise Project',
|
||||
multiplier: '2.0x',
|
||||
description: 'Large scale, high complexity'
|
||||
}
|
||||
},
|
||||
timelineImpact: {
|
||||
'rush': {
|
||||
name: 'Rush (< 2 weeks)',
|
||||
multiplier: '+80%',
|
||||
description: 'Requires overtime and priority handling'
|
||||
},
|
||||
'fast': {
|
||||
name: 'Fast (2-4 weeks)',
|
||||
multiplier: '+40%',
|
||||
description: 'Accelerated timeline'
|
||||
},
|
||||
'standard': {
|
||||
name: 'Standard (1-3 months)',
|
||||
multiplier: 'Standard',
|
||||
description: 'Normal project timeline'
|
||||
},
|
||||
'flexible': {
|
||||
name: 'Flexible (3+ months)',
|
||||
multiplier: '-20%',
|
||||
description: 'Extended timeline allows optimization'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
pricingGuide
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Pricing guide error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching pricing guide'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
316
.history/routes/calculator_20251019204602.js
Normal file
316
.history/routes/calculator_20251019204602.js
Normal file
@@ -0,0 +1,316 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Service } = require('../models');
|
||||
|
||||
// Get all services for calculator
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category', 'features', 'estimatedTime'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
});
|
||||
|
||||
const servicesByCategory = services.reduce((acc, service) => {
|
||||
if (!acc[service.category]) {
|
||||
acc[service.category] = [];
|
||||
}
|
||||
acc[service.category].push(service);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
services: servicesByCategory,
|
||||
allServices: services
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator services error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate project estimate
|
||||
router.post('/calculate', async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
selectedServices = [],
|
||||
projectComplexity = 'medium',
|
||||
timeline = 'standard',
|
||||
additionalFeatures = [],
|
||||
customRequirements = ''
|
||||
} = req.body;
|
||||
|
||||
if (!selectedServices.length) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Please select at least one service'
|
||||
});
|
||||
}
|
||||
|
||||
// Get selected services details
|
||||
const services = await Service.findAll({
|
||||
where: {
|
||||
id: selectedServices,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
|
||||
if (services.length !== selectedServices.length) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Some selected services are not available'
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate base cost
|
||||
let baseCost = 0;
|
||||
let totalTime = 0; // in days
|
||||
|
||||
services.forEach(service => {
|
||||
baseCost += service.pricing.basePrice;
|
||||
totalTime += (service.estimatedTime.min + service.estimatedTime.max) / 2;
|
||||
});
|
||||
|
||||
// Apply complexity multiplier
|
||||
const complexityMultipliers = {
|
||||
'simple': 0.7,
|
||||
'medium': 1.0,
|
||||
'complex': 1.5,
|
||||
'enterprise': 2.0
|
||||
};
|
||||
|
||||
const complexityMultiplier = complexityMultipliers[projectComplexity] || 1.0;
|
||||
|
||||
// Apply timeline multiplier
|
||||
const timelineMultipliers = {
|
||||
'rush': 1.8, // Less than 2 weeks
|
||||
'fast': 1.4, // 2-4 weeks
|
||||
'standard': 1.0, // 1-3 months
|
||||
'flexible': 0.8 // 3+ months
|
||||
};
|
||||
|
||||
const timelineMultiplier = timelineMultipliers[timeline] || 1.0;
|
||||
|
||||
// Additional features cost
|
||||
const featureCosts = {
|
||||
'seo-optimization': 300000,
|
||||
'analytics-setup': 150000,
|
||||
'social-integration': 200000,
|
||||
'payment-gateway': 500000,
|
||||
'multilingual': 400000,
|
||||
'admin-panel': 600000,
|
||||
'api-integration': 350000,
|
||||
'mobile-responsive': 250000,
|
||||
'ssl-certificate': 100000,
|
||||
'backup-system': 200000
|
||||
};
|
||||
|
||||
let additionalCost = 0;
|
||||
additionalFeatures.forEach(feature => {
|
||||
additionalCost += featureCosts[feature] || 0;
|
||||
});
|
||||
|
||||
// Custom requirements cost (estimated based on description length and complexity)
|
||||
let customCost = 0;
|
||||
if (customRequirements && customRequirements.length > 50) {
|
||||
customCost = Math.min(customRequirements.length * 1000, 1000000); // Max 1M KRW
|
||||
}
|
||||
|
||||
// Calculate final estimate
|
||||
const subtotal = baseCost * complexityMultiplier * timelineMultiplier;
|
||||
const total = subtotal + additionalCost + customCost;
|
||||
|
||||
// Calculate time estimate
|
||||
const timeMultiplier = complexityMultiplier * timelineMultiplier;
|
||||
const estimatedDays = Math.ceil(totalTime * timeMultiplier);
|
||||
const estimatedWeeks = Math.ceil(estimatedDays / 7);
|
||||
|
||||
// Create price ranges (±20%)
|
||||
const minPrice = Math.round(total * 0.8);
|
||||
const maxPrice = Math.round(total * 1.2);
|
||||
|
||||
// Breakdown
|
||||
const breakdown = {
|
||||
baseServices: Math.round(baseCost),
|
||||
complexityAdjustment: Math.round(baseCost * (complexityMultiplier - 1)),
|
||||
timelineAdjustment: Math.round(baseCost * complexityMultiplier * (timelineMultiplier - 1)),
|
||||
additionalFeatures: additionalCost,
|
||||
customRequirements: customCost,
|
||||
subtotal: Math.round(subtotal),
|
||||
total: Math.round(total)
|
||||
};
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
estimate: {
|
||||
total: Math.round(total),
|
||||
range: {
|
||||
min: minPrice,
|
||||
max: maxPrice,
|
||||
formatted: `₩${minPrice.toLocaleString()} - ₩${maxPrice.toLocaleString()}`
|
||||
},
|
||||
breakdown,
|
||||
timeline: {
|
||||
days: estimatedDays,
|
||||
weeks: estimatedWeeks,
|
||||
formatted: estimatedWeeks === 1 ? '1 week' : `${estimatedWeeks} weeks`
|
||||
},
|
||||
currency: 'KRW',
|
||||
confidence: calculateConfidence(services.length, projectComplexity, customRequirements),
|
||||
recommendations: generateRecommendations(projectComplexity, timeline, services)
|
||||
},
|
||||
selectedServices: services.map(s => ({
|
||||
id: s._id,
|
||||
name: s.name,
|
||||
category: s.category,
|
||||
basePrice: s.pricing.basePrice
|
||||
})),
|
||||
calculatedAt: new Date()
|
||||
};
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Calculator calculation error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error calculating estimate'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to calculate confidence level
|
||||
function calculateConfidence(serviceCount, complexity, customRequirements) {
|
||||
let confidence = 85; // Base confidence
|
||||
|
||||
// Adjust based on service count
|
||||
if (serviceCount === 1) confidence += 10;
|
||||
else if (serviceCount > 5) confidence -= 10;
|
||||
|
||||
// Adjust based on complexity
|
||||
if (complexity === 'simple') confidence += 10;
|
||||
else if (complexity === 'enterprise') confidence -= 15;
|
||||
|
||||
// Adjust based on custom requirements
|
||||
if (customRequirements && customRequirements.length > 200) {
|
||||
confidence -= 20;
|
||||
}
|
||||
|
||||
return Math.max(60, Math.min(95, confidence));
|
||||
}
|
||||
|
||||
// Helper function to generate recommendations
|
||||
function generateRecommendations(complexity, timeline, services) {
|
||||
const recommendations = [];
|
||||
|
||||
if (complexity === 'enterprise' && timeline === 'rush') {
|
||||
recommendations.push('Consider extending timeline for enterprise-level projects to ensure quality');
|
||||
}
|
||||
|
||||
if (services.length > 6) {
|
||||
recommendations.push('Consider breaking down into phases for better project management');
|
||||
}
|
||||
|
||||
if (timeline === 'flexible') {
|
||||
recommendations.push('Flexible timeline allows for better cost optimization and quality assurance');
|
||||
}
|
||||
|
||||
const hasDesign = services.some(s => s.category === 'design');
|
||||
const hasDevelopment = services.some(s => s.category === 'development');
|
||||
|
||||
if (hasDevelopment && !hasDesign) {
|
||||
recommendations.push('Consider adding UI/UX design services for better user experience');
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
// Get pricing guidelines
|
||||
router.get('/pricing-guide', async (req, res) => {
|
||||
try {
|
||||
const pricingGuide = {
|
||||
serviceCategories: {
|
||||
'development': {
|
||||
name: 'Web Development',
|
||||
priceRange: '₩500,000 - ₩5,000,000',
|
||||
description: 'Custom web applications and websites'
|
||||
},
|
||||
'design': {
|
||||
name: 'UI/UX Design',
|
||||
priceRange: '₩300,000 - ₩2,000,000',
|
||||
description: 'User interface and experience design'
|
||||
},
|
||||
'marketing': {
|
||||
name: 'Digital Marketing',
|
||||
priceRange: '₩200,000 - ₩1,500,000',
|
||||
description: 'SEO, social media, and online marketing'
|
||||
},
|
||||
'consulting': {
|
||||
name: 'Technical Consulting',
|
||||
priceRange: '₩150,000 - ₩1,000,000',
|
||||
description: 'Technology strategy and consultation'
|
||||
}
|
||||
},
|
||||
complexityFactors: {
|
||||
'simple': {
|
||||
name: 'Simple Project',
|
||||
multiplier: '0.7x',
|
||||
description: 'Basic functionality, standard design'
|
||||
},
|
||||
'medium': {
|
||||
name: 'Medium Project',
|
||||
multiplier: '1.0x',
|
||||
description: 'Moderate complexity, custom features'
|
||||
},
|
||||
'complex': {
|
||||
name: 'Complex Project',
|
||||
multiplier: '1.5x',
|
||||
description: 'Advanced features, integrations'
|
||||
},
|
||||
'enterprise': {
|
||||
name: 'Enterprise Project',
|
||||
multiplier: '2.0x',
|
||||
description: 'Large scale, high complexity'
|
||||
}
|
||||
},
|
||||
timelineImpact: {
|
||||
'rush': {
|
||||
name: 'Rush (< 2 weeks)',
|
||||
multiplier: '+80%',
|
||||
description: 'Requires overtime and priority handling'
|
||||
},
|
||||
'fast': {
|
||||
name: 'Fast (2-4 weeks)',
|
||||
multiplier: '+40%',
|
||||
description: 'Accelerated timeline'
|
||||
},
|
||||
'standard': {
|
||||
name: 'Standard (1-3 months)',
|
||||
multiplier: 'Standard',
|
||||
description: 'Normal project timeline'
|
||||
},
|
||||
'flexible': {
|
||||
name: 'Flexible (3+ months)',
|
||||
multiplier: '-20%',
|
||||
description: 'Extended timeline allows optimization'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
pricingGuide
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Pricing guide error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching pricing guide'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
316
.history/routes/calculator_20251019204805.js
Normal file
316
.history/routes/calculator_20251019204805.js
Normal file
@@ -0,0 +1,316 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Service } = require('../models');
|
||||
|
||||
// Get all services for calculator
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category', 'features', 'estimatedTime'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
});
|
||||
|
||||
const servicesByCategory = services.reduce((acc, service) => {
|
||||
if (!acc[service.category]) {
|
||||
acc[service.category] = [];
|
||||
}
|
||||
acc[service.category].push(service);
|
||||
return acc;
|
||||
}, {});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
services: servicesByCategory,
|
||||
allServices: services
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator services error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculate project estimate
|
||||
router.post('/calculate', async (req, res) => {
|
||||
try {
|
||||
const {
|
||||
selectedServices = [],
|
||||
projectComplexity = 'medium',
|
||||
timeline = 'standard',
|
||||
additionalFeatures = [],
|
||||
customRequirements = ''
|
||||
} = req.body;
|
||||
|
||||
if (!selectedServices.length) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Please select at least one service'
|
||||
});
|
||||
}
|
||||
|
||||
// Get selected services details
|
||||
const services = await Service.findAll({
|
||||
where: {
|
||||
id: selectedServices,
|
||||
isActive: true
|
||||
}
|
||||
});
|
||||
|
||||
if (services.length !== selectedServices.length) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Some selected services are not available'
|
||||
});
|
||||
}
|
||||
|
||||
// Calculate base cost
|
||||
let baseCost = 0;
|
||||
let totalTime = 0; // in days
|
||||
|
||||
services.forEach(service => {
|
||||
baseCost += service.pricing.basePrice;
|
||||
totalTime += (service.estimatedTime.min + service.estimatedTime.max) / 2;
|
||||
});
|
||||
|
||||
// Apply complexity multiplier
|
||||
const complexityMultipliers = {
|
||||
'simple': 0.7,
|
||||
'medium': 1.0,
|
||||
'complex': 1.5,
|
||||
'enterprise': 2.0
|
||||
};
|
||||
|
||||
const complexityMultiplier = complexityMultipliers[projectComplexity] || 1.0;
|
||||
|
||||
// Apply timeline multiplier
|
||||
const timelineMultipliers = {
|
||||
'rush': 1.8, // Less than 2 weeks
|
||||
'fast': 1.4, // 2-4 weeks
|
||||
'standard': 1.0, // 1-3 months
|
||||
'flexible': 0.8 // 3+ months
|
||||
};
|
||||
|
||||
const timelineMultiplier = timelineMultipliers[timeline] || 1.0;
|
||||
|
||||
// Additional features cost
|
||||
const featureCosts = {
|
||||
'seo-optimization': 300000,
|
||||
'analytics-setup': 150000,
|
||||
'social-integration': 200000,
|
||||
'payment-gateway': 500000,
|
||||
'multilingual': 400000,
|
||||
'admin-panel': 600000,
|
||||
'api-integration': 350000,
|
||||
'mobile-responsive': 250000,
|
||||
'ssl-certificate': 100000,
|
||||
'backup-system': 200000
|
||||
};
|
||||
|
||||
let additionalCost = 0;
|
||||
additionalFeatures.forEach(feature => {
|
||||
additionalCost += featureCosts[feature] || 0;
|
||||
});
|
||||
|
||||
// Custom requirements cost (estimated based on description length and complexity)
|
||||
let customCost = 0;
|
||||
if (customRequirements && customRequirements.length > 50) {
|
||||
customCost = Math.min(customRequirements.length * 1000, 1000000); // Max 1M KRW
|
||||
}
|
||||
|
||||
// Calculate final estimate
|
||||
const subtotal = baseCost * complexityMultiplier * timelineMultiplier;
|
||||
const total = subtotal + additionalCost + customCost;
|
||||
|
||||
// Calculate time estimate
|
||||
const timeMultiplier = complexityMultiplier * timelineMultiplier;
|
||||
const estimatedDays = Math.ceil(totalTime * timeMultiplier);
|
||||
const estimatedWeeks = Math.ceil(estimatedDays / 7);
|
||||
|
||||
// Create price ranges (±20%)
|
||||
const minPrice = Math.round(total * 0.8);
|
||||
const maxPrice = Math.round(total * 1.2);
|
||||
|
||||
// Breakdown
|
||||
const breakdown = {
|
||||
baseServices: Math.round(baseCost),
|
||||
complexityAdjustment: Math.round(baseCost * (complexityMultiplier - 1)),
|
||||
timelineAdjustment: Math.round(baseCost * complexityMultiplier * (timelineMultiplier - 1)),
|
||||
additionalFeatures: additionalCost,
|
||||
customRequirements: customCost,
|
||||
subtotal: Math.round(subtotal),
|
||||
total: Math.round(total)
|
||||
};
|
||||
|
||||
const result = {
|
||||
success: true,
|
||||
estimate: {
|
||||
total: Math.round(total),
|
||||
range: {
|
||||
min: minPrice,
|
||||
max: maxPrice,
|
||||
formatted: `₩${minPrice.toLocaleString()} - ₩${maxPrice.toLocaleString()}`
|
||||
},
|
||||
breakdown,
|
||||
timeline: {
|
||||
days: estimatedDays,
|
||||
weeks: estimatedWeeks,
|
||||
formatted: estimatedWeeks === 1 ? '1 week' : `${estimatedWeeks} weeks`
|
||||
},
|
||||
currency: 'KRW',
|
||||
confidence: calculateConfidence(services.length, projectComplexity, customRequirements),
|
||||
recommendations: generateRecommendations(projectComplexity, timeline, services)
|
||||
},
|
||||
selectedServices: services.map(s => ({
|
||||
id: s._id,
|
||||
name: s.name,
|
||||
category: s.category,
|
||||
basePrice: s.pricing.basePrice
|
||||
})),
|
||||
calculatedAt: new Date()
|
||||
};
|
||||
|
||||
res.json(result);
|
||||
} catch (error) {
|
||||
console.error('Calculator calculation error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error calculating estimate'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to calculate confidence level
|
||||
function calculateConfidence(serviceCount, complexity, customRequirements) {
|
||||
let confidence = 85; // Base confidence
|
||||
|
||||
// Adjust based on service count
|
||||
if (serviceCount === 1) confidence += 10;
|
||||
else if (serviceCount > 5) confidence -= 10;
|
||||
|
||||
// Adjust based on complexity
|
||||
if (complexity === 'simple') confidence += 10;
|
||||
else if (complexity === 'enterprise') confidence -= 15;
|
||||
|
||||
// Adjust based on custom requirements
|
||||
if (customRequirements && customRequirements.length > 200) {
|
||||
confidence -= 20;
|
||||
}
|
||||
|
||||
return Math.max(60, Math.min(95, confidence));
|
||||
}
|
||||
|
||||
// Helper function to generate recommendations
|
||||
function generateRecommendations(complexity, timeline, services) {
|
||||
const recommendations = [];
|
||||
|
||||
if (complexity === 'enterprise' && timeline === 'rush') {
|
||||
recommendations.push('Consider extending timeline for enterprise-level projects to ensure quality');
|
||||
}
|
||||
|
||||
if (services.length > 6) {
|
||||
recommendations.push('Consider breaking down into phases for better project management');
|
||||
}
|
||||
|
||||
if (timeline === 'flexible') {
|
||||
recommendations.push('Flexible timeline allows for better cost optimization and quality assurance');
|
||||
}
|
||||
|
||||
const hasDesign = services.some(s => s.category === 'design');
|
||||
const hasDevelopment = services.some(s => s.category === 'development');
|
||||
|
||||
if (hasDevelopment && !hasDesign) {
|
||||
recommendations.push('Consider adding UI/UX design services for better user experience');
|
||||
}
|
||||
|
||||
return recommendations;
|
||||
}
|
||||
|
||||
// Get pricing guidelines
|
||||
router.get('/pricing-guide', async (req, res) => {
|
||||
try {
|
||||
const pricingGuide = {
|
||||
serviceCategories: {
|
||||
'development': {
|
||||
name: 'Web Development',
|
||||
priceRange: '₩500,000 - ₩5,000,000',
|
||||
description: 'Custom web applications and websites'
|
||||
},
|
||||
'design': {
|
||||
name: 'UI/UX Design',
|
||||
priceRange: '₩300,000 - ₩2,000,000',
|
||||
description: 'User interface and experience design'
|
||||
},
|
||||
'marketing': {
|
||||
name: 'Digital Marketing',
|
||||
priceRange: '₩200,000 - ₩1,500,000',
|
||||
description: 'SEO, social media, and online marketing'
|
||||
},
|
||||
'consulting': {
|
||||
name: 'Technical Consulting',
|
||||
priceRange: '₩150,000 - ₩1,000,000',
|
||||
description: 'Technology strategy and consultation'
|
||||
}
|
||||
},
|
||||
complexityFactors: {
|
||||
'simple': {
|
||||
name: 'Simple Project',
|
||||
multiplier: '0.7x',
|
||||
description: 'Basic functionality, standard design'
|
||||
},
|
||||
'medium': {
|
||||
name: 'Medium Project',
|
||||
multiplier: '1.0x',
|
||||
description: 'Moderate complexity, custom features'
|
||||
},
|
||||
'complex': {
|
||||
name: 'Complex Project',
|
||||
multiplier: '1.5x',
|
||||
description: 'Advanced features, integrations'
|
||||
},
|
||||
'enterprise': {
|
||||
name: 'Enterprise Project',
|
||||
multiplier: '2.0x',
|
||||
description: 'Large scale, high complexity'
|
||||
}
|
||||
},
|
||||
timelineImpact: {
|
||||
'rush': {
|
||||
name: 'Rush (< 2 weeks)',
|
||||
multiplier: '+80%',
|
||||
description: 'Requires overtime and priority handling'
|
||||
},
|
||||
'fast': {
|
||||
name: 'Fast (2-4 weeks)',
|
||||
multiplier: '+40%',
|
||||
description: 'Accelerated timeline'
|
||||
},
|
||||
'standard': {
|
||||
name: 'Standard (1-3 months)',
|
||||
multiplier: 'Standard',
|
||||
description: 'Normal project timeline'
|
||||
},
|
||||
'flexible': {
|
||||
name: 'Flexible (3+ months)',
|
||||
multiplier: '-20%',
|
||||
description: 'Extended timeline allows optimization'
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
pricingGuide
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Pricing guide error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching pricing guide'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
250
.history/routes/contact_20251019204616.js
Normal file
250
.history/routes/contact_20251019204616.js
Normal file
@@ -0,0 +1,250 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const nodemailer = require('nodemailer');
|
||||
const { Contact } = require('../models');
|
||||
const TelegramBot = require('node-telegram-bot-api');
|
||||
|
||||
// Initialize Telegram bot if token is provided
|
||||
let bot = null;
|
||||
if (process.env.TELEGRAM_BOT_TOKEN) {
|
||||
bot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN, { polling: false });
|
||||
}
|
||||
|
||||
// Contact form validation
|
||||
const contactValidation = [
|
||||
body('name').trim().isLength({ min: 2, max: 100 }),
|
||||
body('email').isEmail().normalizeEmail(),
|
||||
body('subject').trim().isLength({ min: 5, max: 200 }),
|
||||
body('message').trim().isLength({ min: 10, max: 2000 }),
|
||||
body('phone').optional().isMobilePhone(),
|
||||
body('company').optional().trim().isLength({ max: 100 })
|
||||
];
|
||||
|
||||
// Submit contact form
|
||||
router.post('/submit', contactValidation, async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const contactData = {
|
||||
...req.body,
|
||||
ipAddress: req.ip,
|
||||
userAgent: req.get('User-Agent'),
|
||||
source: 'website'
|
||||
};
|
||||
|
||||
// Save to database
|
||||
const contact = new Contact(contactData);
|
||||
await contact.save();
|
||||
|
||||
// Send email notification
|
||||
await sendEmailNotification(contact);
|
||||
|
||||
// Send Telegram notification
|
||||
await sendTelegramNotification(contact);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Your message has been sent successfully. We will get back to you soon!',
|
||||
contactId: contact._id
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact form error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Sorry, there was an error sending your message. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get project estimate
|
||||
router.post('/estimate', [
|
||||
body('services').isArray().notEmpty(),
|
||||
body('projectType').notEmpty(),
|
||||
body('timeline').notEmpty(),
|
||||
body('budget').notEmpty(),
|
||||
body('description').trim().isLength({ min: 10, max: 1000 })
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { services, projectType, timeline, budget, description, contactInfo } = req.body;
|
||||
|
||||
// Calculate estimate (simplified)
|
||||
const estimate = calculateProjectEstimate(services, projectType, timeline);
|
||||
|
||||
// Save inquiry to database
|
||||
const contactData = {
|
||||
name: contactInfo.name,
|
||||
email: contactInfo.email,
|
||||
phone: contactInfo.phone,
|
||||
company: contactInfo.company,
|
||||
subject: `Project Estimate Request - ${projectType}`,
|
||||
message: `Project Description: ${description}\n\nServices: ${services.join(', ')}\nTimeline: ${timeline}\nBudget: ${budget}\n\nCalculated Estimate: ${estimate.formatted}`,
|
||||
serviceInterest: projectType,
|
||||
budget: budget,
|
||||
timeline: timeline,
|
||||
source: 'calculator',
|
||||
ipAddress: req.ip,
|
||||
userAgent: req.get('User-Agent')
|
||||
};
|
||||
|
||||
const contact = new Contact(contactData);
|
||||
await contact.save();
|
||||
|
||||
// Send notifications
|
||||
await sendEmailNotification(contact);
|
||||
await sendTelegramNotification(contact);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Your project estimate request has been submitted successfully!',
|
||||
estimate: estimate,
|
||||
contactId: contact._id
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Estimate request error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Sorry, there was an error processing your request. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to send email notification
|
||||
async function sendEmailNotification(contact) {
|
||||
if (!process.env.EMAIL_HOST || !process.env.EMAIL_USER) {
|
||||
console.log('Email configuration not provided, skipping email notification');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const transporter = nodemailer.createTransporter({
|
||||
host: process.env.EMAIL_HOST,
|
||||
port: process.env.EMAIL_PORT || 587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASS
|
||||
}
|
||||
});
|
||||
|
||||
const mailOptions = {
|
||||
from: process.env.EMAIL_USER,
|
||||
to: process.env.ADMIN_EMAIL || process.env.EMAIL_USER,
|
||||
subject: `New Contact Form Submission: ${contact.subject}`,
|
||||
html: `
|
||||
<h2>New Contact Form Submission</h2>
|
||||
<p><strong>Name:</strong> ${contact.name}</p>
|
||||
<p><strong>Email:</strong> ${contact.email}</p>
|
||||
<p><strong>Phone:</strong> ${contact.phone || 'Not provided'}</p>
|
||||
<p><strong>Company:</strong> ${contact.company || 'Not provided'}</p>
|
||||
<p><strong>Subject:</strong> ${contact.subject}</p>
|
||||
<p><strong>Message:</strong></p>
|
||||
<p>${contact.message.replace(/\n/g, '<br>')}</p>
|
||||
<p><strong>Source:</strong> ${contact.source}</p>
|
||||
<p><strong>Submitted:</strong> ${contact.createdAt}</p>
|
||||
<hr>
|
||||
<p><a href="${process.env.SITE_URL}/admin/contacts/${contact._id}">View in Admin Panel</a></p>
|
||||
`
|
||||
};
|
||||
|
||||
await transporter.sendMail(mailOptions);
|
||||
console.log('Email notification sent successfully');
|
||||
} catch (error) {
|
||||
console.error('Email notification error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to send Telegram notification
|
||||
async function sendTelegramNotification(contact) {
|
||||
if (!bot || !process.env.TELEGRAM_CHAT_ID) {
|
||||
console.log('Telegram configuration not provided, skipping Telegram notification');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const message = `
|
||||
🔔 *New Contact Form Submission*
|
||||
|
||||
👤 *Name:* ${contact.name}
|
||||
📧 *Email:* ${contact.email}
|
||||
📱 *Phone:* ${contact.phone || 'Not provided'}
|
||||
🏢 *Company:* ${contact.company || 'Not provided'}
|
||||
📝 *Subject:* ${contact.subject}
|
||||
|
||||
💬 *Message:*
|
||||
${contact.message}
|
||||
|
||||
📍 *Source:* ${contact.source}
|
||||
🕐 *Time:* ${contact.createdAt.toLocaleString()}
|
||||
|
||||
[View in Admin Panel](${process.env.SITE_URL}/admin/contacts/${contact._id})
|
||||
`;
|
||||
|
||||
await bot.sendMessage(process.env.TELEGRAM_CHAT_ID, message, {
|
||||
parse_mode: 'Markdown',
|
||||
disable_web_page_preview: true
|
||||
});
|
||||
|
||||
console.log('Telegram notification sent successfully');
|
||||
} catch (error) {
|
||||
console.error('Telegram notification error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to calculate project estimate
|
||||
function calculateProjectEstimate(services, projectType, timeline) {
|
||||
const baseRates = {
|
||||
'web-development': 50000,
|
||||
'mobile-app': 80000,
|
||||
'ui-ux-design': 30000,
|
||||
'branding': 20000,
|
||||
'e-commerce': 70000,
|
||||
'consulting': 40000
|
||||
};
|
||||
|
||||
const timelineMultipliers = {
|
||||
'asap': 1.5,
|
||||
'1-month': 1.2,
|
||||
'1-3-months': 1.0,
|
||||
'3-6-months': 0.9,
|
||||
'flexible': 0.8
|
||||
};
|
||||
|
||||
let basePrice = baseRates[projectType] || 50000;
|
||||
let multiplier = timelineMultipliers[timeline] || 1.0;
|
||||
|
||||
// Add service modifiers
|
||||
let serviceModifier = 1.0;
|
||||
if (services.length > 3) serviceModifier += 0.3;
|
||||
if (services.length > 5) serviceModifier += 0.5;
|
||||
|
||||
const totalEstimate = Math.round(basePrice * multiplier * serviceModifier);
|
||||
const rangeMin = Math.round(totalEstimate * 0.8);
|
||||
const rangeMax = Math.round(totalEstimate * 1.3);
|
||||
|
||||
return {
|
||||
base: totalEstimate,
|
||||
min: rangeMin,
|
||||
max: rangeMax,
|
||||
formatted: `₩${rangeMin.toLocaleString()} - ₩${rangeMax.toLocaleString()}`,
|
||||
currency: 'KRW'
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
250
.history/routes/contact_20251019204806.js
Normal file
250
.history/routes/contact_20251019204806.js
Normal file
@@ -0,0 +1,250 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const nodemailer = require('nodemailer');
|
||||
const { Contact } = require('../models');
|
||||
const TelegramBot = require('node-telegram-bot-api');
|
||||
|
||||
// Initialize Telegram bot if token is provided
|
||||
let bot = null;
|
||||
if (process.env.TELEGRAM_BOT_TOKEN) {
|
||||
bot = new TelegramBot(process.env.TELEGRAM_BOT_TOKEN, { polling: false });
|
||||
}
|
||||
|
||||
// Contact form validation
|
||||
const contactValidation = [
|
||||
body('name').trim().isLength({ min: 2, max: 100 }),
|
||||
body('email').isEmail().normalizeEmail(),
|
||||
body('subject').trim().isLength({ min: 5, max: 200 }),
|
||||
body('message').trim().isLength({ min: 10, max: 2000 }),
|
||||
body('phone').optional().isMobilePhone(),
|
||||
body('company').optional().trim().isLength({ max: 100 })
|
||||
];
|
||||
|
||||
// Submit contact form
|
||||
router.post('/submit', contactValidation, async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const contactData = {
|
||||
...req.body,
|
||||
ipAddress: req.ip,
|
||||
userAgent: req.get('User-Agent'),
|
||||
source: 'website'
|
||||
};
|
||||
|
||||
// Save to database
|
||||
const contact = new Contact(contactData);
|
||||
await contact.save();
|
||||
|
||||
// Send email notification
|
||||
await sendEmailNotification(contact);
|
||||
|
||||
// Send Telegram notification
|
||||
await sendTelegramNotification(contact);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Your message has been sent successfully. We will get back to you soon!',
|
||||
contactId: contact._id
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact form error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Sorry, there was an error sending your message. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get project estimate
|
||||
router.post('/estimate', [
|
||||
body('services').isArray().notEmpty(),
|
||||
body('projectType').notEmpty(),
|
||||
body('timeline').notEmpty(),
|
||||
body('budget').notEmpty(),
|
||||
body('description').trim().isLength({ min: 10, max: 1000 })
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { services, projectType, timeline, budget, description, contactInfo } = req.body;
|
||||
|
||||
// Calculate estimate (simplified)
|
||||
const estimate = calculateProjectEstimate(services, projectType, timeline);
|
||||
|
||||
// Save inquiry to database
|
||||
const contactData = {
|
||||
name: contactInfo.name,
|
||||
email: contactInfo.email,
|
||||
phone: contactInfo.phone,
|
||||
company: contactInfo.company,
|
||||
subject: `Project Estimate Request - ${projectType}`,
|
||||
message: `Project Description: ${description}\n\nServices: ${services.join(', ')}\nTimeline: ${timeline}\nBudget: ${budget}\n\nCalculated Estimate: ${estimate.formatted}`,
|
||||
serviceInterest: projectType,
|
||||
budget: budget,
|
||||
timeline: timeline,
|
||||
source: 'calculator',
|
||||
ipAddress: req.ip,
|
||||
userAgent: req.get('User-Agent')
|
||||
};
|
||||
|
||||
const contact = new Contact(contactData);
|
||||
await contact.save();
|
||||
|
||||
// Send notifications
|
||||
await sendEmailNotification(contact);
|
||||
await sendTelegramNotification(contact);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Your project estimate request has been submitted successfully!',
|
||||
estimate: estimate,
|
||||
contactId: contact._id
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Estimate request error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Sorry, there was an error processing your request. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to send email notification
|
||||
async function sendEmailNotification(contact) {
|
||||
if (!process.env.EMAIL_HOST || !process.env.EMAIL_USER) {
|
||||
console.log('Email configuration not provided, skipping email notification');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const transporter = nodemailer.createTransporter({
|
||||
host: process.env.EMAIL_HOST,
|
||||
port: process.env.EMAIL_PORT || 587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASS
|
||||
}
|
||||
});
|
||||
|
||||
const mailOptions = {
|
||||
from: process.env.EMAIL_USER,
|
||||
to: process.env.ADMIN_EMAIL || process.env.EMAIL_USER,
|
||||
subject: `New Contact Form Submission: ${contact.subject}`,
|
||||
html: `
|
||||
<h2>New Contact Form Submission</h2>
|
||||
<p><strong>Name:</strong> ${contact.name}</p>
|
||||
<p><strong>Email:</strong> ${contact.email}</p>
|
||||
<p><strong>Phone:</strong> ${contact.phone || 'Not provided'}</p>
|
||||
<p><strong>Company:</strong> ${contact.company || 'Not provided'}</p>
|
||||
<p><strong>Subject:</strong> ${contact.subject}</p>
|
||||
<p><strong>Message:</strong></p>
|
||||
<p>${contact.message.replace(/\n/g, '<br>')}</p>
|
||||
<p><strong>Source:</strong> ${contact.source}</p>
|
||||
<p><strong>Submitted:</strong> ${contact.createdAt}</p>
|
||||
<hr>
|
||||
<p><a href="${process.env.SITE_URL}/admin/contacts/${contact._id}">View in Admin Panel</a></p>
|
||||
`
|
||||
};
|
||||
|
||||
await transporter.sendMail(mailOptions);
|
||||
console.log('Email notification sent successfully');
|
||||
} catch (error) {
|
||||
console.error('Email notification error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to send Telegram notification
|
||||
async function sendTelegramNotification(contact) {
|
||||
if (!bot || !process.env.TELEGRAM_CHAT_ID) {
|
||||
console.log('Telegram configuration not provided, skipping Telegram notification');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const message = `
|
||||
🔔 *New Contact Form Submission*
|
||||
|
||||
👤 *Name:* ${contact.name}
|
||||
📧 *Email:* ${contact.email}
|
||||
📱 *Phone:* ${contact.phone || 'Not provided'}
|
||||
🏢 *Company:* ${contact.company || 'Not provided'}
|
||||
📝 *Subject:* ${contact.subject}
|
||||
|
||||
💬 *Message:*
|
||||
${contact.message}
|
||||
|
||||
📍 *Source:* ${contact.source}
|
||||
🕐 *Time:* ${contact.createdAt.toLocaleString()}
|
||||
|
||||
[View in Admin Panel](${process.env.SITE_URL}/admin/contacts/${contact._id})
|
||||
`;
|
||||
|
||||
await bot.sendMessage(process.env.TELEGRAM_CHAT_ID, message, {
|
||||
parse_mode: 'Markdown',
|
||||
disable_web_page_preview: true
|
||||
});
|
||||
|
||||
console.log('Telegram notification sent successfully');
|
||||
} catch (error) {
|
||||
console.error('Telegram notification error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to calculate project estimate
|
||||
function calculateProjectEstimate(services, projectType, timeline) {
|
||||
const baseRates = {
|
||||
'web-development': 50000,
|
||||
'mobile-app': 80000,
|
||||
'ui-ux-design': 30000,
|
||||
'branding': 20000,
|
||||
'e-commerce': 70000,
|
||||
'consulting': 40000
|
||||
};
|
||||
|
||||
const timelineMultipliers = {
|
||||
'asap': 1.5,
|
||||
'1-month': 1.2,
|
||||
'1-3-months': 1.0,
|
||||
'3-6-months': 0.9,
|
||||
'flexible': 0.8
|
||||
};
|
||||
|
||||
let basePrice = baseRates[projectType] || 50000;
|
||||
let multiplier = timelineMultipliers[timeline] || 1.0;
|
||||
|
||||
// Add service modifiers
|
||||
let serviceModifier = 1.0;
|
||||
if (services.length > 3) serviceModifier += 0.3;
|
||||
if (services.length > 5) serviceModifier += 0.5;
|
||||
|
||||
const totalEstimate = Math.round(basePrice * multiplier * serviceModifier);
|
||||
const rangeMin = Math.round(totalEstimate * 0.8);
|
||||
const rangeMax = Math.round(totalEstimate * 1.3);
|
||||
|
||||
return {
|
||||
base: totalEstimate,
|
||||
min: rangeMin,
|
||||
max: rangeMax,
|
||||
formatted: `₩${rangeMin.toLocaleString()} - ₩${rangeMax.toLocaleString()}`,
|
||||
currency: 'KRW'
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
244
.history/routes/contact_20251021213241.js
Normal file
244
.history/routes/contact_20251021213241.js
Normal file
@@ -0,0 +1,244 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const nodemailer = require('nodemailer');
|
||||
const { Contact } = require('../models');
|
||||
const telegramService = require('../services/telegram');
|
||||
|
||||
// Contact form validation
|
||||
const contactValidation = [
|
||||
body('name').trim().isLength({ min: 2, max: 100 }),
|
||||
body('email').isEmail().normalizeEmail(),
|
||||
body('subject').trim().isLength({ min: 5, max: 200 }),
|
||||
body('message').trim().isLength({ min: 10, max: 2000 }),
|
||||
body('phone').optional().isMobilePhone(),
|
||||
body('company').optional().trim().isLength({ max: 100 })
|
||||
];
|
||||
|
||||
// Submit contact form
|
||||
router.post('/submit', contactValidation, async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const contactData = {
|
||||
...req.body,
|
||||
ipAddress: req.ip,
|
||||
userAgent: req.get('User-Agent'),
|
||||
source: 'website'
|
||||
};
|
||||
|
||||
// Save to database
|
||||
const contact = new Contact(contactData);
|
||||
await contact.save();
|
||||
|
||||
// Send email notification
|
||||
await sendEmailNotification(contact);
|
||||
|
||||
// Send Telegram notification
|
||||
await sendTelegramNotification(contact);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Your message has been sent successfully. We will get back to you soon!',
|
||||
contactId: contact._id
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact form error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Sorry, there was an error sending your message. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get project estimate
|
||||
router.post('/estimate', [
|
||||
body('services').isArray().notEmpty(),
|
||||
body('projectType').notEmpty(),
|
||||
body('timeline').notEmpty(),
|
||||
body('budget').notEmpty(),
|
||||
body('description').trim().isLength({ min: 10, max: 1000 })
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { services, projectType, timeline, budget, description, contactInfo } = req.body;
|
||||
|
||||
// Calculate estimate (simplified)
|
||||
const estimate = calculateProjectEstimate(services, projectType, timeline);
|
||||
|
||||
// Save inquiry to database
|
||||
const contactData = {
|
||||
name: contactInfo.name,
|
||||
email: contactInfo.email,
|
||||
phone: contactInfo.phone,
|
||||
company: contactInfo.company,
|
||||
subject: `Project Estimate Request - ${projectType}`,
|
||||
message: `Project Description: ${description}\n\nServices: ${services.join(', ')}\nTimeline: ${timeline}\nBudget: ${budget}\n\nCalculated Estimate: ${estimate.formatted}`,
|
||||
serviceInterest: projectType,
|
||||
budget: budget,
|
||||
timeline: timeline,
|
||||
source: 'calculator',
|
||||
ipAddress: req.ip,
|
||||
userAgent: req.get('User-Agent')
|
||||
};
|
||||
|
||||
const contact = new Contact(contactData);
|
||||
await contact.save();
|
||||
|
||||
// Send notifications
|
||||
await sendEmailNotification(contact);
|
||||
await sendTelegramNotification(contact);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Your project estimate request has been submitted successfully!',
|
||||
estimate: estimate,
|
||||
contactId: contact._id
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Estimate request error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Sorry, there was an error processing your request. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to send email notification
|
||||
async function sendEmailNotification(contact) {
|
||||
if (!process.env.EMAIL_HOST || !process.env.EMAIL_USER) {
|
||||
console.log('Email configuration not provided, skipping email notification');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const transporter = nodemailer.createTransporter({
|
||||
host: process.env.EMAIL_HOST,
|
||||
port: process.env.EMAIL_PORT || 587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASS
|
||||
}
|
||||
});
|
||||
|
||||
const mailOptions = {
|
||||
from: process.env.EMAIL_USER,
|
||||
to: process.env.ADMIN_EMAIL || process.env.EMAIL_USER,
|
||||
subject: `New Contact Form Submission: ${contact.subject}`,
|
||||
html: `
|
||||
<h2>New Contact Form Submission</h2>
|
||||
<p><strong>Name:</strong> ${contact.name}</p>
|
||||
<p><strong>Email:</strong> ${contact.email}</p>
|
||||
<p><strong>Phone:</strong> ${contact.phone || 'Not provided'}</p>
|
||||
<p><strong>Company:</strong> ${contact.company || 'Not provided'}</p>
|
||||
<p><strong>Subject:</strong> ${contact.subject}</p>
|
||||
<p><strong>Message:</strong></p>
|
||||
<p>${contact.message.replace(/\n/g, '<br>')}</p>
|
||||
<p><strong>Source:</strong> ${contact.source}</p>
|
||||
<p><strong>Submitted:</strong> ${contact.createdAt}</p>
|
||||
<hr>
|
||||
<p><a href="${process.env.SITE_URL}/admin/contacts/${contact._id}">View in Admin Panel</a></p>
|
||||
`
|
||||
};
|
||||
|
||||
await transporter.sendMail(mailOptions);
|
||||
console.log('Email notification sent successfully');
|
||||
} catch (error) {
|
||||
console.error('Email notification error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to send Telegram notification
|
||||
async function sendTelegramNotification(contact) {
|
||||
if (!bot || !process.env.TELEGRAM_CHAT_ID) {
|
||||
console.log('Telegram configuration not provided, skipping Telegram notification');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const message = `
|
||||
🔔 *New Contact Form Submission*
|
||||
|
||||
👤 *Name:* ${contact.name}
|
||||
📧 *Email:* ${contact.email}
|
||||
📱 *Phone:* ${contact.phone || 'Not provided'}
|
||||
🏢 *Company:* ${contact.company || 'Not provided'}
|
||||
📝 *Subject:* ${contact.subject}
|
||||
|
||||
💬 *Message:*
|
||||
${contact.message}
|
||||
|
||||
📍 *Source:* ${contact.source}
|
||||
🕐 *Time:* ${contact.createdAt.toLocaleString()}
|
||||
|
||||
[View in Admin Panel](${process.env.SITE_URL}/admin/contacts/${contact._id})
|
||||
`;
|
||||
|
||||
await bot.sendMessage(process.env.TELEGRAM_CHAT_ID, message, {
|
||||
parse_mode: 'Markdown',
|
||||
disable_web_page_preview: true
|
||||
});
|
||||
|
||||
console.log('Telegram notification sent successfully');
|
||||
} catch (error) {
|
||||
console.error('Telegram notification error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to calculate project estimate
|
||||
function calculateProjectEstimate(services, projectType, timeline) {
|
||||
const baseRates = {
|
||||
'web-development': 50000,
|
||||
'mobile-app': 80000,
|
||||
'ui-ux-design': 30000,
|
||||
'branding': 20000,
|
||||
'e-commerce': 70000,
|
||||
'consulting': 40000
|
||||
};
|
||||
|
||||
const timelineMultipliers = {
|
||||
'asap': 1.5,
|
||||
'1-month': 1.2,
|
||||
'1-3-months': 1.0,
|
||||
'3-6-months': 0.9,
|
||||
'flexible': 0.8
|
||||
};
|
||||
|
||||
let basePrice = baseRates[projectType] || 50000;
|
||||
let multiplier = timelineMultipliers[timeline] || 1.0;
|
||||
|
||||
// Add service modifiers
|
||||
let serviceModifier = 1.0;
|
||||
if (services.length > 3) serviceModifier += 0.3;
|
||||
if (services.length > 5) serviceModifier += 0.5;
|
||||
|
||||
const totalEstimate = Math.round(basePrice * multiplier * serviceModifier);
|
||||
const rangeMin = Math.round(totalEstimate * 0.8);
|
||||
const rangeMax = Math.round(totalEstimate * 1.3);
|
||||
|
||||
return {
|
||||
base: totalEstimate,
|
||||
min: rangeMin,
|
||||
max: rangeMax,
|
||||
formatted: `₩${rangeMin.toLocaleString()} - ₩${rangeMax.toLocaleString()}`,
|
||||
currency: 'KRW'
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
244
.history/routes/contact_20251021213250.js
Normal file
244
.history/routes/contact_20251021213250.js
Normal file
@@ -0,0 +1,244 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const nodemailer = require('nodemailer');
|
||||
const { Contact } = require('../models');
|
||||
const telegramService = require('../services/telegram');
|
||||
|
||||
// Contact form validation
|
||||
const contactValidation = [
|
||||
body('name').trim().isLength({ min: 2, max: 100 }),
|
||||
body('email').isEmail().normalizeEmail(),
|
||||
body('subject').trim().isLength({ min: 5, max: 200 }),
|
||||
body('message').trim().isLength({ min: 10, max: 2000 }),
|
||||
body('phone').optional().isMobilePhone(),
|
||||
body('company').optional().trim().isLength({ max: 100 })
|
||||
];
|
||||
|
||||
// Submit contact form
|
||||
router.post('/submit', contactValidation, async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const contactData = {
|
||||
...req.body,
|
||||
ipAddress: req.ip,
|
||||
userAgent: req.get('User-Agent'),
|
||||
source: 'website'
|
||||
};
|
||||
|
||||
// Save to database
|
||||
const contact = new Contact(contactData);
|
||||
await contact.save();
|
||||
|
||||
// Send email notification
|
||||
await sendEmailNotification(contact);
|
||||
|
||||
// Send Telegram notification
|
||||
await telegramService.sendNewContactAlert(contact);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Your message has been sent successfully. We will get back to you soon!',
|
||||
contactId: contact._id
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact form error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Sorry, there was an error sending your message. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get project estimate
|
||||
router.post('/estimate', [
|
||||
body('services').isArray().notEmpty(),
|
||||
body('projectType').notEmpty(),
|
||||
body('timeline').notEmpty(),
|
||||
body('budget').notEmpty(),
|
||||
body('description').trim().isLength({ min: 10, max: 1000 })
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { services, projectType, timeline, budget, description, contactInfo } = req.body;
|
||||
|
||||
// Calculate estimate (simplified)
|
||||
const estimate = calculateProjectEstimate(services, projectType, timeline);
|
||||
|
||||
// Save inquiry to database
|
||||
const contactData = {
|
||||
name: contactInfo.name,
|
||||
email: contactInfo.email,
|
||||
phone: contactInfo.phone,
|
||||
company: contactInfo.company,
|
||||
subject: `Project Estimate Request - ${projectType}`,
|
||||
message: `Project Description: ${description}\n\nServices: ${services.join(', ')}\nTimeline: ${timeline}\nBudget: ${budget}\n\nCalculated Estimate: ${estimate.formatted}`,
|
||||
serviceInterest: projectType,
|
||||
budget: budget,
|
||||
timeline: timeline,
|
||||
source: 'calculator',
|
||||
ipAddress: req.ip,
|
||||
userAgent: req.get('User-Agent')
|
||||
};
|
||||
|
||||
const contact = new Contact(contactData);
|
||||
await contact.save();
|
||||
|
||||
// Send notifications
|
||||
await sendEmailNotification(contact);
|
||||
await sendTelegramNotification(contact);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Your project estimate request has been submitted successfully!',
|
||||
estimate: estimate,
|
||||
contactId: contact._id
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Estimate request error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Sorry, there was an error processing your request. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to send email notification
|
||||
async function sendEmailNotification(contact) {
|
||||
if (!process.env.EMAIL_HOST || !process.env.EMAIL_USER) {
|
||||
console.log('Email configuration not provided, skipping email notification');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const transporter = nodemailer.createTransporter({
|
||||
host: process.env.EMAIL_HOST,
|
||||
port: process.env.EMAIL_PORT || 587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASS
|
||||
}
|
||||
});
|
||||
|
||||
const mailOptions = {
|
||||
from: process.env.EMAIL_USER,
|
||||
to: process.env.ADMIN_EMAIL || process.env.EMAIL_USER,
|
||||
subject: `New Contact Form Submission: ${contact.subject}`,
|
||||
html: `
|
||||
<h2>New Contact Form Submission</h2>
|
||||
<p><strong>Name:</strong> ${contact.name}</p>
|
||||
<p><strong>Email:</strong> ${contact.email}</p>
|
||||
<p><strong>Phone:</strong> ${contact.phone || 'Not provided'}</p>
|
||||
<p><strong>Company:</strong> ${contact.company || 'Not provided'}</p>
|
||||
<p><strong>Subject:</strong> ${contact.subject}</p>
|
||||
<p><strong>Message:</strong></p>
|
||||
<p>${contact.message.replace(/\n/g, '<br>')}</p>
|
||||
<p><strong>Source:</strong> ${contact.source}</p>
|
||||
<p><strong>Submitted:</strong> ${contact.createdAt}</p>
|
||||
<hr>
|
||||
<p><a href="${process.env.SITE_URL}/admin/contacts/${contact._id}">View in Admin Panel</a></p>
|
||||
`
|
||||
};
|
||||
|
||||
await transporter.sendMail(mailOptions);
|
||||
console.log('Email notification sent successfully');
|
||||
} catch (error) {
|
||||
console.error('Email notification error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to send Telegram notification
|
||||
async function sendTelegramNotification(contact) {
|
||||
if (!bot || !process.env.TELEGRAM_CHAT_ID) {
|
||||
console.log('Telegram configuration not provided, skipping Telegram notification');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const message = `
|
||||
🔔 *New Contact Form Submission*
|
||||
|
||||
👤 *Name:* ${contact.name}
|
||||
📧 *Email:* ${contact.email}
|
||||
📱 *Phone:* ${contact.phone || 'Not provided'}
|
||||
🏢 *Company:* ${contact.company || 'Not provided'}
|
||||
📝 *Subject:* ${contact.subject}
|
||||
|
||||
💬 *Message:*
|
||||
${contact.message}
|
||||
|
||||
📍 *Source:* ${contact.source}
|
||||
🕐 *Time:* ${contact.createdAt.toLocaleString()}
|
||||
|
||||
[View in Admin Panel](${process.env.SITE_URL}/admin/contacts/${contact._id})
|
||||
`;
|
||||
|
||||
await bot.sendMessage(process.env.TELEGRAM_CHAT_ID, message, {
|
||||
parse_mode: 'Markdown',
|
||||
disable_web_page_preview: true
|
||||
});
|
||||
|
||||
console.log('Telegram notification sent successfully');
|
||||
} catch (error) {
|
||||
console.error('Telegram notification error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to calculate project estimate
|
||||
function calculateProjectEstimate(services, projectType, timeline) {
|
||||
const baseRates = {
|
||||
'web-development': 50000,
|
||||
'mobile-app': 80000,
|
||||
'ui-ux-design': 30000,
|
||||
'branding': 20000,
|
||||
'e-commerce': 70000,
|
||||
'consulting': 40000
|
||||
};
|
||||
|
||||
const timelineMultipliers = {
|
||||
'asap': 1.5,
|
||||
'1-month': 1.2,
|
||||
'1-3-months': 1.0,
|
||||
'3-6-months': 0.9,
|
||||
'flexible': 0.8
|
||||
};
|
||||
|
||||
let basePrice = baseRates[projectType] || 50000;
|
||||
let multiplier = timelineMultipliers[timeline] || 1.0;
|
||||
|
||||
// Add service modifiers
|
||||
let serviceModifier = 1.0;
|
||||
if (services.length > 3) serviceModifier += 0.3;
|
||||
if (services.length > 5) serviceModifier += 0.5;
|
||||
|
||||
const totalEstimate = Math.round(basePrice * multiplier * serviceModifier);
|
||||
const rangeMin = Math.round(totalEstimate * 0.8);
|
||||
const rangeMax = Math.round(totalEstimate * 1.3);
|
||||
|
||||
return {
|
||||
base: totalEstimate,
|
||||
min: rangeMin,
|
||||
max: rangeMax,
|
||||
formatted: `₩${rangeMin.toLocaleString()} - ₩${rangeMax.toLocaleString()}`,
|
||||
currency: 'KRW'
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
247
.history/routes/contact_20251021213258.js
Normal file
247
.history/routes/contact_20251021213258.js
Normal file
@@ -0,0 +1,247 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const nodemailer = require('nodemailer');
|
||||
const { Contact } = require('../models');
|
||||
const telegramService = require('../services/telegram');
|
||||
|
||||
// Contact form validation
|
||||
const contactValidation = [
|
||||
body('name').trim().isLength({ min: 2, max: 100 }),
|
||||
body('email').isEmail().normalizeEmail(),
|
||||
body('subject').trim().isLength({ min: 5, max: 200 }),
|
||||
body('message').trim().isLength({ min: 10, max: 2000 }),
|
||||
body('phone').optional().isMobilePhone(),
|
||||
body('company').optional().trim().isLength({ max: 100 })
|
||||
];
|
||||
|
||||
// Submit contact form
|
||||
router.post('/submit', contactValidation, async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const contactData = {
|
||||
...req.body,
|
||||
ipAddress: req.ip,
|
||||
userAgent: req.get('User-Agent'),
|
||||
source: 'website'
|
||||
};
|
||||
|
||||
// Save to database
|
||||
const contact = new Contact(contactData);
|
||||
await contact.save();
|
||||
|
||||
// Send email notification
|
||||
await sendEmailNotification(contact);
|
||||
|
||||
// Send Telegram notification
|
||||
await telegramService.sendNewContactAlert(contact);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Your message has been sent successfully. We will get back to you soon!',
|
||||
contactId: contact._id
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact form error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Sorry, there was an error sending your message. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get project estimate
|
||||
router.post('/estimate', [
|
||||
body('services').isArray().notEmpty(),
|
||||
body('projectType').notEmpty(),
|
||||
body('timeline').notEmpty(),
|
||||
body('budget').notEmpty(),
|
||||
body('description').trim().isLength({ min: 10, max: 1000 })
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { services, projectType, timeline, budget, description, contactInfo } = req.body;
|
||||
|
||||
// Calculate estimate (simplified)
|
||||
const estimate = calculateProjectEstimate(services, projectType, timeline);
|
||||
|
||||
// Save inquiry to database
|
||||
const contactData = {
|
||||
name: contactInfo.name,
|
||||
email: contactInfo.email,
|
||||
phone: contactInfo.phone,
|
||||
company: contactInfo.company,
|
||||
subject: `Project Estimate Request - ${projectType}`,
|
||||
message: `Project Description: ${description}\n\nServices: ${services.join(', ')}\nTimeline: ${timeline}\nBudget: ${budget}\n\nCalculated Estimate: ${estimate.formatted}`,
|
||||
serviceInterest: projectType,
|
||||
budget: budget,
|
||||
timeline: timeline,
|
||||
source: 'calculator',
|
||||
ipAddress: req.ip,
|
||||
userAgent: req.get('User-Agent')
|
||||
};
|
||||
|
||||
const contact = new Contact(contactData);
|
||||
await contact.save();
|
||||
|
||||
// Send notifications
|
||||
await sendEmailNotification(contact);
|
||||
await telegramService.sendCalculatorQuote({
|
||||
...contactData,
|
||||
services: services.map(s => ({ name: s, price: 0 })) // Simplified for now
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Your project estimate request has been submitted successfully!',
|
||||
estimate: estimate,
|
||||
contactId: contact._id
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Estimate request error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Sorry, there was an error processing your request. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to send email notification
|
||||
async function sendEmailNotification(contact) {
|
||||
if (!process.env.EMAIL_HOST || !process.env.EMAIL_USER) {
|
||||
console.log('Email configuration not provided, skipping email notification');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const transporter = nodemailer.createTransporter({
|
||||
host: process.env.EMAIL_HOST,
|
||||
port: process.env.EMAIL_PORT || 587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASS
|
||||
}
|
||||
});
|
||||
|
||||
const mailOptions = {
|
||||
from: process.env.EMAIL_USER,
|
||||
to: process.env.ADMIN_EMAIL || process.env.EMAIL_USER,
|
||||
subject: `New Contact Form Submission: ${contact.subject}`,
|
||||
html: `
|
||||
<h2>New Contact Form Submission</h2>
|
||||
<p><strong>Name:</strong> ${contact.name}</p>
|
||||
<p><strong>Email:</strong> ${contact.email}</p>
|
||||
<p><strong>Phone:</strong> ${contact.phone || 'Not provided'}</p>
|
||||
<p><strong>Company:</strong> ${contact.company || 'Not provided'}</p>
|
||||
<p><strong>Subject:</strong> ${contact.subject}</p>
|
||||
<p><strong>Message:</strong></p>
|
||||
<p>${contact.message.replace(/\n/g, '<br>')}</p>
|
||||
<p><strong>Source:</strong> ${contact.source}</p>
|
||||
<p><strong>Submitted:</strong> ${contact.createdAt}</p>
|
||||
<hr>
|
||||
<p><a href="${process.env.SITE_URL}/admin/contacts/${contact._id}">View in Admin Panel</a></p>
|
||||
`
|
||||
};
|
||||
|
||||
await transporter.sendMail(mailOptions);
|
||||
console.log('Email notification sent successfully');
|
||||
} catch (error) {
|
||||
console.error('Email notification error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to send Telegram notification
|
||||
async function sendTelegramNotification(contact) {
|
||||
if (!bot || !process.env.TELEGRAM_CHAT_ID) {
|
||||
console.log('Telegram configuration not provided, skipping Telegram notification');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const message = `
|
||||
🔔 *New Contact Form Submission*
|
||||
|
||||
👤 *Name:* ${contact.name}
|
||||
📧 *Email:* ${contact.email}
|
||||
📱 *Phone:* ${contact.phone || 'Not provided'}
|
||||
🏢 *Company:* ${contact.company || 'Not provided'}
|
||||
📝 *Subject:* ${contact.subject}
|
||||
|
||||
💬 *Message:*
|
||||
${contact.message}
|
||||
|
||||
📍 *Source:* ${contact.source}
|
||||
🕐 *Time:* ${contact.createdAt.toLocaleString()}
|
||||
|
||||
[View in Admin Panel](${process.env.SITE_URL}/admin/contacts/${contact._id})
|
||||
`;
|
||||
|
||||
await bot.sendMessage(process.env.TELEGRAM_CHAT_ID, message, {
|
||||
parse_mode: 'Markdown',
|
||||
disable_web_page_preview: true
|
||||
});
|
||||
|
||||
console.log('Telegram notification sent successfully');
|
||||
} catch (error) {
|
||||
console.error('Telegram notification error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Helper function to calculate project estimate
|
||||
function calculateProjectEstimate(services, projectType, timeline) {
|
||||
const baseRates = {
|
||||
'web-development': 50000,
|
||||
'mobile-app': 80000,
|
||||
'ui-ux-design': 30000,
|
||||
'branding': 20000,
|
||||
'e-commerce': 70000,
|
||||
'consulting': 40000
|
||||
};
|
||||
|
||||
const timelineMultipliers = {
|
||||
'asap': 1.5,
|
||||
'1-month': 1.2,
|
||||
'1-3-months': 1.0,
|
||||
'3-6-months': 0.9,
|
||||
'flexible': 0.8
|
||||
};
|
||||
|
||||
let basePrice = baseRates[projectType] || 50000;
|
||||
let multiplier = timelineMultipliers[timeline] || 1.0;
|
||||
|
||||
// Add service modifiers
|
||||
let serviceModifier = 1.0;
|
||||
if (services.length > 3) serviceModifier += 0.3;
|
||||
if (services.length > 5) serviceModifier += 0.5;
|
||||
|
||||
const totalEstimate = Math.round(basePrice * multiplier * serviceModifier);
|
||||
const rangeMin = Math.round(totalEstimate * 0.8);
|
||||
const rangeMax = Math.round(totalEstimate * 1.3);
|
||||
|
||||
return {
|
||||
base: totalEstimate,
|
||||
min: rangeMin,
|
||||
max: rangeMax,
|
||||
formatted: `₩${rangeMin.toLocaleString()} - ₩${rangeMax.toLocaleString()}`,
|
||||
currency: 'KRW'
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
212
.history/routes/contact_20251021213310.js
Normal file
212
.history/routes/contact_20251021213310.js
Normal file
@@ -0,0 +1,212 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const nodemailer = require('nodemailer');
|
||||
const { Contact } = require('../models');
|
||||
const telegramService = require('../services/telegram');
|
||||
|
||||
// Contact form validation
|
||||
const contactValidation = [
|
||||
body('name').trim().isLength({ min: 2, max: 100 }),
|
||||
body('email').isEmail().normalizeEmail(),
|
||||
body('subject').trim().isLength({ min: 5, max: 200 }),
|
||||
body('message').trim().isLength({ min: 10, max: 2000 }),
|
||||
body('phone').optional().isMobilePhone(),
|
||||
body('company').optional().trim().isLength({ max: 100 })
|
||||
];
|
||||
|
||||
// Submit contact form
|
||||
router.post('/submit', contactValidation, async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const contactData = {
|
||||
...req.body,
|
||||
ipAddress: req.ip,
|
||||
userAgent: req.get('User-Agent'),
|
||||
source: 'website'
|
||||
};
|
||||
|
||||
// Save to database
|
||||
const contact = new Contact(contactData);
|
||||
await contact.save();
|
||||
|
||||
// Send email notification
|
||||
await sendEmailNotification(contact);
|
||||
|
||||
// Send Telegram notification
|
||||
await telegramService.sendNewContactAlert(contact);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Your message has been sent successfully. We will get back to you soon!',
|
||||
contactId: contact._id
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact form error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Sorry, there was an error sending your message. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get project estimate
|
||||
router.post('/estimate', [
|
||||
body('services').isArray().notEmpty(),
|
||||
body('projectType').notEmpty(),
|
||||
body('timeline').notEmpty(),
|
||||
body('budget').notEmpty(),
|
||||
body('description').trim().isLength({ min: 10, max: 1000 })
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { services, projectType, timeline, budget, description, contactInfo } = req.body;
|
||||
|
||||
// Calculate estimate (simplified)
|
||||
const estimate = calculateProjectEstimate(services, projectType, timeline);
|
||||
|
||||
// Save inquiry to database
|
||||
const contactData = {
|
||||
name: contactInfo.name,
|
||||
email: contactInfo.email,
|
||||
phone: contactInfo.phone,
|
||||
company: contactInfo.company,
|
||||
subject: `Project Estimate Request - ${projectType}`,
|
||||
message: `Project Description: ${description}\n\nServices: ${services.join(', ')}\nTimeline: ${timeline}\nBudget: ${budget}\n\nCalculated Estimate: ${estimate.formatted}`,
|
||||
serviceInterest: projectType,
|
||||
budget: budget,
|
||||
timeline: timeline,
|
||||
source: 'calculator',
|
||||
ipAddress: req.ip,
|
||||
userAgent: req.get('User-Agent')
|
||||
};
|
||||
|
||||
const contact = new Contact(contactData);
|
||||
await contact.save();
|
||||
|
||||
// Send notifications
|
||||
await sendEmailNotification(contact);
|
||||
await telegramService.sendCalculatorQuote({
|
||||
...contactData,
|
||||
services: services.map(s => ({ name: s, price: 0 })) // Simplified for now
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Your project estimate request has been submitted successfully!',
|
||||
estimate: estimate,
|
||||
contactId: contact._id
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Estimate request error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Sorry, there was an error processing your request. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to send email notification
|
||||
async function sendEmailNotification(contact) {
|
||||
if (!process.env.EMAIL_HOST || !process.env.EMAIL_USER) {
|
||||
console.log('Email configuration not provided, skipping email notification');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const transporter = nodemailer.createTransporter({
|
||||
host: process.env.EMAIL_HOST,
|
||||
port: process.env.EMAIL_PORT || 587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASS
|
||||
}
|
||||
});
|
||||
|
||||
const mailOptions = {
|
||||
from: process.env.EMAIL_USER,
|
||||
to: process.env.ADMIN_EMAIL || process.env.EMAIL_USER,
|
||||
subject: `New Contact Form Submission: ${contact.subject}`,
|
||||
html: `
|
||||
<h2>New Contact Form Submission</h2>
|
||||
<p><strong>Name:</strong> ${contact.name}</p>
|
||||
<p><strong>Email:</strong> ${contact.email}</p>
|
||||
<p><strong>Phone:</strong> ${contact.phone || 'Not provided'}</p>
|
||||
<p><strong>Company:</strong> ${contact.company || 'Not provided'}</p>
|
||||
<p><strong>Subject:</strong> ${contact.subject}</p>
|
||||
<p><strong>Message:</strong></p>
|
||||
<p>${contact.message.replace(/\n/g, '<br>')}</p>
|
||||
<p><strong>Source:</strong> ${contact.source}</p>
|
||||
<p><strong>Submitted:</strong> ${contact.createdAt}</p>
|
||||
<hr>
|
||||
<p><a href="${process.env.SITE_URL}/admin/contacts/${contact._id}">View in Admin Panel</a></p>
|
||||
`
|
||||
};
|
||||
|
||||
await transporter.sendMail(mailOptions);
|
||||
console.log('Email notification sent successfully');
|
||||
} catch (error) {
|
||||
console.error('Email notification error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Telegram notifications now handled by telegramService
|
||||
|
||||
// Helper function to calculate project estimate
|
||||
function calculateProjectEstimate(services, projectType, timeline) {
|
||||
const baseRates = {
|
||||
'web-development': 50000,
|
||||
'mobile-app': 80000,
|
||||
'ui-ux-design': 30000,
|
||||
'branding': 20000,
|
||||
'e-commerce': 70000,
|
||||
'consulting': 40000
|
||||
};
|
||||
|
||||
const timelineMultipliers = {
|
||||
'asap': 1.5,
|
||||
'1-month': 1.2,
|
||||
'1-3-months': 1.0,
|
||||
'3-6-months': 0.9,
|
||||
'flexible': 0.8
|
||||
};
|
||||
|
||||
let basePrice = baseRates[projectType] || 50000;
|
||||
let multiplier = timelineMultipliers[timeline] || 1.0;
|
||||
|
||||
// Add service modifiers
|
||||
let serviceModifier = 1.0;
|
||||
if (services.length > 3) serviceModifier += 0.3;
|
||||
if (services.length > 5) serviceModifier += 0.5;
|
||||
|
||||
const totalEstimate = Math.round(basePrice * multiplier * serviceModifier);
|
||||
const rangeMin = Math.round(totalEstimate * 0.8);
|
||||
const rangeMax = Math.round(totalEstimate * 1.3);
|
||||
|
||||
return {
|
||||
base: totalEstimate,
|
||||
min: rangeMin,
|
||||
max: rangeMax,
|
||||
formatted: `₩${rangeMin.toLocaleString()} - ₩${rangeMax.toLocaleString()}`,
|
||||
currency: 'KRW'
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
212
.history/routes/contact_20251021214112.js
Normal file
212
.history/routes/contact_20251021214112.js
Normal file
@@ -0,0 +1,212 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { body, validationResult } = require('express-validator');
|
||||
const nodemailer = require('nodemailer');
|
||||
const { Contact } = require('../models');
|
||||
const telegramService = require('../services/telegram');
|
||||
|
||||
// Contact form validation
|
||||
const contactValidation = [
|
||||
body('name').trim().isLength({ min: 2, max: 100 }),
|
||||
body('email').isEmail().normalizeEmail(),
|
||||
body('subject').trim().isLength({ min: 5, max: 200 }),
|
||||
body('message').trim().isLength({ min: 10, max: 2000 }),
|
||||
body('phone').optional().isMobilePhone(),
|
||||
body('company').optional().trim().isLength({ max: 100 })
|
||||
];
|
||||
|
||||
// Submit contact form
|
||||
router.post('/submit', contactValidation, async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const contactData = {
|
||||
...req.body,
|
||||
ipAddress: req.ip,
|
||||
userAgent: req.get('User-Agent'),
|
||||
source: 'website'
|
||||
};
|
||||
|
||||
// Save to database
|
||||
const contact = new Contact(contactData);
|
||||
await contact.save();
|
||||
|
||||
// Send email notification
|
||||
await sendEmailNotification(contact);
|
||||
|
||||
// Send Telegram notification
|
||||
await telegramService.sendNewContactAlert(contact);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Your message has been sent successfully. We will get back to you soon!',
|
||||
contactId: contact._id
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact form error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Sorry, there was an error sending your message. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get project estimate
|
||||
router.post('/estimate', [
|
||||
body('services').isArray().notEmpty(),
|
||||
body('projectType').notEmpty(),
|
||||
body('timeline').notEmpty(),
|
||||
body('budget').notEmpty(),
|
||||
body('description').trim().isLength({ min: 10, max: 1000 })
|
||||
], async (req, res) => {
|
||||
try {
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid input data',
|
||||
errors: errors.array()
|
||||
});
|
||||
}
|
||||
|
||||
const { services, projectType, timeline, budget, description, contactInfo } = req.body;
|
||||
|
||||
// Calculate estimate (simplified)
|
||||
const estimate = calculateProjectEstimate(services, projectType, timeline);
|
||||
|
||||
// Save inquiry to database
|
||||
const contactData = {
|
||||
name: contactInfo.name,
|
||||
email: contactInfo.email,
|
||||
phone: contactInfo.phone,
|
||||
company: contactInfo.company,
|
||||
subject: `Project Estimate Request - ${projectType}`,
|
||||
message: `Project Description: ${description}\n\nServices: ${services.join(', ')}\nTimeline: ${timeline}\nBudget: ${budget}\n\nCalculated Estimate: ${estimate.formatted}`,
|
||||
serviceInterest: projectType,
|
||||
budget: budget,
|
||||
timeline: timeline,
|
||||
source: 'calculator',
|
||||
ipAddress: req.ip,
|
||||
userAgent: req.get('User-Agent')
|
||||
};
|
||||
|
||||
const contact = new Contact(contactData);
|
||||
await contact.save();
|
||||
|
||||
// Send notifications
|
||||
await sendEmailNotification(contact);
|
||||
await telegramService.sendCalculatorQuote({
|
||||
...contactData,
|
||||
services: services.map(s => ({ name: s, price: 0 })) // Simplified for now
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Your project estimate request has been submitted successfully!',
|
||||
estimate: estimate,
|
||||
contactId: contact._id
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Estimate request error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Sorry, there was an error processing your request. Please try again.'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Helper function to send email notification
|
||||
async function sendEmailNotification(contact) {
|
||||
if (!process.env.EMAIL_HOST || !process.env.EMAIL_USER) {
|
||||
console.log('Email configuration not provided, skipping email notification');
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
const transporter = nodemailer.createTransporter({
|
||||
host: process.env.EMAIL_HOST,
|
||||
port: process.env.EMAIL_PORT || 587,
|
||||
secure: false,
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASS
|
||||
}
|
||||
});
|
||||
|
||||
const mailOptions = {
|
||||
from: process.env.EMAIL_USER,
|
||||
to: process.env.ADMIN_EMAIL || process.env.EMAIL_USER,
|
||||
subject: `New Contact Form Submission: ${contact.subject}`,
|
||||
html: `
|
||||
<h2>New Contact Form Submission</h2>
|
||||
<p><strong>Name:</strong> ${contact.name}</p>
|
||||
<p><strong>Email:</strong> ${contact.email}</p>
|
||||
<p><strong>Phone:</strong> ${contact.phone || 'Not provided'}</p>
|
||||
<p><strong>Company:</strong> ${contact.company || 'Not provided'}</p>
|
||||
<p><strong>Subject:</strong> ${contact.subject}</p>
|
||||
<p><strong>Message:</strong></p>
|
||||
<p>${contact.message.replace(/\n/g, '<br>')}</p>
|
||||
<p><strong>Source:</strong> ${contact.source}</p>
|
||||
<p><strong>Submitted:</strong> ${contact.createdAt}</p>
|
||||
<hr>
|
||||
<p><a href="${process.env.SITE_URL}/admin/contacts/${contact._id}">View in Admin Panel</a></p>
|
||||
`
|
||||
};
|
||||
|
||||
await transporter.sendMail(mailOptions);
|
||||
console.log('Email notification sent successfully');
|
||||
} catch (error) {
|
||||
console.error('Email notification error:', error);
|
||||
}
|
||||
}
|
||||
|
||||
// Telegram notifications now handled by telegramService
|
||||
|
||||
// Helper function to calculate project estimate
|
||||
function calculateProjectEstimate(services, projectType, timeline) {
|
||||
const baseRates = {
|
||||
'web-development': 50000,
|
||||
'mobile-app': 80000,
|
||||
'ui-ux-design': 30000,
|
||||
'branding': 20000,
|
||||
'e-commerce': 70000,
|
||||
'consulting': 40000
|
||||
};
|
||||
|
||||
const timelineMultipliers = {
|
||||
'asap': 1.5,
|
||||
'1-month': 1.2,
|
||||
'1-3-months': 1.0,
|
||||
'3-6-months': 0.9,
|
||||
'flexible': 0.8
|
||||
};
|
||||
|
||||
let basePrice = baseRates[projectType] || 50000;
|
||||
let multiplier = timelineMultipliers[timeline] || 1.0;
|
||||
|
||||
// Add service modifiers
|
||||
let serviceModifier = 1.0;
|
||||
if (services.length > 3) serviceModifier += 0.3;
|
||||
if (services.length > 5) serviceModifier += 0.5;
|
||||
|
||||
const totalEstimate = Math.round(basePrice * multiplier * serviceModifier);
|
||||
const rangeMin = Math.round(totalEstimate * 0.8);
|
||||
const rangeMax = Math.round(totalEstimate * 1.3);
|
||||
|
||||
return {
|
||||
base: totalEstimate,
|
||||
min: rangeMin,
|
||||
max: rangeMax,
|
||||
formatted: `₩${rangeMin.toLocaleString()} - ₩${rangeMax.toLocaleString()}`,
|
||||
currency: 'KRW'
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
201
.history/routes/index_20251019203116.js
Normal file
201
.history/routes/index_20251019203116.js
Normal file
@@ -0,0 +1,201 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.find({ featured: true, isPublished: true })
|
||||
.sort({ order: 1, createdAt: -1 })
|
||||
.limit(6),
|
||||
Service.find({ featured: true, isActive: true })
|
||||
.sort({ order: 1 })
|
||||
.limit(4)
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [portfolio, total, categories] = await Promise.all([
|
||||
Portfolio.find(query)
|
||||
.sort({ featured: -1, publishedAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Portfolio.countDocuments(query),
|
||||
Portfolio.distinct('category', { isPublished: true })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.find({
|
||||
_id: { $ne: portfolio._id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
}).limit(3);
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.sort({ featured: -1, order: 1 })
|
||||
.populate('portfolio', 'title images');
|
||||
|
||||
const categories = await Service.distinct('category', { isActive: true });
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.select('name pricing category')
|
||||
.sort({ category: 1, name: 1 });
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
205
.history/routes/index_20251019203123.js
Normal file
205
.history/routes/index_20251019203123.js
Normal file
@@ -0,0 +1,205 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [portfolio, total, categories] = await Promise.all([
|
||||
Portfolio.find(query)
|
||||
.sort({ featured: -1, publishedAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Portfolio.countDocuments(query),
|
||||
Portfolio.distinct('category', { isPublished: true })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.find({
|
||||
_id: { $ne: portfolio._id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
}).limit(3);
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.sort({ featured: -1, order: 1 })
|
||||
.populate('portfolio', 'title images');
|
||||
|
||||
const categories = await Service.distinct('category', { isActive: true });
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.select('name pricing category')
|
||||
.sort({ category: 1, name: 1 });
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
205
.history/routes/index_20251019203147.js
Normal file
205
.history/routes/index_20251019203147.js
Normal file
@@ -0,0 +1,205 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [portfolio, total, categories] = await Promise.all([
|
||||
Portfolio.find(query)
|
||||
.sort({ featured: -1, publishedAt: -1 })
|
||||
.skip(skip)
|
||||
.limit(limit),
|
||||
Portfolio.countDocuments(query),
|
||||
Portfolio.distinct('category', { isPublished: true })
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.find({
|
||||
_id: { $ne: portfolio._id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
}).limit(3);
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.sort({ featured: -1, order: 1 })
|
||||
.populate('portfolio', 'title images');
|
||||
|
||||
const categories = await Service.distinct('category', { isActive: true });
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.select('name pricing category')
|
||||
.sort({ category: 1, name: 1 });
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
211
.history/routes/index_20251019203238.js
Normal file
211
.history/routes/index_20251019203238.js
Normal file
@@ -0,0 +1,211 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [portfolio, total, categories] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.find({
|
||||
_id: { $ne: portfolio._id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
}).limit(3);
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.sort({ featured: -1, order: 1 })
|
||||
.populate('portfolio', 'title images');
|
||||
|
||||
const categories = await Service.distinct('category', { isActive: true });
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.select('name pricing category')
|
||||
.sort({ category: 1, name: 1 });
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
211
.history/routes/index_20251019203244.js
Normal file
211
.history/routes/index_20251019203244.js
Normal file
@@ -0,0 +1,211 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [portfolio, total, categories] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.find({
|
||||
_id: { $ne: portfolio._id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
}).limit(3);
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.sort({ featured: -1, order: 1 })
|
||||
.populate('portfolio', 'title images');
|
||||
|
||||
const categories = await Service.distinct('category', { isActive: true });
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.select('name pricing category')
|
||||
.sort({ category: 1, name: 1 });
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
215
.history/routes/index_20251019203308.js
Normal file
215
.history/routes/index_20251019203308.js
Normal file
@@ -0,0 +1,215 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [portfolio, total, categories] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.sort({ featured: -1, order: 1 })
|
||||
.populate('portfolio', 'title images');
|
||||
|
||||
const categories = await Service.distinct('category', { isActive: true });
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.select('name pricing category')
|
||||
.sort({ category: 1, name: 1 });
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
216
.history/routes/index_20251019203315.js
Normal file
216
.history/routes/index_20251019203315.js
Normal file
@@ -0,0 +1,216 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [portfolio, total, categories] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.sort({ featured: -1, order: 1 })
|
||||
.populate('portfolio', 'title images');
|
||||
|
||||
const categories = await Service.distinct('category', { isActive: true });
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.select('name pricing category')
|
||||
.sort({ category: 1, name: 1 });
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
216
.history/routes/index_20251019203341.js
Normal file
216
.history/routes/index_20251019203341.js
Normal file
@@ -0,0 +1,216 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [portfolio, total, categories] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.sort({ featured: -1, order: 1 })
|
||||
.populate('portfolio', 'title images');
|
||||
|
||||
const categories = await Service.distinct('category', { isActive: true });
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.select('name pricing category')
|
||||
.sort({ category: 1, name: 1 });
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
221
.history/routes/index_20251019204717.js
Normal file
221
.history/routes/index_20251019204717.js
Normal file
@@ -0,0 +1,221 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [portfolio, total, categories] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
});
|
||||
|
||||
const categories = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
});
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.find({ isActive: true })
|
||||
.select('name pricing category')
|
||||
.sort({ category: 1, name: 1 });
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
223
.history/routes/index_20251019204728.js
Normal file
223
.history/routes/index_20251019204728.js
Normal file
@@ -0,0 +1,223 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [portfolio, total, categories] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
});
|
||||
|
||||
const categories = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
});
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
});
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
223
.history/routes/index_20251019204805.js
Normal file
223
.history/routes/index_20251019204805.js
Normal file
@@ -0,0 +1,223 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [portfolio, total, categories] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
});
|
||||
|
||||
const categories = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
});
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
});
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
223
.history/routes/index_20251019204839.js
Normal file
223
.history/routes/index_20251019204839.js
Normal file
@@ -0,0 +1,223 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [portfolio, total, categories] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
portfolioItems: portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
});
|
||||
|
||||
const categories = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
});
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
});
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
223
.history/routes/index_20251019204914.js
Normal file
223
.history/routes/index_20251019204914.js
Normal file
@@ -0,0 +1,223 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [portfolio, total, categories] = await Promise.all([
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
portfolioItems: portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
});
|
||||
|
||||
const categories = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
});
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
});
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
225
.history/routes/index_20251020035435.js
Normal file
225
.history/routes/index_20251020035435.js
Normal file
@@ -0,0 +1,225 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [settings, portfolio, total, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
settings: settings || {},
|
||||
portfolioItems: portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
});
|
||||
|
||||
const categories = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
});
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
});
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
230
.history/routes/index_20251020035447.js
Normal file
230
.history/routes/index_20251020035447.js
Normal file
@@ -0,0 +1,230 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [settings, portfolio, total, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
settings: settings || {},
|
||||
portfolioItems: portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const [settings, portfolio] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findByPk(req.params.id)
|
||||
]);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
settings: settings || {},
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
settings: settings || {},
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
});
|
||||
|
||||
const categories = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
});
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
});
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
233
.history/routes/index_20251020035455.js
Normal file
233
.history/routes/index_20251020035455.js
Normal file
@@ -0,0 +1,233 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [settings, portfolio, total, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
settings: settings || {},
|
||||
portfolioItems: portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const [settings, portfolio] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findByPk(req.params.id)
|
||||
]);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
settings: settings || {},
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
settings: settings || {},
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const [settings, services, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const services = await Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
});
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
237
.history/routes/index_20251020035505.js
Normal file
237
.history/routes/index_20251020035505.js
Normal file
@@ -0,0 +1,237 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [settings, portfolio, total, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
settings: settings || {},
|
||||
portfolioItems: portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const [settings, portfolio] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findByPk(req.params.id)
|
||||
]);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
settings: settings || {},
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
settings: settings || {},
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const [settings, services, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const [settings, services] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
238
.history/routes/index_20251020035544.js
Normal file
238
.history/routes/index_20251020035544.js
Normal file
@@ -0,0 +1,238 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [settings, portfolio, total, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
settings: settings || {},
|
||||
portfolioItems: portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const [settings, portfolio] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findByPk(req.params.id)
|
||||
]);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
settings: settings || {},
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
settings: settings || {},
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const [settings, services, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const [settings, services] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
239
.history/routes/index_20251020035550.js
Normal file
239
.history/routes/index_20251020035550.js
Normal file
@@ -0,0 +1,239 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [settings, portfolio, total, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
settings: settings || {},
|
||||
portfolioItems: portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const [settings, portfolio] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findByPk(req.params.id)
|
||||
]);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
settings: settings || {},
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
settings: settings || {},
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const [settings, services, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const [settings, services] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
240
.history/routes/index_20251020035557.js
Normal file
240
.history/routes/index_20251020035557.js
Normal file
@@ -0,0 +1,240 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [settings, portfolio, total, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
settings: settings || {},
|
||||
portfolioItems: portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const [settings, portfolio] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findByPk(req.params.id)
|
||||
]);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
settings: settings || {},
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
settings: settings || {},
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const [settings, services, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const [settings, services] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
241
.history/routes/index_20251020035603.js
Normal file
241
.history/routes/index_20251020035603.js
Normal file
@@ -0,0 +1,241 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [settings, portfolio, total, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
settings: settings || {},
|
||||
portfolioItems: portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const [settings, portfolio] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findByPk(req.params.id)
|
||||
]);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
settings: settings || {},
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
settings: settings || {},
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const [settings, services, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const [settings, services] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
242
.history/routes/index_20251020035610.js
Normal file
242
.history/routes/index_20251020035610.js
Normal file
@@ -0,0 +1,242 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [settings, portfolio, total, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
settings: settings || {},
|
||||
portfolioItems: portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const [settings, portfolio] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findByPk(req.params.id)
|
||||
]);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
settings: settings || {},
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
settings: settings || {},
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const [settings, services, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const [settings, services] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
243
.history/routes/index_20251020035616.js
Normal file
243
.history/routes/index_20251020035616.js
Normal file
@@ -0,0 +1,243 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [settings, portfolio, total, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
settings: settings || {},
|
||||
portfolioItems: portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const [settings, portfolio] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findByPk(req.params.id)
|
||||
]);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
settings: settings || {},
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
settings: settings || {},
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const [settings, services, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const [settings, services] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
244
.history/routes/index_20251020035622.js
Normal file
244
.history/routes/index_20251020035622.js
Normal file
@@ -0,0 +1,244 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [settings, portfolio, total, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
settings: settings || {},
|
||||
portfolioItems: portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const [settings, portfolio] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findByPk(req.params.id)
|
||||
]);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
settings: settings || {},
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
settings: settings || {},
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const [settings, services, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const [settings, services] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
244
.history/routes/index_20251020035856.js
Normal file
244
.history/routes/index_20251020035856.js
Normal file
@@ -0,0 +1,244 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio, Service, SiteSettings } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Home page
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const [settings, featuredPortfolio, featuredServices] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: { featured: true, isPublished: true },
|
||||
order: [['order', 'ASC'], ['createdAt', 'DESC']],
|
||||
limit: 6
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { featured: true, isActive: true },
|
||||
order: [['order', 'ASC']],
|
||||
limit: 4
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('index', {
|
||||
title: 'SmartSolTech - Innovative Technology Solutions',
|
||||
settings: settings || {},
|
||||
featuredPortfolio,
|
||||
featuredServices,
|
||||
currentPage: 'home'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Home page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// About page
|
||||
router.get('/about', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('about', {
|
||||
title: 'About Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'about'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('About page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio page
|
||||
router.get('/portfolio', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
|
||||
let query = { isPublished: true };
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
const [settings, portfolio, total, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findAll({
|
||||
where: query,
|
||||
order: [['featured', 'DESC'], ['publishedAt', 'DESC']],
|
||||
offset: skip,
|
||||
limit: limit
|
||||
}),
|
||||
Portfolio.count({ where: query }),
|
||||
Portfolio.findAll({
|
||||
where: { isPublished: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
}).then(results => results.map(r => r.category))
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.render('portfolio', {
|
||||
title: 'Portfolio - SmartSolTech',
|
||||
settings: settings || {},
|
||||
portfolioItems: portfolio,
|
||||
categories,
|
||||
currentCategory: category || 'all',
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Portfolio detail page
|
||||
router.get('/portfolio/:id', async (req, res) => {
|
||||
try {
|
||||
const [settings, portfolio] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Portfolio.findByPk(req.params.id)
|
||||
]);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).render('404', {
|
||||
title: '404 - Project Not Found',
|
||||
settings: settings || {},
|
||||
message: 'The requested project was not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
const relatedProjects = await Portfolio.findAll({
|
||||
where: {
|
||||
id: { [Op.ne]: portfolio.id },
|
||||
category: portfolio.category,
|
||||
isPublished: true
|
||||
},
|
||||
order: [['publishedAt', 'DESC']],
|
||||
limit: 3
|
||||
});
|
||||
|
||||
res.render('portfolio-detail', {
|
||||
title: `${portfolio.title} - Portfolio - SmartSolTech`,
|
||||
settings: settings || {},
|
||||
portfolio,
|
||||
relatedProjects,
|
||||
currentPage: 'portfolio'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Services page
|
||||
router.get('/services', async (req, res) => {
|
||||
try {
|
||||
const [settings, services, categories] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
}),
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['category'],
|
||||
group: ['category']
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('services', {
|
||||
title: 'Services - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
categories,
|
||||
currentPage: 'services'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Calculator page
|
||||
router.get('/calculator', async (req, res) => {
|
||||
try {
|
||||
const [settings, services] = await Promise.all([
|
||||
SiteSettings.findOne() || {},
|
||||
Service.findAll({
|
||||
where: { isActive: true },
|
||||
attributes: ['id', 'name', 'pricing', 'category'],
|
||||
order: [['category', 'ASC'], ['name', 'ASC']]
|
||||
})
|
||||
]);
|
||||
|
||||
res.render('calculator', {
|
||||
title: 'Project Calculator - SmartSolTech',
|
||||
settings: settings || {},
|
||||
services,
|
||||
currentPage: 'calculator'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Calculator page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Contact page
|
||||
router.get('/contact', async (req, res) => {
|
||||
try {
|
||||
const settings = await SiteSettings.findOne() || {};
|
||||
|
||||
res.render('contact', {
|
||||
title: 'Contact Us - SmartSolTech',
|
||||
settings,
|
||||
currentPage: 'contact'
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Contact page error:', error);
|
||||
res.status(500).render('error', {
|
||||
title: 'Error',
|
||||
settings: {},
|
||||
message: 'Something went wrong'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
415
.history/routes/media_20251022052143.js
Normal file
415
.history/routes/media_20251022052143.js
Normal file
@@ -0,0 +1,415 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const multer = require('multer');
|
||||
const sharp = require('sharp');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Authentication required'
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Configure multer for file uploads
|
||||
const storage = multer.diskStorage({
|
||||
destination: async (req, file, cb) => {
|
||||
const uploadPath = path.join(__dirname, '../public/uploads');
|
||||
try {
|
||||
await fs.mkdir(uploadPath, { recursive: true });
|
||||
cb(null, uploadPath);
|
||||
} catch (error) {
|
||||
cb(error);
|
||||
}
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
const ext = path.extname(file.originalname);
|
||||
cb(null, file.fieldname + '-' + uniqueSuffix + ext);
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: {
|
||||
fileSize: parseInt(process.env.MAX_FILE_SIZE) || 10 * 1024 * 1024, // 10MB
|
||||
files: 10
|
||||
},
|
||||
fileFilter: (req, file, cb) => {
|
||||
// Allow images only
|
||||
if (file.mimetype.startsWith('image/')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('Only image files are allowed'), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Upload single image
|
||||
router.post('/upload', requireAuth, upload.single('image'), async (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'No file uploaded'
|
||||
});
|
||||
}
|
||||
|
||||
const originalPath = req.file.path;
|
||||
const filename = req.file.filename;
|
||||
const nameWithoutExt = path.parse(filename).name;
|
||||
|
||||
// Create optimized versions
|
||||
const sizes = {
|
||||
thumbnail: { width: 300, height: 200 },
|
||||
medium: { width: 800, height: 600 },
|
||||
large: { width: 1200, height: 900 }
|
||||
};
|
||||
|
||||
const optimizedImages = {};
|
||||
|
||||
for (const [sizeName, dimensions] of Object.entries(sizes)) {
|
||||
const outputPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-${sizeName}.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.resize(dimensions.width, dimensions.height, {
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true
|
||||
})
|
||||
.webp({ quality: 85 })
|
||||
.toFile(outputPath);
|
||||
|
||||
optimizedImages[sizeName] = `/uploads/${path.basename(outputPath)}`;
|
||||
}
|
||||
|
||||
// Keep original as well (converted to webp for better compression)
|
||||
const originalWebpPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-original.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.webp({ quality: 90 })
|
||||
.toFile(originalWebpPath);
|
||||
|
||||
optimizedImages.original = `/uploads/${path.basename(originalWebpPath)}`;
|
||||
|
||||
// Remove the original uploaded file
|
||||
await fs.unlink(originalPath);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Image uploaded and optimized successfully',
|
||||
images: optimizedImages,
|
||||
metadata: {
|
||||
originalName: req.file.originalname,
|
||||
mimeType: req.file.mimetype,
|
||||
size: req.file.size
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Image upload error:', error);
|
||||
|
||||
// Clean up files on error
|
||||
if (req.file && req.file.path) {
|
||||
try {
|
||||
await fs.unlink(req.file.path);
|
||||
} catch (unlinkError) {
|
||||
console.error('Error removing file:', unlinkError);
|
||||
}
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error uploading image'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Upload multiple images
|
||||
router.post('/upload-multiple', requireAuth, upload.array('images', 10), async (req, res) => {
|
||||
try {
|
||||
if (!req.files || req.files.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'No files uploaded'
|
||||
});
|
||||
}
|
||||
|
||||
const uploadedImages = [];
|
||||
|
||||
for (const file of req.files) {
|
||||
try {
|
||||
const originalPath = file.path;
|
||||
const filename = file.filename;
|
||||
const nameWithoutExt = path.parse(filename).name;
|
||||
|
||||
// Create optimized versions
|
||||
const sizes = {
|
||||
thumbnail: { width: 300, height: 200 },
|
||||
medium: { width: 800, height: 600 },
|
||||
large: { width: 1200, height: 900 }
|
||||
};
|
||||
|
||||
const optimizedImages = {};
|
||||
|
||||
for (const [sizeName, dimensions] of Object.entries(sizes)) {
|
||||
const outputPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-${sizeName}.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.resize(dimensions.width, dimensions.height, {
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true
|
||||
})
|
||||
.webp({ quality: 85 })
|
||||
.toFile(outputPath);
|
||||
|
||||
optimizedImages[sizeName] = `/uploads/${path.basename(outputPath)}`;
|
||||
}
|
||||
|
||||
// Original as webp
|
||||
const originalWebpPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-original.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.webp({ quality: 90 })
|
||||
.toFile(originalWebpPath);
|
||||
|
||||
optimizedImages.original = `/uploads/${path.basename(originalWebpPath)}`;
|
||||
|
||||
uploadedImages.push({
|
||||
originalName: file.originalname,
|
||||
images: optimizedImages,
|
||||
metadata: {
|
||||
mimeType: file.mimetype,
|
||||
size: file.size
|
||||
}
|
||||
});
|
||||
|
||||
// Remove original file
|
||||
await fs.unlink(originalPath);
|
||||
} catch (fileError) {
|
||||
console.error(`Error processing file ${file.originalname}:`, fileError);
|
||||
// Clean up this file and continue with others
|
||||
try {
|
||||
await fs.unlink(file.path);
|
||||
} catch (unlinkError) {
|
||||
console.error('Error removing file:', unlinkError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `${uploadedImages.length} images uploaded and optimized successfully`,
|
||||
images: uploadedImages
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Multiple images upload error:', error);
|
||||
|
||||
// Clean up files on error
|
||||
if (req.files) {
|
||||
for (const file of req.files) {
|
||||
try {
|
||||
await fs.unlink(file.path);
|
||||
} catch (unlinkError) {
|
||||
console.error('Error removing file:', unlinkError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error uploading images'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Delete image
|
||||
router.delete('/:filename', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const filename = req.params.filename;
|
||||
const uploadPath = path.join(__dirname, '../public/uploads');
|
||||
|
||||
// Security check - ensure filename doesn't contain path traversal
|
||||
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid filename'
|
||||
});
|
||||
}
|
||||
|
||||
const filePath = path.join(uploadPath, filename);
|
||||
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
await fs.unlink(filePath);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Image deleted successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Image not found'
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Image deletion error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error deleting image'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// List uploaded images
|
||||
router.get('/list', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const uploadPath = path.join(__dirname, '../public/uploads');
|
||||
|
||||
// Create uploads directory if it doesn't exist
|
||||
try {
|
||||
await fs.mkdir(uploadPath, { recursive: true });
|
||||
} catch (mkdirError) {
|
||||
// Directory might already exist, continue
|
||||
}
|
||||
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 50;
|
||||
|
||||
let files;
|
||||
try {
|
||||
files = await fs.readdir(uploadPath);
|
||||
} catch (readdirError) {
|
||||
if (readdirError.code === 'ENOENT') {
|
||||
// Directory doesn't exist, return empty list
|
||||
return res.json({
|
||||
success: true,
|
||||
images: [],
|
||||
pagination: {
|
||||
current: 1,
|
||||
total: 0,
|
||||
limit,
|
||||
totalItems: 0,
|
||||
hasNext: false,
|
||||
hasPrev: false
|
||||
}
|
||||
});
|
||||
}
|
||||
throw readdirError;
|
||||
}
|
||||
|
||||
const imageFiles = files.filter(file =>
|
||||
/\.(jpg|jpeg|png|gif|webp|svg)$/i.test(file)
|
||||
);
|
||||
|
||||
// Sort files by modification time (newest first)
|
||||
const filesWithStats = await Promise.all(
|
||||
imageFiles.map(async (file) => {
|
||||
try {
|
||||
const filePath = path.join(uploadPath, file);
|
||||
const stats = await fs.stat(filePath);
|
||||
return { file, stats };
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const validFiles = filesWithStats
|
||||
.filter(item => item !== null)
|
||||
.sort((a, b) => b.stats.mtime - a.stats.mtime);
|
||||
|
||||
const total = validFiles.length;
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
const start = (page - 1) * limit;
|
||||
const end = start + limit;
|
||||
|
||||
const paginatedFiles = validFiles.slice(start, end);
|
||||
|
||||
const imagesWithDetails = await Promise.all(
|
||||
paginatedFiles.map(async ({ file, stats }) => {
|
||||
try {
|
||||
const filePath = path.join(uploadPath, file);
|
||||
|
||||
// Try to get image dimensions
|
||||
let dimensions = null;
|
||||
try {
|
||||
const metadata = await sharp(filePath).metadata();
|
||||
dimensions = {
|
||||
width: metadata.width,
|
||||
height: metadata.height
|
||||
};
|
||||
} catch (sharpError) {
|
||||
// Not a processable image, skip dimensions
|
||||
}
|
||||
|
||||
// Determine mime type from extension
|
||||
const ext = path.extname(file).toLowerCase();
|
||||
let mimetype = 'image/jpeg';
|
||||
switch (ext) {
|
||||
case '.png': mimetype = 'image/png'; break;
|
||||
case '.gif': mimetype = 'image/gif'; break;
|
||||
case '.webp': mimetype = 'image/webp'; break;
|
||||
case '.svg': mimetype = 'image/svg+xml'; break;
|
||||
}
|
||||
|
||||
return {
|
||||
filename: file,
|
||||
url: `/uploads/${file}`,
|
||||
size: stats.size,
|
||||
mimetype,
|
||||
uploadedAt: stats.birthtime || stats.mtime,
|
||||
modifiedAt: stats.mtime,
|
||||
dimensions,
|
||||
isImage: true
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error getting details for ${file}:`, error);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const validImages = imagesWithDetails.filter(img => img !== null);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
images: validImages,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
limit,
|
||||
totalItems: total,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('List images error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error listing images'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
415
.history/routes/media_20251022052247.js
Normal file
415
.history/routes/media_20251022052247.js
Normal file
@@ -0,0 +1,415 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const multer = require('multer');
|
||||
const sharp = require('sharp');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Authentication required'
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Configure multer for file uploads
|
||||
const storage = multer.diskStorage({
|
||||
destination: async (req, file, cb) => {
|
||||
const uploadPath = path.join(__dirname, '../public/uploads');
|
||||
try {
|
||||
await fs.mkdir(uploadPath, { recursive: true });
|
||||
cb(null, uploadPath);
|
||||
} catch (error) {
|
||||
cb(error);
|
||||
}
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
const ext = path.extname(file.originalname);
|
||||
cb(null, file.fieldname + '-' + uniqueSuffix + ext);
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: {
|
||||
fileSize: parseInt(process.env.MAX_FILE_SIZE) || 10 * 1024 * 1024, // 10MB
|
||||
files: 10
|
||||
},
|
||||
fileFilter: (req, file, cb) => {
|
||||
// Allow images only
|
||||
if (file.mimetype.startsWith('image/')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('Only image files are allowed'), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Upload single image
|
||||
router.post('/upload', requireAuth, upload.single('image'), async (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'No file uploaded'
|
||||
});
|
||||
}
|
||||
|
||||
const originalPath = req.file.path;
|
||||
const filename = req.file.filename;
|
||||
const nameWithoutExt = path.parse(filename).name;
|
||||
|
||||
// Create optimized versions
|
||||
const sizes = {
|
||||
thumbnail: { width: 300, height: 200 },
|
||||
medium: { width: 800, height: 600 },
|
||||
large: { width: 1200, height: 900 }
|
||||
};
|
||||
|
||||
const optimizedImages = {};
|
||||
|
||||
for (const [sizeName, dimensions] of Object.entries(sizes)) {
|
||||
const outputPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-${sizeName}.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.resize(dimensions.width, dimensions.height, {
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true
|
||||
})
|
||||
.webp({ quality: 85 })
|
||||
.toFile(outputPath);
|
||||
|
||||
optimizedImages[sizeName] = `/uploads/${path.basename(outputPath)}`;
|
||||
}
|
||||
|
||||
// Keep original as well (converted to webp for better compression)
|
||||
const originalWebpPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-original.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.webp({ quality: 90 })
|
||||
.toFile(originalWebpPath);
|
||||
|
||||
optimizedImages.original = `/uploads/${path.basename(originalWebpPath)}`;
|
||||
|
||||
// Remove the original uploaded file
|
||||
await fs.unlink(originalPath);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Image uploaded and optimized successfully',
|
||||
images: optimizedImages,
|
||||
metadata: {
|
||||
originalName: req.file.originalname,
|
||||
mimeType: req.file.mimetype,
|
||||
size: req.file.size
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Image upload error:', error);
|
||||
|
||||
// Clean up files on error
|
||||
if (req.file && req.file.path) {
|
||||
try {
|
||||
await fs.unlink(req.file.path);
|
||||
} catch (unlinkError) {
|
||||
console.error('Error removing file:', unlinkError);
|
||||
}
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error uploading image'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Upload multiple images
|
||||
router.post('/upload-multiple', requireAuth, upload.array('images', 10), async (req, res) => {
|
||||
try {
|
||||
if (!req.files || req.files.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'No files uploaded'
|
||||
});
|
||||
}
|
||||
|
||||
const uploadedImages = [];
|
||||
|
||||
for (const file of req.files) {
|
||||
try {
|
||||
const originalPath = file.path;
|
||||
const filename = file.filename;
|
||||
const nameWithoutExt = path.parse(filename).name;
|
||||
|
||||
// Create optimized versions
|
||||
const sizes = {
|
||||
thumbnail: { width: 300, height: 200 },
|
||||
medium: { width: 800, height: 600 },
|
||||
large: { width: 1200, height: 900 }
|
||||
};
|
||||
|
||||
const optimizedImages = {};
|
||||
|
||||
for (const [sizeName, dimensions] of Object.entries(sizes)) {
|
||||
const outputPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-${sizeName}.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.resize(dimensions.width, dimensions.height, {
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true
|
||||
})
|
||||
.webp({ quality: 85 })
|
||||
.toFile(outputPath);
|
||||
|
||||
optimizedImages[sizeName] = `/uploads/${path.basename(outputPath)}`;
|
||||
}
|
||||
|
||||
// Original as webp
|
||||
const originalWebpPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-original.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.webp({ quality: 90 })
|
||||
.toFile(originalWebpPath);
|
||||
|
||||
optimizedImages.original = `/uploads/${path.basename(originalWebpPath)}`;
|
||||
|
||||
uploadedImages.push({
|
||||
originalName: file.originalname,
|
||||
images: optimizedImages,
|
||||
metadata: {
|
||||
mimeType: file.mimetype,
|
||||
size: file.size
|
||||
}
|
||||
});
|
||||
|
||||
// Remove original file
|
||||
await fs.unlink(originalPath);
|
||||
} catch (fileError) {
|
||||
console.error(`Error processing file ${file.originalname}:`, fileError);
|
||||
// Clean up this file and continue with others
|
||||
try {
|
||||
await fs.unlink(file.path);
|
||||
} catch (unlinkError) {
|
||||
console.error('Error removing file:', unlinkError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `${uploadedImages.length} images uploaded and optimized successfully`,
|
||||
images: uploadedImages
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Multiple images upload error:', error);
|
||||
|
||||
// Clean up files on error
|
||||
if (req.files) {
|
||||
for (const file of req.files) {
|
||||
try {
|
||||
await fs.unlink(file.path);
|
||||
} catch (unlinkError) {
|
||||
console.error('Error removing file:', unlinkError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error uploading images'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Delete image
|
||||
router.delete('/:filename', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const filename = req.params.filename;
|
||||
const uploadPath = path.join(__dirname, '../public/uploads');
|
||||
|
||||
// Security check - ensure filename doesn't contain path traversal
|
||||
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid filename'
|
||||
});
|
||||
}
|
||||
|
||||
const filePath = path.join(uploadPath, filename);
|
||||
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
await fs.unlink(filePath);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Image deleted successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Image not found'
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Image deletion error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error deleting image'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// List uploaded images
|
||||
router.get('/list', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const uploadPath = path.join(__dirname, '../public/uploads');
|
||||
|
||||
// Create uploads directory if it doesn't exist
|
||||
try {
|
||||
await fs.mkdir(uploadPath, { recursive: true });
|
||||
} catch (mkdirError) {
|
||||
// Directory might already exist, continue
|
||||
}
|
||||
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 50;
|
||||
|
||||
let files;
|
||||
try {
|
||||
files = await fs.readdir(uploadPath);
|
||||
} catch (readdirError) {
|
||||
if (readdirError.code === 'ENOENT') {
|
||||
// Directory doesn't exist, return empty list
|
||||
return res.json({
|
||||
success: true,
|
||||
images: [],
|
||||
pagination: {
|
||||
current: 1,
|
||||
total: 0,
|
||||
limit,
|
||||
totalItems: 0,
|
||||
hasNext: false,
|
||||
hasPrev: false
|
||||
}
|
||||
});
|
||||
}
|
||||
throw readdirError;
|
||||
}
|
||||
|
||||
const imageFiles = files.filter(file =>
|
||||
/\.(jpg|jpeg|png|gif|webp|svg)$/i.test(file)
|
||||
);
|
||||
|
||||
// Sort files by modification time (newest first)
|
||||
const filesWithStats = await Promise.all(
|
||||
imageFiles.map(async (file) => {
|
||||
try {
|
||||
const filePath = path.join(uploadPath, file);
|
||||
const stats = await fs.stat(filePath);
|
||||
return { file, stats };
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const validFiles = filesWithStats
|
||||
.filter(item => item !== null)
|
||||
.sort((a, b) => b.stats.mtime - a.stats.mtime);
|
||||
|
||||
const total = validFiles.length;
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
const start = (page - 1) * limit;
|
||||
const end = start + limit;
|
||||
|
||||
const paginatedFiles = validFiles.slice(start, end);
|
||||
|
||||
const imagesWithDetails = await Promise.all(
|
||||
paginatedFiles.map(async ({ file, stats }) => {
|
||||
try {
|
||||
const filePath = path.join(uploadPath, file);
|
||||
|
||||
// Try to get image dimensions
|
||||
let dimensions = null;
|
||||
try {
|
||||
const metadata = await sharp(filePath).metadata();
|
||||
dimensions = {
|
||||
width: metadata.width,
|
||||
height: metadata.height
|
||||
};
|
||||
} catch (sharpError) {
|
||||
// Not a processable image, skip dimensions
|
||||
}
|
||||
|
||||
// Determine mime type from extension
|
||||
const ext = path.extname(file).toLowerCase();
|
||||
let mimetype = 'image/jpeg';
|
||||
switch (ext) {
|
||||
case '.png': mimetype = 'image/png'; break;
|
||||
case '.gif': mimetype = 'image/gif'; break;
|
||||
case '.webp': mimetype = 'image/webp'; break;
|
||||
case '.svg': mimetype = 'image/svg+xml'; break;
|
||||
}
|
||||
|
||||
return {
|
||||
filename: file,
|
||||
url: `/uploads/${file}`,
|
||||
size: stats.size,
|
||||
mimetype,
|
||||
uploadedAt: stats.birthtime || stats.mtime,
|
||||
modifiedAt: stats.mtime,
|
||||
dimensions,
|
||||
isImage: true
|
||||
};
|
||||
} catch (error) {
|
||||
console.error(`Error getting details for ${file}:`, error);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const validImages = imagesWithDetails.filter(img => img !== null);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
images: validImages,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
limit,
|
||||
totalItems: total,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('List images error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error listing images'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
733
.history/routes/media_20251022195139.js
Normal file
733
.history/routes/media_20251022195139.js
Normal file
@@ -0,0 +1,733 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const multer = require('multer');
|
||||
const sharp = require('sharp');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Authentication required'
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Configure multer for file uploads
|
||||
const storage = multer.diskStorage({
|
||||
destination: async (req, file, cb) => {
|
||||
const uploadPath = path.join(__dirname, '../public/uploads');
|
||||
try {
|
||||
await fs.mkdir(uploadPath, { recursive: true });
|
||||
cb(null, uploadPath);
|
||||
} catch (error) {
|
||||
cb(error);
|
||||
}
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
const ext = path.extname(file.originalname);
|
||||
cb(null, file.fieldname + '-' + uniqueSuffix + ext);
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: {
|
||||
fileSize: parseInt(process.env.MAX_FILE_SIZE) || 10 * 1024 * 1024, // 10MB
|
||||
files: 10
|
||||
},
|
||||
fileFilter: (req, file, cb) => {
|
||||
// Allow images only
|
||||
if (file.mimetype.startsWith('image/')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('Only image files are allowed'), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Upload single image
|
||||
router.post('/upload', requireAuth, upload.single('image'), async (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'No file uploaded'
|
||||
});
|
||||
}
|
||||
|
||||
const originalPath = req.file.path;
|
||||
const filename = req.file.filename;
|
||||
const nameWithoutExt = path.parse(filename).name;
|
||||
|
||||
// Create optimized versions
|
||||
const sizes = {
|
||||
thumbnail: { width: 300, height: 200 },
|
||||
medium: { width: 800, height: 600 },
|
||||
large: { width: 1200, height: 900 }
|
||||
};
|
||||
|
||||
const optimizedImages = {};
|
||||
|
||||
for (const [sizeName, dimensions] of Object.entries(sizes)) {
|
||||
const outputPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-${sizeName}.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.resize(dimensions.width, dimensions.height, {
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true
|
||||
})
|
||||
.webp({ quality: 85 })
|
||||
.toFile(outputPath);
|
||||
|
||||
optimizedImages[sizeName] = `/uploads/${path.basename(outputPath)}`;
|
||||
}
|
||||
|
||||
// Keep original as well (converted to webp for better compression)
|
||||
const originalWebpPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-original.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.webp({ quality: 90 })
|
||||
.toFile(originalWebpPath);
|
||||
|
||||
optimizedImages.original = `/uploads/${path.basename(originalWebpPath)}`;
|
||||
|
||||
// Remove the original uploaded file
|
||||
await fs.unlink(originalPath);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Image uploaded and optimized successfully',
|
||||
images: optimizedImages,
|
||||
metadata: {
|
||||
originalName: req.file.originalname,
|
||||
mimeType: req.file.mimetype,
|
||||
size: req.file.size
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Image upload error:', error);
|
||||
|
||||
// Clean up files on error
|
||||
if (req.file && req.file.path) {
|
||||
try {
|
||||
await fs.unlink(req.file.path);
|
||||
} catch (unlinkError) {
|
||||
console.error('Error removing file:', unlinkError);
|
||||
}
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error uploading image'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Upload multiple images
|
||||
router.post('/upload-multiple', requireAuth, upload.array('images', 10), async (req, res) => {
|
||||
try {
|
||||
if (!req.files || req.files.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'No files uploaded'
|
||||
});
|
||||
}
|
||||
|
||||
const uploadedImages = [];
|
||||
|
||||
for (const file of req.files) {
|
||||
try {
|
||||
const originalPath = file.path;
|
||||
const filename = file.filename;
|
||||
const nameWithoutExt = path.parse(filename).name;
|
||||
|
||||
// Create optimized versions
|
||||
const sizes = {
|
||||
thumbnail: { width: 300, height: 200 },
|
||||
medium: { width: 800, height: 600 },
|
||||
large: { width: 1200, height: 900 }
|
||||
};
|
||||
|
||||
const optimizedImages = {};
|
||||
|
||||
for (const [sizeName, dimensions] of Object.entries(sizes)) {
|
||||
const outputPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-${sizeName}.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.resize(dimensions.width, dimensions.height, {
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true
|
||||
})
|
||||
.webp({ quality: 85 })
|
||||
.toFile(outputPath);
|
||||
|
||||
optimizedImages[sizeName] = `/uploads/${path.basename(outputPath)}`;
|
||||
}
|
||||
|
||||
// Original as webp
|
||||
const originalWebpPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-original.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.webp({ quality: 90 })
|
||||
.toFile(originalWebpPath);
|
||||
|
||||
optimizedImages.original = `/uploads/${path.basename(originalWebpPath)}`;
|
||||
|
||||
uploadedImages.push({
|
||||
originalName: file.originalname,
|
||||
images: optimizedImages,
|
||||
metadata: {
|
||||
mimeType: file.mimetype,
|
||||
size: file.size
|
||||
}
|
||||
});
|
||||
|
||||
// Remove original file
|
||||
await fs.unlink(originalPath);
|
||||
} catch (fileError) {
|
||||
console.error(`Error processing file ${file.originalname}:`, fileError);
|
||||
// Clean up this file and continue with others
|
||||
try {
|
||||
await fs.unlink(file.path);
|
||||
} catch (unlinkError) {
|
||||
console.error('Error removing file:', unlinkError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `${uploadedImages.length} images uploaded and optimized successfully`,
|
||||
images: uploadedImages
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Multiple images upload error:', error);
|
||||
|
||||
// Clean up files on error
|
||||
if (req.files) {
|
||||
for (const file of req.files) {
|
||||
try {
|
||||
await fs.unlink(file.path);
|
||||
} catch (unlinkError) {
|
||||
console.error('Error removing file:', unlinkError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error uploading images'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Delete image
|
||||
router.delete('/:filename', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const filename = req.params.filename;
|
||||
const uploadPath = path.join(__dirname, '../public/uploads');
|
||||
|
||||
// Security check - ensure filename doesn't contain path traversal
|
||||
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid filename'
|
||||
});
|
||||
}
|
||||
|
||||
const filePath = path.join(uploadPath, filename);
|
||||
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
await fs.unlink(filePath);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Image deleted successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Image not found'
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Image deletion error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error deleting image'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// List uploaded images with advanced filtering and search
|
||||
router.get('/list', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const uploadPath = path.join(__dirname, '../public/uploads');
|
||||
|
||||
// Create uploads directory if it doesn't exist
|
||||
try {
|
||||
await fs.mkdir(uploadPath, { recursive: true });
|
||||
} catch (mkdirError) {
|
||||
// Directory might already exist, continue
|
||||
}
|
||||
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 24;
|
||||
const search = req.query.search?.toLowerCase() || '';
|
||||
const sortBy = req.query.sortBy || 'date'; // date, name, size
|
||||
const sortOrder = req.query.sortOrder || 'desc'; // asc, desc
|
||||
const fileType = req.query.fileType || 'all'; // all, image, video, document
|
||||
|
||||
let files;
|
||||
try {
|
||||
files = await fs.readdir(uploadPath);
|
||||
} catch (readdirError) {
|
||||
if (readdirError.code === 'ENOENT') {
|
||||
return res.json({
|
||||
success: true,
|
||||
images: [],
|
||||
pagination: {
|
||||
current: 1,
|
||||
total: 0,
|
||||
limit,
|
||||
totalItems: 0,
|
||||
hasNext: false,
|
||||
hasPrev: false
|
||||
},
|
||||
filters: {
|
||||
search: '',
|
||||
sortBy: 'date',
|
||||
sortOrder: 'desc',
|
||||
fileType: 'all'
|
||||
}
|
||||
});
|
||||
}
|
||||
throw readdirError;
|
||||
}
|
||||
|
||||
// Filter by file type
|
||||
let filteredFiles = files;
|
||||
if (fileType !== 'all') {
|
||||
const typePatterns = {
|
||||
image: /\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?)$/i,
|
||||
video: /\.(mp4|webm|avi|mov|mkv|wmv|flv)$/i,
|
||||
document: /\.(pdf|doc|docx|txt|rtf|odt)$/i
|
||||
};
|
||||
|
||||
const pattern = typePatterns[fileType];
|
||||
if (pattern) {
|
||||
filteredFiles = files.filter(file => pattern.test(file));
|
||||
}
|
||||
} else {
|
||||
// Only show supported media files
|
||||
filteredFiles = files.filter(file =>
|
||||
/\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?|mp4|webm|avi|mov|mkv|pdf|doc|docx)$/i.test(file)
|
||||
);
|
||||
}
|
||||
|
||||
// Apply search filter
|
||||
if (search) {
|
||||
filteredFiles = filteredFiles.filter(file =>
|
||||
file.toLowerCase().includes(search)
|
||||
);
|
||||
}
|
||||
|
||||
// Get file stats and create file objects
|
||||
const filesWithStats = await Promise.all(
|
||||
filteredFiles.map(async (file) => {
|
||||
try {
|
||||
const filePath = path.join(uploadPath, file);
|
||||
const stats = await fs.stat(filePath);
|
||||
return { file, stats, filePath };
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const validFiles = filesWithStats.filter(item => item !== null);
|
||||
|
||||
// Sort files
|
||||
validFiles.sort((a, b) => {
|
||||
let aValue, bValue;
|
||||
|
||||
switch (sortBy) {
|
||||
case 'name':
|
||||
aValue = a.file.toLowerCase();
|
||||
bValue = b.file.toLowerCase();
|
||||
break;
|
||||
case 'size':
|
||||
aValue = a.stats.size;
|
||||
bValue = b.stats.size;
|
||||
break;
|
||||
case 'date':
|
||||
default:
|
||||
aValue = a.stats.mtime;
|
||||
bValue = b.stats.mtime;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sortOrder === 'asc') {
|
||||
return aValue > bValue ? 1 : -1;
|
||||
} else {
|
||||
return aValue < bValue ? 1 : -1;
|
||||
}
|
||||
});
|
||||
|
||||
const total = validFiles.length;
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
const start = (page - 1) * limit;
|
||||
const end = start + limit;
|
||||
|
||||
const paginatedFiles = validFiles.slice(start, end);
|
||||
|
||||
const filesWithDetails = await Promise.all(
|
||||
paginatedFiles.map(async ({ file, stats, filePath }) => {
|
||||
try {
|
||||
const ext = path.extname(file).toLowerCase();
|
||||
let fileDetails = {
|
||||
filename: file,
|
||||
url: `/uploads/${file}`,
|
||||
size: stats.size,
|
||||
uploadedAt: stats.birthtime || stats.mtime,
|
||||
modifiedAt: stats.mtime,
|
||||
extension: ext,
|
||||
isImage: false,
|
||||
isVideo: false,
|
||||
isDocument: false
|
||||
};
|
||||
|
||||
// Determine file type and get additional info
|
||||
if (/\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?)$/i.test(file)) {
|
||||
fileDetails.isImage = true;
|
||||
fileDetails.mimetype = `image/${ext.replace('.', '')}`;
|
||||
|
||||
// Get image dimensions
|
||||
try {
|
||||
const metadata = await sharp(filePath).metadata();
|
||||
fileDetails.dimensions = {
|
||||
width: metadata.width,
|
||||
height: metadata.height
|
||||
};
|
||||
fileDetails.format = metadata.format;
|
||||
} catch (sharpError) {
|
||||
console.warn(`Could not get image metadata for ${file}`);
|
||||
}
|
||||
} else if (/\.(mp4|webm|avi|mov|mkv|wmv|flv)$/i.test(file)) {
|
||||
fileDetails.isVideo = true;
|
||||
fileDetails.mimetype = `video/${ext.replace('.', '')}`;
|
||||
} else if (/\.(pdf|doc|docx|txt|rtf|odt)$/i.test(file)) {
|
||||
fileDetails.isDocument = true;
|
||||
fileDetails.mimetype = `application/${ext.replace('.', '')}`;
|
||||
}
|
||||
|
||||
// Generate thumbnail for images
|
||||
if (fileDetails.isImage && !file.includes('-thumbnail.')) {
|
||||
const thumbnailPath = path.join(uploadPath, `${path.parse(file).name}-thumbnail.webp`);
|
||||
try {
|
||||
await fs.access(thumbnailPath);
|
||||
fileDetails.thumbnail = `/uploads/${path.basename(thumbnailPath)}`;
|
||||
} catch {
|
||||
// Thumbnail doesn't exist, create it
|
||||
try {
|
||||
await sharp(filePath)
|
||||
.resize(200, 150, {
|
||||
fit: 'cover',
|
||||
withoutEnlargement: false
|
||||
})
|
||||
.webp({ quality: 80 })
|
||||
.toFile(thumbnailPath);
|
||||
fileDetails.thumbnail = `/uploads/${path.basename(thumbnailPath)}`;
|
||||
} catch (thumbError) {
|
||||
console.warn(`Could not create thumbnail for ${file}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileDetails;
|
||||
} catch (error) {
|
||||
console.error(`Error getting details for ${file}:`, error);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const validMedia = filesWithDetails.filter(item => item !== null);
|
||||
|
||||
// Calculate storage stats
|
||||
const totalSize = validFiles.reduce((sum, file) => sum + file.stats.size, 0);
|
||||
const imageCount = validMedia.filter(f => f.isImage).length;
|
||||
const videoCount = validMedia.filter(f => f.isVideo).length;
|
||||
const documentCount = validMedia.filter(f => f.isDocument).length;
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
files: validMedia,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
limit,
|
||||
totalItems: total,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
filters: {
|
||||
search,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
fileType
|
||||
},
|
||||
stats: {
|
||||
totalFiles: total,
|
||||
totalSize,
|
||||
imageCount,
|
||||
videoCount,
|
||||
documentCount,
|
||||
formattedSize: formatFileSize(totalSize)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('List media error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error listing media files'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Create folder structure
|
||||
router.post('/folder', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { folderName } = req.body;
|
||||
|
||||
if (!folderName || !folderName.trim()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Folder name is required'
|
||||
});
|
||||
}
|
||||
|
||||
// Sanitize folder name
|
||||
const sanitizedName = folderName.trim().replace(/[^a-zA-Z0-9-_]/g, '-');
|
||||
const folderPath = path.join(__dirname, '../public/uploads', sanitizedName);
|
||||
|
||||
try {
|
||||
await fs.mkdir(folderPath, { recursive: true });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Folder created successfully',
|
||||
folderName: sanitizedName,
|
||||
folderPath: `/uploads/${sanitizedName}`
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'EEXIST') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Folder already exists'
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Create folder error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error creating folder'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get media file info
|
||||
router.get('/info/:filename', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const filename = req.params.filename;
|
||||
|
||||
// Security check
|
||||
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid filename'
|
||||
});
|
||||
}
|
||||
|
||||
const filePath = path.join(__dirname, '../public/uploads', filename);
|
||||
|
||||
try {
|
||||
const stats = await fs.stat(filePath);
|
||||
const ext = path.extname(filename).toLowerCase();
|
||||
|
||||
let fileInfo = {
|
||||
filename,
|
||||
url: `/uploads/${filename}`,
|
||||
size: stats.size,
|
||||
formattedSize: formatFileSize(stats.size),
|
||||
uploadedAt: stats.birthtime || stats.mtime,
|
||||
modifiedAt: stats.mtime,
|
||||
extension: ext,
|
||||
mimetype: getMimeType(ext)
|
||||
};
|
||||
|
||||
// Get additional info for images
|
||||
if (/\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?)$/i.test(filename)) {
|
||||
try {
|
||||
const metadata = await sharp(filePath).metadata();
|
||||
fileInfo.dimensions = {
|
||||
width: metadata.width,
|
||||
height: metadata.height
|
||||
};
|
||||
fileInfo.format = metadata.format;
|
||||
fileInfo.hasAlpha = metadata.hasAlpha;
|
||||
fileInfo.density = metadata.density;
|
||||
} catch (sharpError) {
|
||||
console.warn(`Could not get image metadata for ${filename}`);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
fileInfo
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'File not found'
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Get file info error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error getting file information'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Resize image
|
||||
router.post('/resize/:filename', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const filename = req.params.filename;
|
||||
const { width, height, quality = 85 } = req.body;
|
||||
|
||||
if (!width && !height) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Width or height must be specified'
|
||||
});
|
||||
}
|
||||
|
||||
// Security check
|
||||
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid filename'
|
||||
});
|
||||
}
|
||||
|
||||
const originalPath = path.join(__dirname, '../public/uploads', filename);
|
||||
const nameWithoutExt = path.parse(filename).name;
|
||||
const resizedPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-${width || 'auto'}x${height || 'auto'}.webp`
|
||||
);
|
||||
|
||||
try {
|
||||
let sharpInstance = sharp(originalPath);
|
||||
|
||||
if (width && height) {
|
||||
sharpInstance = sharpInstance.resize(parseInt(width), parseInt(height), {
|
||||
fit: 'cover'
|
||||
});
|
||||
} else if (width) {
|
||||
sharpInstance = sharpInstance.resize(parseInt(width));
|
||||
} else {
|
||||
sharpInstance = sharpInstance.resize(null, parseInt(height));
|
||||
}
|
||||
|
||||
await sharpInstance
|
||||
.webp({ quality: parseInt(quality) })
|
||||
.toFile(resizedPath);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Image resized successfully',
|
||||
originalFile: filename,
|
||||
resizedFile: path.basename(resizedPath),
|
||||
resizedUrl: `/uploads/${path.basename(resizedPath)}`
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Original file not found'
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Resize image error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error resizing image'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Utility functions
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
function getMimeType(ext) {
|
||||
const mimeTypes = {
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.png': 'image/png',
|
||||
'.gif': 'image/gif',
|
||||
'.webp': 'image/webp',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.bmp': 'image/bmp',
|
||||
'.tiff': 'image/tiff',
|
||||
'.tif': 'image/tiff',
|
||||
'.mp4': 'video/mp4',
|
||||
'.webm': 'video/webm',
|
||||
'.avi': 'video/x-msvideo',
|
||||
'.mov': 'video/quicktime',
|
||||
'.mkv': 'video/x-matroska',
|
||||
'.pdf': 'application/pdf',
|
||||
'.doc': 'application/msword',
|
||||
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
};
|
||||
|
||||
return mimeTypes[ext.toLowerCase()] || 'application/octet-stream';
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
733
.history/routes/media_20251022195905.js
Normal file
733
.history/routes/media_20251022195905.js
Normal file
@@ -0,0 +1,733 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const multer = require('multer');
|
||||
const sharp = require('sharp');
|
||||
const path = require('path');
|
||||
const fs = require('fs').promises;
|
||||
|
||||
// Authentication middleware
|
||||
const requireAuth = (req, res, next) => {
|
||||
if (!req.session.user) {
|
||||
return res.status(401).json({
|
||||
success: false,
|
||||
message: 'Authentication required'
|
||||
});
|
||||
}
|
||||
next();
|
||||
};
|
||||
|
||||
// Configure multer for file uploads
|
||||
const storage = multer.diskStorage({
|
||||
destination: async (req, file, cb) => {
|
||||
const uploadPath = path.join(__dirname, '../public/uploads');
|
||||
try {
|
||||
await fs.mkdir(uploadPath, { recursive: true });
|
||||
cb(null, uploadPath);
|
||||
} catch (error) {
|
||||
cb(error);
|
||||
}
|
||||
},
|
||||
filename: (req, file, cb) => {
|
||||
const uniqueSuffix = Date.now() + '-' + Math.round(Math.random() * 1E9);
|
||||
const ext = path.extname(file.originalname);
|
||||
cb(null, file.fieldname + '-' + uniqueSuffix + ext);
|
||||
}
|
||||
});
|
||||
|
||||
const upload = multer({
|
||||
storage: storage,
|
||||
limits: {
|
||||
fileSize: parseInt(process.env.MAX_FILE_SIZE) || 10 * 1024 * 1024, // 10MB
|
||||
files: 10
|
||||
},
|
||||
fileFilter: (req, file, cb) => {
|
||||
// Allow images only
|
||||
if (file.mimetype.startsWith('image/')) {
|
||||
cb(null, true);
|
||||
} else {
|
||||
cb(new Error('Only image files are allowed'), false);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
// Upload single image
|
||||
router.post('/upload', requireAuth, upload.single('image'), async (req, res) => {
|
||||
try {
|
||||
if (!req.file) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'No file uploaded'
|
||||
});
|
||||
}
|
||||
|
||||
const originalPath = req.file.path;
|
||||
const filename = req.file.filename;
|
||||
const nameWithoutExt = path.parse(filename).name;
|
||||
|
||||
// Create optimized versions
|
||||
const sizes = {
|
||||
thumbnail: { width: 300, height: 200 },
|
||||
medium: { width: 800, height: 600 },
|
||||
large: { width: 1200, height: 900 }
|
||||
};
|
||||
|
||||
const optimizedImages = {};
|
||||
|
||||
for (const [sizeName, dimensions] of Object.entries(sizes)) {
|
||||
const outputPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-${sizeName}.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.resize(dimensions.width, dimensions.height, {
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true
|
||||
})
|
||||
.webp({ quality: 85 })
|
||||
.toFile(outputPath);
|
||||
|
||||
optimizedImages[sizeName] = `/uploads/${path.basename(outputPath)}`;
|
||||
}
|
||||
|
||||
// Keep original as well (converted to webp for better compression)
|
||||
const originalWebpPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-original.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.webp({ quality: 90 })
|
||||
.toFile(originalWebpPath);
|
||||
|
||||
optimizedImages.original = `/uploads/${path.basename(originalWebpPath)}`;
|
||||
|
||||
// Remove the original uploaded file
|
||||
await fs.unlink(originalPath);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Image uploaded and optimized successfully',
|
||||
images: optimizedImages,
|
||||
metadata: {
|
||||
originalName: req.file.originalname,
|
||||
mimeType: req.file.mimetype,
|
||||
size: req.file.size
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Image upload error:', error);
|
||||
|
||||
// Clean up files on error
|
||||
if (req.file && req.file.path) {
|
||||
try {
|
||||
await fs.unlink(req.file.path);
|
||||
} catch (unlinkError) {
|
||||
console.error('Error removing file:', unlinkError);
|
||||
}
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error uploading image'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Upload multiple images
|
||||
router.post('/upload-multiple', requireAuth, upload.array('images', 10), async (req, res) => {
|
||||
try {
|
||||
if (!req.files || req.files.length === 0) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'No files uploaded'
|
||||
});
|
||||
}
|
||||
|
||||
const uploadedImages = [];
|
||||
|
||||
for (const file of req.files) {
|
||||
try {
|
||||
const originalPath = file.path;
|
||||
const filename = file.filename;
|
||||
const nameWithoutExt = path.parse(filename).name;
|
||||
|
||||
// Create optimized versions
|
||||
const sizes = {
|
||||
thumbnail: { width: 300, height: 200 },
|
||||
medium: { width: 800, height: 600 },
|
||||
large: { width: 1200, height: 900 }
|
||||
};
|
||||
|
||||
const optimizedImages = {};
|
||||
|
||||
for (const [sizeName, dimensions] of Object.entries(sizes)) {
|
||||
const outputPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-${sizeName}.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.resize(dimensions.width, dimensions.height, {
|
||||
fit: 'inside',
|
||||
withoutEnlargement: true
|
||||
})
|
||||
.webp({ quality: 85 })
|
||||
.toFile(outputPath);
|
||||
|
||||
optimizedImages[sizeName] = `/uploads/${path.basename(outputPath)}`;
|
||||
}
|
||||
|
||||
// Original as webp
|
||||
const originalWebpPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-original.webp`
|
||||
);
|
||||
|
||||
await sharp(originalPath)
|
||||
.webp({ quality: 90 })
|
||||
.toFile(originalWebpPath);
|
||||
|
||||
optimizedImages.original = `/uploads/${path.basename(originalWebpPath)}`;
|
||||
|
||||
uploadedImages.push({
|
||||
originalName: file.originalname,
|
||||
images: optimizedImages,
|
||||
metadata: {
|
||||
mimeType: file.mimetype,
|
||||
size: file.size
|
||||
}
|
||||
});
|
||||
|
||||
// Remove original file
|
||||
await fs.unlink(originalPath);
|
||||
} catch (fileError) {
|
||||
console.error(`Error processing file ${file.originalname}:`, fileError);
|
||||
// Clean up this file and continue with others
|
||||
try {
|
||||
await fs.unlink(file.path);
|
||||
} catch (unlinkError) {
|
||||
console.error('Error removing file:', unlinkError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: `${uploadedImages.length} images uploaded and optimized successfully`,
|
||||
images: uploadedImages
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Multiple images upload error:', error);
|
||||
|
||||
// Clean up files on error
|
||||
if (req.files) {
|
||||
for (const file of req.files) {
|
||||
try {
|
||||
await fs.unlink(file.path);
|
||||
} catch (unlinkError) {
|
||||
console.error('Error removing file:', unlinkError);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error uploading images'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Delete image
|
||||
router.delete('/:filename', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const filename = req.params.filename;
|
||||
const uploadPath = path.join(__dirname, '../public/uploads');
|
||||
|
||||
// Security check - ensure filename doesn't contain path traversal
|
||||
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid filename'
|
||||
});
|
||||
}
|
||||
|
||||
const filePath = path.join(uploadPath, filename);
|
||||
|
||||
try {
|
||||
await fs.access(filePath);
|
||||
await fs.unlink(filePath);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Image deleted successfully'
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Image not found'
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Image deletion error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error deleting image'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// List uploaded images with advanced filtering and search
|
||||
router.get('/list', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const uploadPath = path.join(__dirname, '../public/uploads');
|
||||
|
||||
// Create uploads directory if it doesn't exist
|
||||
try {
|
||||
await fs.mkdir(uploadPath, { recursive: true });
|
||||
} catch (mkdirError) {
|
||||
// Directory might already exist, continue
|
||||
}
|
||||
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 24;
|
||||
const search = req.query.search?.toLowerCase() || '';
|
||||
const sortBy = req.query.sortBy || 'date'; // date, name, size
|
||||
const sortOrder = req.query.sortOrder || 'desc'; // asc, desc
|
||||
const fileType = req.query.fileType || 'all'; // all, image, video, document
|
||||
|
||||
let files;
|
||||
try {
|
||||
files = await fs.readdir(uploadPath);
|
||||
} catch (readdirError) {
|
||||
if (readdirError.code === 'ENOENT') {
|
||||
return res.json({
|
||||
success: true,
|
||||
images: [],
|
||||
pagination: {
|
||||
current: 1,
|
||||
total: 0,
|
||||
limit,
|
||||
totalItems: 0,
|
||||
hasNext: false,
|
||||
hasPrev: false
|
||||
},
|
||||
filters: {
|
||||
search: '',
|
||||
sortBy: 'date',
|
||||
sortOrder: 'desc',
|
||||
fileType: 'all'
|
||||
}
|
||||
});
|
||||
}
|
||||
throw readdirError;
|
||||
}
|
||||
|
||||
// Filter by file type
|
||||
let filteredFiles = files;
|
||||
if (fileType !== 'all') {
|
||||
const typePatterns = {
|
||||
image: /\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?)$/i,
|
||||
video: /\.(mp4|webm|avi|mov|mkv|wmv|flv)$/i,
|
||||
document: /\.(pdf|doc|docx|txt|rtf|odt)$/i
|
||||
};
|
||||
|
||||
const pattern = typePatterns[fileType];
|
||||
if (pattern) {
|
||||
filteredFiles = files.filter(file => pattern.test(file));
|
||||
}
|
||||
} else {
|
||||
// Only show supported media files
|
||||
filteredFiles = files.filter(file =>
|
||||
/\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?|mp4|webm|avi|mov|mkv|pdf|doc|docx)$/i.test(file)
|
||||
);
|
||||
}
|
||||
|
||||
// Apply search filter
|
||||
if (search) {
|
||||
filteredFiles = filteredFiles.filter(file =>
|
||||
file.toLowerCase().includes(search)
|
||||
);
|
||||
}
|
||||
|
||||
// Get file stats and create file objects
|
||||
const filesWithStats = await Promise.all(
|
||||
filteredFiles.map(async (file) => {
|
||||
try {
|
||||
const filePath = path.join(uploadPath, file);
|
||||
const stats = await fs.stat(filePath);
|
||||
return { file, stats, filePath };
|
||||
} catch (error) {
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const validFiles = filesWithStats.filter(item => item !== null);
|
||||
|
||||
// Sort files
|
||||
validFiles.sort((a, b) => {
|
||||
let aValue, bValue;
|
||||
|
||||
switch (sortBy) {
|
||||
case 'name':
|
||||
aValue = a.file.toLowerCase();
|
||||
bValue = b.file.toLowerCase();
|
||||
break;
|
||||
case 'size':
|
||||
aValue = a.stats.size;
|
||||
bValue = b.stats.size;
|
||||
break;
|
||||
case 'date':
|
||||
default:
|
||||
aValue = a.stats.mtime;
|
||||
bValue = b.stats.mtime;
|
||||
break;
|
||||
}
|
||||
|
||||
if (sortOrder === 'asc') {
|
||||
return aValue > bValue ? 1 : -1;
|
||||
} else {
|
||||
return aValue < bValue ? 1 : -1;
|
||||
}
|
||||
});
|
||||
|
||||
const total = validFiles.length;
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
const start = (page - 1) * limit;
|
||||
const end = start + limit;
|
||||
|
||||
const paginatedFiles = validFiles.slice(start, end);
|
||||
|
||||
const filesWithDetails = await Promise.all(
|
||||
paginatedFiles.map(async ({ file, stats, filePath }) => {
|
||||
try {
|
||||
const ext = path.extname(file).toLowerCase();
|
||||
let fileDetails = {
|
||||
filename: file,
|
||||
url: `/uploads/${file}`,
|
||||
size: stats.size,
|
||||
uploadedAt: stats.birthtime || stats.mtime,
|
||||
modifiedAt: stats.mtime,
|
||||
extension: ext,
|
||||
isImage: false,
|
||||
isVideo: false,
|
||||
isDocument: false
|
||||
};
|
||||
|
||||
// Determine file type and get additional info
|
||||
if (/\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?)$/i.test(file)) {
|
||||
fileDetails.isImage = true;
|
||||
fileDetails.mimetype = `image/${ext.replace('.', '')}`;
|
||||
|
||||
// Get image dimensions
|
||||
try {
|
||||
const metadata = await sharp(filePath).metadata();
|
||||
fileDetails.dimensions = {
|
||||
width: metadata.width,
|
||||
height: metadata.height
|
||||
};
|
||||
fileDetails.format = metadata.format;
|
||||
} catch (sharpError) {
|
||||
console.warn(`Could not get image metadata for ${file}`);
|
||||
}
|
||||
} else if (/\.(mp4|webm|avi|mov|mkv|wmv|flv)$/i.test(file)) {
|
||||
fileDetails.isVideo = true;
|
||||
fileDetails.mimetype = `video/${ext.replace('.', '')}`;
|
||||
} else if (/\.(pdf|doc|docx|txt|rtf|odt)$/i.test(file)) {
|
||||
fileDetails.isDocument = true;
|
||||
fileDetails.mimetype = `application/${ext.replace('.', '')}`;
|
||||
}
|
||||
|
||||
// Generate thumbnail for images
|
||||
if (fileDetails.isImage && !file.includes('-thumbnail.')) {
|
||||
const thumbnailPath = path.join(uploadPath, `${path.parse(file).name}-thumbnail.webp`);
|
||||
try {
|
||||
await fs.access(thumbnailPath);
|
||||
fileDetails.thumbnail = `/uploads/${path.basename(thumbnailPath)}`;
|
||||
} catch {
|
||||
// Thumbnail doesn't exist, create it
|
||||
try {
|
||||
await sharp(filePath)
|
||||
.resize(200, 150, {
|
||||
fit: 'cover',
|
||||
withoutEnlargement: false
|
||||
})
|
||||
.webp({ quality: 80 })
|
||||
.toFile(thumbnailPath);
|
||||
fileDetails.thumbnail = `/uploads/${path.basename(thumbnailPath)}`;
|
||||
} catch (thumbError) {
|
||||
console.warn(`Could not create thumbnail for ${file}`);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return fileDetails;
|
||||
} catch (error) {
|
||||
console.error(`Error getting details for ${file}:`, error);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
);
|
||||
|
||||
const validMedia = filesWithDetails.filter(item => item !== null);
|
||||
|
||||
// Calculate storage stats
|
||||
const totalSize = validFiles.reduce((sum, file) => sum + file.stats.size, 0);
|
||||
const imageCount = validMedia.filter(f => f.isImage).length;
|
||||
const videoCount = validMedia.filter(f => f.isVideo).length;
|
||||
const documentCount = validMedia.filter(f => f.isDocument).length;
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
files: validMedia,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
limit,
|
||||
totalItems: total,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
},
|
||||
filters: {
|
||||
search,
|
||||
sortBy,
|
||||
sortOrder,
|
||||
fileType
|
||||
},
|
||||
stats: {
|
||||
totalFiles: total,
|
||||
totalSize,
|
||||
imageCount,
|
||||
videoCount,
|
||||
documentCount,
|
||||
formattedSize: formatFileSize(totalSize)
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('List media error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error listing media files'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Create folder structure
|
||||
router.post('/folder', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const { folderName } = req.body;
|
||||
|
||||
if (!folderName || !folderName.trim()) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Folder name is required'
|
||||
});
|
||||
}
|
||||
|
||||
// Sanitize folder name
|
||||
const sanitizedName = folderName.trim().replace(/[^a-zA-Z0-9-_]/g, '-');
|
||||
const folderPath = path.join(__dirname, '../public/uploads', sanitizedName);
|
||||
|
||||
try {
|
||||
await fs.mkdir(folderPath, { recursive: true });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Folder created successfully',
|
||||
folderName: sanitizedName,
|
||||
folderPath: `/uploads/${sanitizedName}`
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'EEXIST') {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Folder already exists'
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Create folder error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error creating folder'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get media file info
|
||||
router.get('/info/:filename', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const filename = req.params.filename;
|
||||
|
||||
// Security check
|
||||
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid filename'
|
||||
});
|
||||
}
|
||||
|
||||
const filePath = path.join(__dirname, '../public/uploads', filename);
|
||||
|
||||
try {
|
||||
const stats = await fs.stat(filePath);
|
||||
const ext = path.extname(filename).toLowerCase();
|
||||
|
||||
let fileInfo = {
|
||||
filename,
|
||||
url: `/uploads/${filename}`,
|
||||
size: stats.size,
|
||||
formattedSize: formatFileSize(stats.size),
|
||||
uploadedAt: stats.birthtime || stats.mtime,
|
||||
modifiedAt: stats.mtime,
|
||||
extension: ext,
|
||||
mimetype: getMimeType(ext)
|
||||
};
|
||||
|
||||
// Get additional info for images
|
||||
if (/\.(jpg|jpeg|png|gif|webp|svg|bmp|tiff?)$/i.test(filename)) {
|
||||
try {
|
||||
const metadata = await sharp(filePath).metadata();
|
||||
fileInfo.dimensions = {
|
||||
width: metadata.width,
|
||||
height: metadata.height
|
||||
};
|
||||
fileInfo.format = metadata.format;
|
||||
fileInfo.hasAlpha = metadata.hasAlpha;
|
||||
fileInfo.density = metadata.density;
|
||||
} catch (sharpError) {
|
||||
console.warn(`Could not get image metadata for ${filename}`);
|
||||
}
|
||||
}
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
fileInfo
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'File not found'
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Get file info error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error getting file information'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Resize image
|
||||
router.post('/resize/:filename', requireAuth, async (req, res) => {
|
||||
try {
|
||||
const filename = req.params.filename;
|
||||
const { width, height, quality = 85 } = req.body;
|
||||
|
||||
if (!width && !height) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Width or height must be specified'
|
||||
});
|
||||
}
|
||||
|
||||
// Security check
|
||||
if (filename.includes('..') || filename.includes('/') || filename.includes('\\')) {
|
||||
return res.status(400).json({
|
||||
success: false,
|
||||
message: 'Invalid filename'
|
||||
});
|
||||
}
|
||||
|
||||
const originalPath = path.join(__dirname, '../public/uploads', filename);
|
||||
const nameWithoutExt = path.parse(filename).name;
|
||||
const resizedPath = path.join(
|
||||
path.dirname(originalPath),
|
||||
`${nameWithoutExt}-${width || 'auto'}x${height || 'auto'}.webp`
|
||||
);
|
||||
|
||||
try {
|
||||
let sharpInstance = sharp(originalPath);
|
||||
|
||||
if (width && height) {
|
||||
sharpInstance = sharpInstance.resize(parseInt(width), parseInt(height), {
|
||||
fit: 'cover'
|
||||
});
|
||||
} else if (width) {
|
||||
sharpInstance = sharpInstance.resize(parseInt(width));
|
||||
} else {
|
||||
sharpInstance = sharpInstance.resize(null, parseInt(height));
|
||||
}
|
||||
|
||||
await sharpInstance
|
||||
.webp({ quality: parseInt(quality) })
|
||||
.toFile(resizedPath);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
message: 'Image resized successfully',
|
||||
originalFile: filename,
|
||||
resizedFile: path.basename(resizedPath),
|
||||
resizedUrl: `/uploads/${path.basename(resizedPath)}`
|
||||
});
|
||||
} catch (error) {
|
||||
if (error.code === 'ENOENT') {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Original file not found'
|
||||
});
|
||||
}
|
||||
throw error;
|
||||
}
|
||||
} catch (error) {
|
||||
console.error('Resize image error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error resizing image'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Utility functions
|
||||
function formatFileSize(bytes) {
|
||||
if (bytes === 0) return '0 Bytes';
|
||||
const k = 1024;
|
||||
const sizes = ['Bytes', 'KB', 'MB', 'GB'];
|
||||
const i = Math.floor(Math.log(bytes) / Math.log(k));
|
||||
return parseFloat((bytes / Math.pow(k, i)).toFixed(2)) + ' ' + sizes[i];
|
||||
}
|
||||
|
||||
function getMimeType(ext) {
|
||||
const mimeTypes = {
|
||||
'.jpg': 'image/jpeg',
|
||||
'.jpeg': 'image/jpeg',
|
||||
'.png': 'image/png',
|
||||
'.gif': 'image/gif',
|
||||
'.webp': 'image/webp',
|
||||
'.svg': 'image/svg+xml',
|
||||
'.bmp': 'image/bmp',
|
||||
'.tiff': 'image/tiff',
|
||||
'.tif': 'image/tiff',
|
||||
'.mp4': 'video/mp4',
|
||||
'.webm': 'video/webm',
|
||||
'.avi': 'video/x-msvideo',
|
||||
'.mov': 'video/quicktime',
|
||||
'.mkv': 'video/x-matroska',
|
||||
'.pdf': 'application/pdf',
|
||||
'.doc': 'application/msword',
|
||||
'.docx': 'application/vnd.openxmlformats-officedocument.wordprocessingml.document'
|
||||
};
|
||||
|
||||
return mimeTypes[ext.toLowerCase()] || 'application/octet-stream';
|
||||
}
|
||||
|
||||
module.exports = router;
|
||||
197
.history/routes/portfolio_20251019203104.js
Normal file
197
.history/routes/portfolio_20251019203104.js
Normal file
@@ -0,0 +1,197 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Get all portfolio items
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
const search = req.query.search;
|
||||
const featured = req.query.featured;
|
||||
|
||||
// Build query
|
||||
let query = { isPublished: true };
|
||||
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
if (featured === 'true') {
|
||||
query.featured = true;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
query.$text = { $search: 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)
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
limit,
|
||||
totalItems: total,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get single portfolio item
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
relatedProjects
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get portfolio categories
|
||||
router.get('/meta/categories', async (req, res) => {
|
||||
try {
|
||||
const categories = await Portfolio.distinct('category', { isPublished: true });
|
||||
|
||||
// Get count for each category
|
||||
const categoriesWithCount = await Promise.all(
|
||||
categories.map(async (category) => {
|
||||
const count = await Portfolio.countDocuments({
|
||||
category,
|
||||
isPublished: true
|
||||
});
|
||||
return { category, count };
|
||||
})
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
categories: categoriesWithCount
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio categories API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching categories'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Like portfolio item
|
||||
router.post('/:id/like', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
portfolio.likes += 1;
|
||||
await portfolio.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
likes: portfolio.likes
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio like API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error liking portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Search portfolio
|
||||
router.get('/search/:term', async (req, res) => {
|
||||
try {
|
||||
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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
searchTerm,
|
||||
count: portfolio.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio search API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error searching portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
197
.history/routes/portfolio_20251019203147.js
Normal file
197
.history/routes/portfolio_20251019203147.js
Normal file
@@ -0,0 +1,197 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Get all portfolio items
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
const search = req.query.search;
|
||||
const featured = req.query.featured;
|
||||
|
||||
// Build query
|
||||
let query = { isPublished: true };
|
||||
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
if (featured === 'true') {
|
||||
query.featured = true;
|
||||
}
|
||||
|
||||
if (search) {
|
||||
query.$text = { $search: 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)
|
||||
]);
|
||||
|
||||
const totalPages = Math.ceil(total / limit);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
limit,
|
||||
totalItems: total,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get single portfolio item
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
relatedProjects
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get portfolio categories
|
||||
router.get('/meta/categories', async (req, res) => {
|
||||
try {
|
||||
const categories = await Portfolio.distinct('category', { isPublished: true });
|
||||
|
||||
// Get count for each category
|
||||
const categoriesWithCount = await Promise.all(
|
||||
categories.map(async (category) => {
|
||||
const count = await Portfolio.countDocuments({
|
||||
category,
|
||||
isPublished: true
|
||||
});
|
||||
return { category, count };
|
||||
})
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
categories: categoriesWithCount
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio categories API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching categories'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Like portfolio item
|
||||
router.post('/:id/like', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
portfolio.likes += 1;
|
||||
await portfolio.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
likes: portfolio.likes
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio like API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error liking portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Search portfolio
|
||||
router.get('/search/:term', async (req, res) => {
|
||||
try {
|
||||
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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
searchTerm,
|
||||
count: portfolio.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio search API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error searching portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
206
.history/routes/portfolio_20251019204338.js
Normal file
206
.history/routes/portfolio_20251019204338.js
Normal file
@@ -0,0 +1,206 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Get all portfolio items
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
const search = req.query.search;
|
||||
const featured = req.query.featured;
|
||||
|
||||
// Build query
|
||||
let whereClause = { isPublished: true };
|
||||
|
||||
if (category && category !== 'all') {
|
||||
whereClause.category = category;
|
||||
}
|
||||
|
||||
if (featured === 'true') {
|
||||
whereClause.featured = true;
|
||||
}
|
||||
|
||||
if (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.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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
limit,
|
||||
totalItems: total,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get single portfolio item
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
relatedProjects
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get portfolio categories
|
||||
router.get('/meta/categories', async (req, res) => {
|
||||
try {
|
||||
const categories = await Portfolio.distinct('category', { isPublished: true });
|
||||
|
||||
// Get count for each category
|
||||
const categoriesWithCount = await Promise.all(
|
||||
categories.map(async (category) => {
|
||||
const count = await Portfolio.countDocuments({
|
||||
category,
|
||||
isPublished: true
|
||||
});
|
||||
return { category, count };
|
||||
})
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
categories: categoriesWithCount
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio categories API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching categories'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Like portfolio item
|
||||
router.post('/:id/like', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
portfolio.likes += 1;
|
||||
await portfolio.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
likes: portfolio.likes
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio like API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error liking portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Search portfolio
|
||||
router.get('/search/:term', async (req, res) => {
|
||||
try {
|
||||
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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
searchTerm,
|
||||
count: portfolio.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio search API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error searching portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
206
.history/routes/portfolio_20251019204352.js
Normal file
206
.history/routes/portfolio_20251019204352.js
Normal file
@@ -0,0 +1,206 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Get all portfolio items
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
const search = req.query.search;
|
||||
const featured = req.query.featured;
|
||||
|
||||
// Build query
|
||||
let whereClause = { isPublished: true };
|
||||
|
||||
if (category && category !== 'all') {
|
||||
whereClause.category = category;
|
||||
}
|
||||
|
||||
if (featured === 'true') {
|
||||
whereClause.featured = true;
|
||||
}
|
||||
|
||||
if (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.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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
limit,
|
||||
totalItems: total,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get single portfolio item
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
relatedProjects
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get portfolio categories
|
||||
router.get('/meta/categories', async (req, res) => {
|
||||
try {
|
||||
const categories = await Portfolio.distinct('category', { isPublished: true });
|
||||
|
||||
// Get count for each category
|
||||
const categoriesWithCount = await Promise.all(
|
||||
categories.map(async (category) => {
|
||||
const count = await Portfolio.countDocuments({
|
||||
category,
|
||||
isPublished: true
|
||||
});
|
||||
return { category, count };
|
||||
})
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
categories: categoriesWithCount
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio categories API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching categories'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Like portfolio item
|
||||
router.post('/:id/like', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
portfolio.likes += 1;
|
||||
await portfolio.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
likes: portfolio.likes
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio like API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error liking portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Search portfolio
|
||||
router.get('/search/:term', async (req, res) => {
|
||||
try {
|
||||
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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
searchTerm,
|
||||
count: portfolio.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio search API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error searching portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
208
.history/routes/portfolio_20251019204403.js
Normal file
208
.history/routes/portfolio_20251019204403.js
Normal file
@@ -0,0 +1,208 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Get all portfolio items
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
const search = req.query.search;
|
||||
const featured = req.query.featured;
|
||||
|
||||
// Build query
|
||||
let whereClause = { isPublished: true };
|
||||
|
||||
if (category && category !== 'all') {
|
||||
whereClause.category = category;
|
||||
}
|
||||
|
||||
if (featured === 'true') {
|
||||
whereClause.featured = true;
|
||||
}
|
||||
|
||||
if (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.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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
limit,
|
||||
totalItems: total,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get single portfolio item
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
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,
|
||||
portfolio,
|
||||
relatedProjects
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get portfolio categories
|
||||
router.get('/meta/categories', async (req, res) => {
|
||||
try {
|
||||
const categories = await Portfolio.distinct('category', { isPublished: true });
|
||||
|
||||
// Get count for each category
|
||||
const categoriesWithCount = await Promise.all(
|
||||
categories.map(async (category) => {
|
||||
const count = await Portfolio.countDocuments({
|
||||
category,
|
||||
isPublished: true
|
||||
});
|
||||
return { category, count };
|
||||
})
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
categories: categoriesWithCount
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio categories API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching categories'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Like portfolio item
|
||||
router.post('/:id/like', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findById(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
portfolio.likes += 1;
|
||||
await portfolio.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
likes: portfolio.likes
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio like API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error liking portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Search portfolio
|
||||
router.get('/search/:term', async (req, res) => {
|
||||
try {
|
||||
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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
searchTerm,
|
||||
count: portfolio.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio search API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error searching portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
208
.history/routes/portfolio_20251019204418.js
Normal file
208
.history/routes/portfolio_20251019204418.js
Normal file
@@ -0,0 +1,208 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Get all portfolio items
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
const search = req.query.search;
|
||||
const featured = req.query.featured;
|
||||
|
||||
// Build query
|
||||
let whereClause = { isPublished: true };
|
||||
|
||||
if (category && category !== 'all') {
|
||||
whereClause.category = category;
|
||||
}
|
||||
|
||||
if (featured === 'true') {
|
||||
whereClause.featured = true;
|
||||
}
|
||||
|
||||
if (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.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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
limit,
|
||||
totalItems: total,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get single portfolio item
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
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,
|
||||
portfolio,
|
||||
relatedProjects
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get portfolio categories
|
||||
router.get('/meta/categories', async (req, res) => {
|
||||
try {
|
||||
const categories = await Portfolio.distinct('category', { isPublished: true });
|
||||
|
||||
// Get count for each category
|
||||
const categoriesWithCount = await Promise.all(
|
||||
categories.map(async (category) => {
|
||||
const count = await Portfolio.countDocuments({
|
||||
category,
|
||||
isPublished: true
|
||||
});
|
||||
return { category, count };
|
||||
})
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
categories: categoriesWithCount
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio categories API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching categories'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Like portfolio item
|
||||
router.post('/:id/like', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
portfolio.likes += 1;
|
||||
await portfolio.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
likes: portfolio.likes
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio like API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error liking portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Search portfolio
|
||||
router.get('/search/:term', async (req, res) => {
|
||||
try {
|
||||
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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
searchTerm,
|
||||
count: portfolio.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio search API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error searching portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
209
.history/routes/portfolio_20251019204435.js
Normal file
209
.history/routes/portfolio_20251019204435.js
Normal file
@@ -0,0 +1,209 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Get all portfolio items
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
const search = req.query.search;
|
||||
const featured = req.query.featured;
|
||||
|
||||
// Build query
|
||||
let whereClause = { isPublished: true };
|
||||
|
||||
if (category && category !== 'all') {
|
||||
whereClause.category = category;
|
||||
}
|
||||
|
||||
if (featured === 'true') {
|
||||
whereClause.featured = true;
|
||||
}
|
||||
|
||||
if (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.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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
limit,
|
||||
totalItems: total,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get single portfolio item
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
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,
|
||||
portfolio,
|
||||
relatedProjects
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get portfolio categories
|
||||
router.get('/meta/categories', async (req, res) => {
|
||||
try {
|
||||
const categories = await Portfolio.distinct('category', { isPublished: true });
|
||||
|
||||
// Get count for each category
|
||||
const categoriesWithCount = await Promise.all(
|
||||
categories.map(async (category) => {
|
||||
const count = await Portfolio.countDocuments({
|
||||
category,
|
||||
isPublished: true
|
||||
});
|
||||
return { category, count };
|
||||
})
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
categories: categoriesWithCount
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio categories API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching categories'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Like portfolio item
|
||||
router.post('/:id/like', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
portfolio.likes += 1;
|
||||
await portfolio.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
likes: portfolio.likes
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio like API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error liking portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Search portfolio
|
||||
router.get('/search/:term', async (req, res) => {
|
||||
try {
|
||||
const searchTerm = req.params.term;
|
||||
const limit = parseInt(req.query.limit) || 10;
|
||||
|
||||
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,
|
||||
portfolio,
|
||||
searchTerm,
|
||||
count: portfolio.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio search API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error searching portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
209
.history/routes/portfolio_20251019204805.js
Normal file
209
.history/routes/portfolio_20251019204805.js
Normal file
@@ -0,0 +1,209 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Portfolio } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Get all portfolio items
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const page = parseInt(req.query.page) || 1;
|
||||
const limit = parseInt(req.query.limit) || 12;
|
||||
const skip = (page - 1) * limit;
|
||||
const category = req.query.category;
|
||||
const search = req.query.search;
|
||||
const featured = req.query.featured;
|
||||
|
||||
// Build query
|
||||
let whereClause = { isPublished: true };
|
||||
|
||||
if (category && category !== 'all') {
|
||||
whereClause.category = category;
|
||||
}
|
||||
|
||||
if (featured === 'true') {
|
||||
whereClause.featured = true;
|
||||
}
|
||||
|
||||
if (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.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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
portfolio,
|
||||
pagination: {
|
||||
current: page,
|
||||
total: totalPages,
|
||||
limit,
|
||||
totalItems: total,
|
||||
hasNext: page < totalPages,
|
||||
hasPrev: page > 1
|
||||
}
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get single portfolio item
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Increment view count
|
||||
portfolio.viewCount += 1;
|
||||
await portfolio.save();
|
||||
|
||||
// Get related projects
|
||||
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,
|
||||
portfolio,
|
||||
relatedProjects
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio detail API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get portfolio categories
|
||||
router.get('/meta/categories', async (req, res) => {
|
||||
try {
|
||||
const categories = await Portfolio.distinct('category', { isPublished: true });
|
||||
|
||||
// Get count for each category
|
||||
const categoriesWithCount = await Promise.all(
|
||||
categories.map(async (category) => {
|
||||
const count = await Portfolio.countDocuments({
|
||||
category,
|
||||
isPublished: true
|
||||
});
|
||||
return { category, count };
|
||||
})
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
categories: categoriesWithCount
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio categories API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching categories'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Like portfolio item
|
||||
router.post('/:id/like', async (req, res) => {
|
||||
try {
|
||||
const portfolio = await Portfolio.findByPk(req.params.id);
|
||||
|
||||
if (!portfolio || !portfolio.isPublished) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Portfolio item not found'
|
||||
});
|
||||
}
|
||||
|
||||
portfolio.likes += 1;
|
||||
await portfolio.save();
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
likes: portfolio.likes
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio like API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error liking portfolio item'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Search portfolio
|
||||
router.get('/search/:term', async (req, res) => {
|
||||
try {
|
||||
const searchTerm = req.params.term;
|
||||
const limit = parseInt(req.query.limit) || 10;
|
||||
|
||||
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,
|
||||
portfolio,
|
||||
searchTerm,
|
||||
count: portfolio.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Portfolio search API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error searching portfolio'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
141
.history/routes/services_20251019204450.js
Normal file
141
.history/routes/services_20251019204450.js
Normal file
@@ -0,0 +1,141 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Service, Portfolio } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Get all services
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const category = req.query.category;
|
||||
const featured = req.query.featured;
|
||||
|
||||
let query = { isActive: true };
|
||||
|
||||
if (category && category !== 'all') {
|
||||
query.category = category;
|
||||
}
|
||||
|
||||
if (featured === 'true') {
|
||||
query.featured = true;
|
||||
}
|
||||
|
||||
const services = await Service.find(query)
|
||||
.populate('portfolio', 'title images')
|
||||
.sort({ featured: -1, order: 1 });
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
services
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get single service
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findById(req.params.id)
|
||||
.populate('portfolio', 'title shortDescription images category');
|
||||
|
||||
if (!service || !service.isActive) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Get related services
|
||||
const relatedServices = await Service.find({
|
||||
_id: { $ne: service._id },
|
||||
category: service.category,
|
||||
isActive: true
|
||||
})
|
||||
.select('name shortDescription icon pricing')
|
||||
.limit(3);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
service,
|
||||
relatedServices
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service detail API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get service categories
|
||||
router.get('/meta/categories', async (req, res) => {
|
||||
try {
|
||||
const categories = await Service.distinct('category', { isActive: true });
|
||||
|
||||
// Get count for each category
|
||||
const categoriesWithCount = await Promise.all(
|
||||
categories.map(async (category) => {
|
||||
const count = await Service.countDocuments({
|
||||
category,
|
||||
isActive: true
|
||||
});
|
||||
return { category, count };
|
||||
})
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
categories: categoriesWithCount
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service categories API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching categories'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Search services
|
||||
router.get('/search/:term', async (req, res) => {
|
||||
try {
|
||||
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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
services,
|
||||
searchTerm,
|
||||
count: services.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service search API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error searching services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
142
.history/routes/services_20251019204458.js
Normal file
142
.history/routes/services_20251019204458.js
Normal file
@@ -0,0 +1,142 @@
|
||||
const express = require('express');
|
||||
const router = express.Router();
|
||||
const { Service, Portfolio } = require('../models');
|
||||
const { Op } = require('sequelize');
|
||||
|
||||
// Get all services
|
||||
router.get('/', async (req, res) => {
|
||||
try {
|
||||
const category = req.query.category;
|
||||
const featured = req.query.featured;
|
||||
|
||||
let whereClause = { isActive: true };
|
||||
|
||||
if (category && category !== 'all') {
|
||||
whereClause.category = category;
|
||||
}
|
||||
|
||||
if (featured === 'true') {
|
||||
whereClause.featured = true;
|
||||
}
|
||||
|
||||
const services = await Service.findAll({
|
||||
where: whereClause,
|
||||
order: [['featured', 'DESC'], ['order', 'ASC']]
|
||||
});
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
services
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Services API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get single service
|
||||
router.get('/:id', async (req, res) => {
|
||||
try {
|
||||
const service = await Service.findById(req.params.id)
|
||||
.populate('portfolio', 'title shortDescription images category');
|
||||
|
||||
if (!service || !service.isActive) {
|
||||
return res.status(404).json({
|
||||
success: false,
|
||||
message: 'Service not found'
|
||||
});
|
||||
}
|
||||
|
||||
// Get related services
|
||||
const relatedServices = await Service.find({
|
||||
_id: { $ne: service._id },
|
||||
category: service.category,
|
||||
isActive: true
|
||||
})
|
||||
.select('name shortDescription icon pricing')
|
||||
.limit(3);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
service,
|
||||
relatedServices
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service detail API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching service'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Get service categories
|
||||
router.get('/meta/categories', async (req, res) => {
|
||||
try {
|
||||
const categories = await Service.distinct('category', { isActive: true });
|
||||
|
||||
// Get count for each category
|
||||
const categoriesWithCount = await Promise.all(
|
||||
categories.map(async (category) => {
|
||||
const count = await Service.countDocuments({
|
||||
category,
|
||||
isActive: true
|
||||
});
|
||||
return { category, count };
|
||||
})
|
||||
);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
categories: categoriesWithCount
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service categories API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error fetching categories'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// Search services
|
||||
router.get('/search/:term', async (req, res) => {
|
||||
try {
|
||||
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);
|
||||
|
||||
res.json({
|
||||
success: true,
|
||||
services,
|
||||
searchTerm,
|
||||
count: services.length
|
||||
});
|
||||
} catch (error) {
|
||||
console.error('Service search API error:', error);
|
||||
res.status(500).json({
|
||||
success: false,
|
||||
message: 'Error searching services'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user