SEO optimization, site map generator for dynamic pages

This commit is contained in:
2ManyProjects 2025-05-07 13:04:33 -05:00
parent 36cb50e9db
commit 1129489750
3 changed files with 154 additions and 5 deletions

1
.gitignore vendored
View file

@ -4,3 +4,4 @@ npm-debug.log
yarn-error.log yarn-error.log
.DS_Store .DS_Store
backend/public/uploads/* backend/public/uploads/*
backend/public/*

View file

@ -12,6 +12,7 @@ const SystemSettings = require('./models/SystemSettings');
const fs = require('fs'); const fs = require('fs');
// services // services
const notificationService = require('./services/notificationService'); const notificationService = require('./services/notificationService');
const siteMapService = require('./services/sitemapService');
const emailService = require('./services/emailService'); const emailService = require('./services/emailService');
const storageService = require('./services/storageService'); const storageService = require('./services/storageService');
@ -99,6 +100,9 @@ if (!fs.existsSync(blogImagesDir)) {
// fileSize: 5 * 1024 * 1024 // 5MB limit // fileSize: 5 * 1024 * 1024 // 5MB limit
// } // }
// }); // });
const upload = storageService.getUploadMiddleware(); const upload = storageService.getUploadMiddleware();
pool.connect() pool.connect()
@ -119,27 +123,47 @@ pool.connect()
}) })
.catch(err => console.error('Database connection error:', err)) .catch(err => console.error('Database connection error:', err))
.finally((() => { .finally((() => {
setupRepeatedChecks(pool, query);
}));
async function setupRepeatedChecks(p, q){
let timeInterval = 60 * 1000; let timeInterval = 60 * 1000;
let siteGeneratorInterval = 60 * 1000;
if (config.environment === 'prod') { 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; timeInterval = 10 * 60 * 1000;
siteGeneratorInterval = 60 * 60 * 1000;
}else { }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; timeInterval = 2 * 60 * 1000;
siteGeneratorInterval = 10 * 60 * 1000;
} }
// Process stock notifications every X minutes // Process stock notifications every X minutes
setInterval(async () => { setInterval(async () => {
try { try {
console.log('Processing low stock notifications...'); 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`); console.log(`Processed ${processedCount} low stock notifications`);
} catch (error) { } catch (error) {
console.error('Error processing low stock notifications:', error); console.error('Error processing low stock notifications:', error);
} }
}, timeInterval); }, 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 // Handle SSL proxy headers
app.use((req, res, next) => { app.use((req, res, next) => {

View file

@ -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 = `<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<!-- Static pages -->
<url>
<loc>${baseUrl}/</loc>
<changefreq>weekly</changefreq>
<priority>1.0</priority>
</url>
<url>
<loc>${baseUrl}/products</loc>
<changefreq>daily</changefreq>
<priority>0.9</priority>
</url>
<url>
<loc>${baseUrl}/blog</loc>
<changefreq>weekly</changefreq>
<priority>0.8</priority>
</url>`;
// 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 += ` <url>
<loc>${baseUrl}/products/${product.id}</loc>
<lastmod>${lastmod}</lastmod>
<changefreq>weekly</changefreq>
<priority>0.7</priority>
</url>
`;
}
// 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 += ` <url>
<loc>${baseUrl}/blog/${post.slug}</loc>
<lastmod>${lastmod}</lastmod>
<changefreq>monthly</changefreq>
<priority>0.6</priority>
</url>
`;
}
// 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 += ` <url>
<loc>${baseUrl}/products?category=${encodeURIComponent(category.name)}</loc>
<changefreq>weekly</changefreq>
<priority>0.5</priority>
</url>`;
}
sitemap += `</urlset>`;
// 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;