E-Commerce-Module/backend/src/services/notificationService.js
2025-04-28 13:59:02 -05:00

177 lines
No EOL
5.9 KiB
JavaScript

// Create a new file: src/services/notificationService.js
const nodemailer = require('nodemailer');
const config = require('../config');
/**
* Service for handling notifications including stock alerts
*/
const notificationService = {
/**
* Create email transporter
* @returns {Object} Configured nodemailer transporter
*/
createTransporter() {
return nodemailer.createTransport({
host: config.email.host,
port: config.email.port,
auth: {
user: config.email.user,
pass: config.email.pass
}
});
},
/**
* Process pending low stock notifications
* @param {Object} pool - Database connection pool
* @param {Function} query - Database query function
* @returns {Promise<number>} Number of notifications processed
*/
async processLowStockNotifications(pool, query) {
const client = await pool.connect();
let processedCount = 0;
try {
await client.query('BEGIN');
// Get pending low stock notifications
const pendingNotifications = await client.query(`
SELECT id
FROM notification_logs
WHERE notification_type = 'low_stock_alert'
AND status = 'pending'
LIMIT 50
`);
if (pendingNotifications.rows.length === 0) {
await client.query('COMMIT');
return 0;
}
// Get products with current stock below threshold
const lowStockProducts = await client.query(`
SELECT
p.id,
p.name,
p.stock_quantity,
p.stock_notification
FROM products p
WHERE
p.stock_notification IS NOT NULL
AND p.stock_notification->>'enabled' = 'true'
AND p.stock_notification->>'email' IS NOT NULL
AND p.stock_quantity <= (p.stock_notification->>'threshold')::int
`);
if (lowStockProducts.rows.length === 0) {
// Mark notifications as processed with no action
const notificationIds = pendingNotifications.rows.map(n => n.id);
await client.query(`
UPDATE notification_logs
SET status = 'completed', error_message = 'No eligible products found'
WHERE id = ANY($1)
`, [notificationIds]);
await client.query('COMMIT');
return 0;
}
// Initialize email transporter
const transporter = this.createTransporter();
// Send notifications for each low stock product
console.log(JSON.stringify(lowStockProducts, null, 4))
for (const product of lowStockProducts.rows) {
console.log(JSON.stringify(product, null, 4))
console.log(typeof product.stock_notification)
const notification = JSON.parse(product.stock_notification);
try {
// Send email notification
await transporter.sendMail({
from: config.email.reply,
to: notification.email,
subject: `Low Stock Alert: ${product.name}`,
html: this.generateLowStockEmailTemplate(product)
});
// Mark one notification as processed
if (pendingNotifications.rows[processedCount]) {
await client.query(`
UPDATE notification_logs
SET status = 'success'
WHERE id = $1
`, [pendingNotifications.rows[processedCount].id]);
processedCount++;
}
} catch (error) {
console.error(`Error sending low stock notification for product ${product.id}:`, error);
// Mark notification as failed
if (pendingNotifications.rows[processedCount]) {
await client.query(`
UPDATE notification_logs
SET status = 'failed', error_message = $2
WHERE id = $1
`, [pendingNotifications.rows[processedCount].id, error.message]);
processedCount++;
}
}
}
await client.query('COMMIT');
return processedCount;
} catch (error) {
await client.query('ROLLBACK');
console.error('Error processing low stock notifications:', error);
throw error;
} finally {
client.release();
}
},
/**
* Generate email template for low stock notification
* @param {Object} product - Product with low stock
* @returns {string} HTML email template
*/
generateLowStockEmailTemplate(product) {
const stockNotification = JSON.parse(product.stock_notification);
const threshold = stockNotification.threshold || 0;
return `
<div style="font-family: Arial, sans-serif; max-width: 600px; margin: 0 auto;">
<div style="background-color: #f8f8f8; padding: 20px; text-align: center;">
<h1 style="color: #ff6b6b;">Low Stock Alert</h1>
</div>
<div style="padding: 20px;">
<p>Hello,</p>
<p>This is an automated notification to inform you that the following product is running low on stock:</p>
<div style="background-color: #f8f8f8; padding: 15px; margin: 20px 0; border-left: 4px solid #ff6b6b;">
<h3 style="margin-top: 0;">${product.name}</h3>
<p><strong>Current Stock:</strong> ${product.stock_quantity}</p>
<p><strong>Threshold:</strong> ${threshold}</p>
</div>
<p>You might want to restock this item soon to avoid running out of inventory.</p>
<div style="margin-top: 30px; border-top: 1px solid #eee; padding-top: 20px;">
<p>This is an automated notification. You received this because you set up stock notifications for this product.</p>
</div>
</div>
<div style="background-color: #333; color: white; padding: 15px; text-align: center; font-size: 12px;">
<p>&copy; ${new Date().getFullYear()} Rocks, Bones & Sticks. All rights reserved.</p>
</div>
</div>
`;
}
};
module.exports = notificationService;