From 1129489750ff96787c9151251cd3644176f068eb Mon Sep 17 00:00:00 2001 From: 2ManyProjects Date: Wed, 7 May 2025 13:04:33 -0500 Subject: [PATCH] SEO optimization, site map generator for dynamic pages --- .gitignore | 3 +- backend/src/index.js | 32 ++++++- backend/src/services/sitemapService.js | 124 +++++++++++++++++++++++++ 3 files changed, 154 insertions(+), 5 deletions(-) create mode 100644 backend/src/services/sitemapService.js diff --git a/.gitignore b/.gitignore index 4ec5b20..271b5e3 100644 --- a/.gitignore +++ b/.gitignore @@ -3,4 +3,5 @@ node_modules npm-debug.log yarn-error.log .DS_Store -backend/public/uploads/* \ No newline at end of file +backend/public/uploads/* +backend/public/* \ No newline at end of file diff --git a/backend/src/index.js b/backend/src/index.js index ad7a5c4..014962c 100644 --- a/backend/src/index.js +++ b/backend/src/index.js @@ -12,6 +12,7 @@ const SystemSettings = require('./models/SystemSettings'); const fs = require('fs'); // services const notificationService = require('./services/notificationService'); +const siteMapService = require('./services/sitemapService'); const emailService = require('./services/emailService'); const storageService = require('./services/storageService'); @@ -99,6 +100,9 @@ if (!fs.existsSync(blogImagesDir)) { // fileSize: 5 * 1024 * 1024 // 5MB limit // } // }); + + + const upload = storageService.getUploadMiddleware(); pool.connect() @@ -119,27 +123,47 @@ pool.connect() }) .catch(err => console.error('Database connection error:', err)) .finally((() => { + setupRepeatedChecks(pool, query); + })); + + + async function setupRepeatedChecks(p, q){ let timeInterval = 60 * 1000; + let siteGeneratorInterval = 60 * 1000; if (config.environment === 'prod') { - console.log('Setting up scheduled tasks for production environment, 10 mins'); + console.log('Setting up scheduled tasks for production environment, 10 mins, 60 mins'); timeInterval = 10 * 60 * 1000; + siteGeneratorInterval = 60 * 60 * 1000; }else { - console.log('Setting up scheduled tasks for development environment, 2 mins'); + console.log('Setting up scheduled tasks for development environment, 2 mins, 10 mins'); timeInterval = 2 * 60 * 1000; + siteGeneratorInterval = 10 * 60 * 1000; } // Process stock notifications every X minutes setInterval(async () => { try { console.log('Processing low stock notifications...'); - const processedCount = await notificationService.processLowStockNotifications(pool, query); + const processedCount = await notificationService.processLowStockNotifications(p, q); console.log(`Processed ${processedCount} low stock notifications`); } catch (error) { console.error('Error processing low stock notifications:', error); } }, timeInterval); - })); + console.log('Processing sitemap...'); + siteMapService.generateSitemap(p, q); + console.log(`Sitemap generated`); + setInterval(async () => { + try { + console.log('Processing sitemap...'); + await siteMapService.generateSitemap(p, q); + console.log(`Sitemap generated`); + } catch (error) { + console.error('Error processing low stock notifications:', error); + } + }, timeInterval); + } // Handle SSL proxy headers app.use((req, res, next) => { diff --git a/backend/src/services/sitemapService.js b/backend/src/services/sitemapService.js new file mode 100644 index 0000000..bbf1b28 --- /dev/null +++ b/backend/src/services/sitemapService.js @@ -0,0 +1,124 @@ +const config = require('../config'); +const path = require('path'); +const fs = require('fs'); + +/** + * Service for updating site map for dynamic content + */ +const siteMapService = { + /** + * Process site and output SiteMap + robot.txt + * @param {Object} pool - Database connection pool + * @param {Function} query - Database query function + */ + async generateSitemap(pool, query) { + const client = await pool.connect(); + try { + await client.query('BEGIN'); + console.log('Generating sitemap...'); + const siteDomain = config.site.domain; + const protocol = config.site.protocol === 'prod' ? 'https' : 'http'; + const baseUrl = `${protocol}://${siteDomain}`; + let sitemap = ` + + + + ${baseUrl}/ + weekly + 1.0 + + + ${baseUrl}/products + daily + 0.9 + + + ${baseUrl}/blog + weekly + 0.8 + `; + + // Get all products + const productsResult = await client.query(`SELECT id, updated_at FROM products WHERE is_active = true`); + + // Add product URLs + for (const product of productsResult.rows) { + const lastmod = new Date(product.updated_at).toISOString().split('T')[0]; + sitemap += ` + ${baseUrl}/products/${product.id} + ${lastmod} + weekly + 0.7 + + `; + } + + // Get all published blog posts + const blogResult = await client.query(` + SELECT slug, updated_at FROM blog_posts WHERE status = 'published' + `); + + // Add blog post URLs + for (const post of blogResult.rows) { + const lastmod = new Date(post.updated_at).toISOString().split('T')[0]; + sitemap += ` + ${baseUrl}/blog/${post.slug} + ${lastmod} + monthly + 0.6 + + `; + } + + // Get product categories + const categoriesResult = await client.query(`SELECT name FROM product_categories`); + + // Add category URLs (assuming they use name as the identifier in the URL) + for (const category of categoriesResult.rows) { + sitemap += ` + ${baseUrl}/products?category=${encodeURIComponent(category.name)} + weekly + 0.5 + `; + } + + sitemap += ``; + + // Write sitemap to file + const publicDir = path.join(__dirname, '../../public'); + if (!fs.existsSync(publicDir)) { + fs.mkdirSync(publicDir, { recursive: true }); + } + + fs.writeFileSync(path.join(publicDir, 'sitemap.xml'), sitemap); + console.log('Sitemap generated successfully at public/sitemap.xml'); + + // Create robots.txt if it doesn't exist + const robotsTxtPath = path.join(publicDir, 'robots.txt'); + if (!fs.existsSync(robotsTxtPath)) { + const robotsTxt = `# robots.txt for ${siteDomain} + User-agent: * + Disallow: /admin/ + Disallow: /auth/ + Disallow: /checkout/ + Disallow: /account/ + Disallow: /verify + + # Allow sitemaps + Sitemap: ${baseUrl}/sitemap.xml + `; + fs.writeFileSync(robotsTxtPath, robotsTxt); + console.log('robots.txt created successfully'); + } + await client.query('COMMIT'); + + } catch (error) { + await client.query('ROLLBACK'); + console.error('Error generating sitemap:', error); + } finally { + client.release(); + } + } +}; + +module.exports = siteMapService; \ No newline at end of file