const express = require('express'); const router = express.Router(); const nodemailer = require('nodemailer'); const config = require('../config'); // Helper function to create email transporter const createTransporter = () => { return nodemailer.createTransport({ host: config.email.host, port: config.email.port, auth: { user: config.email.user, pass: config.email.pass } }); }; module.exports = (pool, query, authMiddleware) => { // Apply authentication middleware to all routes router.use(authMiddleware); // Get all orders (admin only) router.get('/', async (req, res, next) => { try { // Check if user is admin if (!req.user.is_admin) { return res.status(403).json({ error: true, message: 'Admin access required' }); } const result = await query(` SELECT o.*, u.email, u.first_name, u.last_name, COUNT(oi.id) AS item_count FROM orders o JOIN users u ON o.user_id = u.id LEFT JOIN order_items oi ON o.id = oi.order_id GROUP BY o.id, u.id ORDER BY o.created_at DESC `); res.json(result.rows); } catch (error) { next(error); } }); // Get single order with items router.get('/:id', async (req, res, next) => { try { const { id } = req.params; // Check if user is admin if (!req.user.is_admin) { return res.status(403).json({ error: true, message: 'Admin access required' }); } // Get order details with shipping information const orderResult = await query(` SELECT o.*, u.email, u.first_name, u.last_name, (SELECT EXISTS( SELECT 1 FROM notification_logs WHERE order_id = o.id AND notification_type = 'shipping_notification' )) as notification_sent FROM orders o JOIN users u ON o.user_id = u.id WHERE o.id = $1 `, [id]); if (orderResult.rows.length === 0) { return res.status(404).json({ error: true, message: 'Order not found' }); } // Get order items with product details const itemsResult = await query(` SELECT oi.*, p.name as product_name, p.description as product_description, pc.name as product_category, ( SELECT json_agg( json_build_object( 'id', pi.id, 'path', pi.image_path, 'isPrimary', pi.is_primary, 'displayOrder', pi.display_order ) ORDER BY pi.display_order ) FROM product_images pi WHERE pi.product_id = p.id ) AS product_images FROM order_items oi JOIN products p ON oi.product_id = p.id JOIN product_categories pc ON p.category_id = pc.id WHERE oi.order_id = $1 `, [id]); // Combine order with items const order = { ...orderResult.rows[0], items: itemsResult.rows }; res.json(order); } catch (error) { next(error); } }); // Update order status (simple version) router.patch('/:id', async (req, res, next) => { try { const { id } = req.params; const { status } = req.body; // Check if user is admin if (!req.user.is_admin) { return res.status(403).json({ error: true, message: 'Admin access required' }); } // Validate status const validStatuses = ['pending', 'processing', 'shipped', 'delivered', 'cancelled', 'refunded']; if (!validStatuses.includes(status)) { return res.status(400).json({ error: true, message: `Invalid status. Must be one of: ${validStatuses.join(', ')}` }); } // Update order status const result = await query(` UPDATE orders SET status = $1, updated_at = NOW() WHERE id = $2 RETURNING * `, [status, id]); if (result.rows.length === 0) { return res.status(404).json({ error: true, message: 'Order not found' }); } res.json({ message: 'Order status updated successfully', order: result.rows[0] }); } catch (error) { next(error); } }); // Update order status with shipping information and send notification router.patch('/:id/shipping', async (req, res, next) => { try { const { id } = req.params; const { status, shippingData, sendNotification } = req.body; // Check if user is admin if (!req.user.is_admin) { return res.status(403).json({ error: true, message: 'Admin access required' }); } // Validate status const validStatuses = ['pending', 'processing', 'shipped', 'delivered', 'cancelled', 'refunded']; if (!validStatuses.includes(status)) { return res.status(400).json({ error: true, message: `Invalid status. Must be one of: ${validStatuses.join(', ')}` }); } // Get order with customer information before updating const orderResult = await query(` SELECT o.*, u.email, u.first_name, u.last_name FROM orders o JOIN users u ON o.user_id = u.id WHERE o.id = $1 `, [id]); if (orderResult.rows.length === 0) { return res.status(404).json({ error: true, message: 'Order not found' }); } const order = orderResult.rows[0]; // Begin transaction const client = await pool.connect(); try { await client.query('BEGIN'); // Store shipping information as JSON in the database const updatedOrder = await client.query(` UPDATE orders SET status = $1, updated_at = NOW(), shipping_info = $2, shipping_date = $3 WHERE id = $4 RETURNING * `, [ status, JSON.stringify(shippingData), new Date(), id ]); // If status is 'shipped' and notification requested, send email if (status === 'shipped' && sendNotification) { // Get order items for the email const itemsResult = await client.query(` SELECT oi.*, p.name as product_name, p.price as original_price FROM order_items oi JOIN products p ON oi.product_id = p.id WHERE oi.order_id = $1 `, [id]); const orderItems = itemsResult.rows; // Send email notification await sendShippingNotification( order, orderItems, shippingData ); // Log the notification in the database await client.query(` INSERT INTO notification_logs (order_id, notification_type, sent_at) VALUES ($1, $2, NOW()) `, [id, 'shipping_notification']); } await client.query('COMMIT'); res.json({ message: 'Order status updated successfully', order: updatedOrder.rows[0] }); } catch (error) { await client.query('ROLLBACK'); throw error; } finally { client.release(); } } catch (error) { console.error('Error updating order status with shipping:', error); next(error); } }); // Helper function to send shipping notification email async function sendShippingNotification(order, orderItems, shippingData) { try { const transporter = createTransporter(); // Calculate order total const orderTotal = orderItems.reduce((sum, item) => { return sum + (parseFloat(item.price_at_purchase) * item.quantity); }, 0); // Format shipping date const shippedDate = new Date(shippingData.shippedDate || new Date()).toLocaleDateString(); // Generate items HTML table const itemsHtml = orderItems.map(item => ` ${item.product_name} ${item.quantity} $${parseFloat(item.price_at_purchase).toFixed(2)} $${(parseFloat(item.price_at_purchase) * item.quantity).toFixed(2)} `).join(''); // Generate carrier tracking link let trackingLink = '#'; const shipper = shippingData.shipper || ''; const trackingNumber = shippingData.trackingNumber; if (trackingNumber) { // Match exactly with the values from the dropdown switch(shipper) { case 'USPS': trackingLink = `https://tools.usps.com/go/TrackConfirmAction?tLabels=${trackingNumber}`; break; case 'UPS': trackingLink = `https://www.ups.com/track?tracknum=${trackingNumber}`; break; case 'FedEx': trackingLink = `https://www.fedex.com/apps/fedextrack/?tracknumbers=${trackingNumber}`; break; case 'DHL': trackingLink = `https://www.dhl.com/global-en/home/tracking.html?tracking-id=${trackingNumber}`; break; case 'Canada Post': trackingLink = `https://www.canadapost-postescanada.ca/track-reperage/en#/search?searchFor=${trackingNumber}`; break; case 'Purolator': trackingLink = `https://www.purolator.com/en/shipping/track/tracking-number/${trackingNumber}`; break; default: // For "other" or any carrier not in our list // Just make the tracking number text without a link trackingLink = '#'; break; } } // Build email HTML const emailHtml = `

Your Order Has Shipped!

Order #${order.id.substring(0, 8)}

Hello ${order.first_name},

Good news! Your order has been shipped and is on its way to you.

${shippingData.customerMessage ? `

Message from our team: ${shippingData.customerMessage}

` : ''}

Shipping Details

Carrier: ${shippingData.shipper || 'Standard Shipping'}

Tracking Number: ${shippingData.trackingNumber}

Shipped On: ${shippedDate}

${shippingData.estimatedDelivery ? `

Estimated Delivery: ${shippingData.estimatedDelivery}

` : ''}

Order Summary

${itemsHtml}
Item Qty Price Total
Total: $${orderTotal.toFixed(2)}

Thank you for your purchase! If you have any questions, please contact our customer service.

© ${new Date().getFullYear()} Rocks, Bones & Sticks. All rights reserved.

`; // Send the email await transporter.sendMail({ from: config.email.reply, to: order.email, subject: `Your Order #${order.id.substring(0, 8)} Has Shipped!`, html: emailHtml }); console.log(`Shipping notification email sent to ${order.email}`); return true; } catch (error) { console.error('Error sending shipping notification:', error); throw error; } } return router; };