some fixes

This commit is contained in:
2025-10-22 21:22:44 +09:00
parent 46fad7ecc2
commit 6ff35e26f4
514 changed files with 156165 additions and 0 deletions

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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;

View 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