SEO optimization, site map generator for dynamic pages
This commit is contained in:
parent
36cb50e9db
commit
1129489750
3 changed files with 154 additions and 5 deletions
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -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/*
|
||||||
|
|
@ -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) => {
|
||||||
|
|
|
||||||
124
backend/src/services/sitemapService.js
Normal file
124
backend/src/services/sitemapService.js
Normal 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;
|
||||||
Loading…
Reference in a new issue