/** * Contact and Newsletter Routes * Handles contact form submissions and newsletter subscriptions */ const express = require("express"); const nodemailer = require("nodemailer"); const { query } = require("../config/database"); const logger = require("../config/logger"); const { asyncHandler } = require("../middleware/errorHandler"); const { sendSuccess, sendError } = require("../utils/responseHelpers"); const rateLimit = require("express-rate-limit"); const router = express.Router(); // Rate limiting for contact form (5 submissions per 15 minutes) const contactLimiter = rateLimit({ windowMs: 15 * 60 * 1000, // 15 minutes max: 5, message: { success: false, message: "Too many contact requests. Please try again later.", }, }); // Rate limiting for newsletter (10 subscriptions per hour) const newsletterLimiter = rateLimit({ windowMs: 60 * 60 * 1000, // 1 hour max: 10, message: { success: false, message: "Too many subscription attempts. Please try again later.", }, }); // Create email transporter function createTransporter() { if ( !process.env.SMTP_HOST || !process.env.SMTP_USER || !process.env.SMTP_PASS ) { logger.warn("SMTP not configured - emails will be logged only"); return null; } return nodemailer.createTransport({ host: process.env.SMTP_HOST, port: parseInt(process.env.SMTP_PORT) || 587, secure: process.env.SMTP_SECURE === "true", auth: { user: process.env.SMTP_USER, pass: process.env.SMTP_PASS, }, }); } // =========================== // CONTACT FORM SUBMISSION // =========================== router.post( "/contact", contactLimiter, asyncHandler(async (req, res) => { const { name, email, subject, message } = req.body; // Validation if (!name || !email || !subject || !message) { return sendError(res, "All fields are required", 400); } // Basic email validation const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { return sendError(res, "Please provide a valid email address", 400); } // Sanitize inputs const sanitizedName = name.trim().slice(0, 255); const sanitizedEmail = email.trim().toLowerCase().slice(0, 255); const sanitizedSubject = subject.trim().slice(0, 500); const sanitizedMessage = message.trim().slice(0, 5000); try { // Save to database await query( `INSERT INTO contact_messages (name, email, subject, message) VALUES ($1, $2, $3, $4)`, [sanitizedName, sanitizedEmail, sanitizedSubject, sanitizedMessage], ); logger.info(`Contact form submission from ${sanitizedEmail}`); // Send email notification to admin const transporter = createTransporter(); if (transporter) { try { // Email to shop owner await transporter.sendMail({ from: process.env.SMTP_FROM || `"Sky Art Shop" <${process.env.SMTP_USER}>`, to: process.env.SMTP_USER, // Send to the shop's email replyTo: sanitizedEmail, subject: `📬 New Contact Form Message: ${sanitizedSubject}`, html: `

📬 New Contact Message

From: ${sanitizedName}

Email: ${sanitizedEmail}

Subject: ${sanitizedSubject}


Message:

${sanitizedMessage.replace(/\n/g, "
")}

© ${new Date().getFullYear()} Sky Art Shop - Contact Form Notification

`, }); // Auto-reply to customer await transporter.sendMail({ from: process.env.SMTP_FROM || `"Sky Art Shop" <${process.env.SMTP_USER}>`, to: sanitizedEmail, subject: `Thank you for contacting Sky Art Shop! 🎨`, html: `

🎨 Thank You!

Hi ${sanitizedName},

Thank you for reaching out to Sky Art Shop! We've received your message and will get back to you within 24-48 hours.

Here's a copy of your message:

Subject: ${sanitizedSubject}

${sanitizedMessage.replace(/\n/g, "
")}

In the meantime, feel free to explore our collection at our shop!

Best regards,
The Sky Art Shop Team

© ${new Date().getFullYear()} Sky Art Shop

`, }); logger.info( `Contact notification and auto-reply sent for ${sanitizedEmail}`, ); } catch (emailError) { logger.error("Failed to send contact email:", emailError); // Don't fail the request - message is saved to database } } else { logger.info( `Contact form (no SMTP): From: ${sanitizedName} <${sanitizedEmail}>, Subject: ${sanitizedSubject}`, ); } sendSuccess(res, { message: "Thank you for your message! We'll get back to you within 24-48 hours.", }); } catch (error) { logger.error("Contact form error:", error); sendError(res, "Failed to send message. Please try again later.", 500); } }), ); // =========================== // NEWSLETTER SUBSCRIPTION // =========================== router.post( "/newsletter/subscribe", newsletterLimiter, asyncHandler(async (req, res) => { const { email, source = "website" } = req.body; // Validation if (!email) { return sendError(res, "Email address is required", 400); } // Basic email validation const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/; if (!emailRegex.test(email)) { return sendError(res, "Please provide a valid email address", 400); } const sanitizedEmail = email.trim().toLowerCase().slice(0, 255); const sanitizedSource = (source || "website").slice(0, 50); try { // Check if already subscribed (either in newsletter_subscribers or customers table) const existingSubscriber = await query( `SELECT id, is_active FROM newsletter_subscribers WHERE email = $1`, [sanitizedEmail], ); if (existingSubscriber.rows.length > 0) { const subscriber = existingSubscriber.rows[0]; if (subscriber.is_active) { return sendSuccess(res, { message: "You're already subscribed to our newsletter!", alreadySubscribed: true, }); } else { // Reactivate subscription await query( `UPDATE newsletter_subscribers SET is_active = TRUE, subscribed_at = CURRENT_TIMESTAMP, unsubscribed_at = NULL, source = $1 WHERE email = $2`, [sanitizedSource, sanitizedEmail], ); logger.info(`Newsletter resubscription: ${sanitizedEmail}`); } } else { // Check if they're a registered customer const existingCustomer = await query( `SELECT id, newsletter_subscribed FROM customers WHERE email = $1`, [sanitizedEmail], ); if (existingCustomer.rows.length > 0) { if (existingCustomer.rows[0].newsletter_subscribed) { return sendSuccess(res, { message: "You're already subscribed to our newsletter!", alreadySubscribed: true, }); } else { // Update customer's newsletter preference await query( `UPDATE customers SET newsletter_subscribed = TRUE WHERE email = $1`, [sanitizedEmail], ); logger.info( `Customer newsletter subscription updated: ${sanitizedEmail}`, ); } } else { // New subscriber - add to newsletter_subscribers table await query( `INSERT INTO newsletter_subscribers (email, source) VALUES ($1, $2)`, [sanitizedEmail, sanitizedSource], ); logger.info( `New newsletter subscription: ${sanitizedEmail} (source: ${sanitizedSource})`, ); } } // Send welcome email const transporter = createTransporter(); if (transporter) { try { await transporter.sendMail({ from: process.env.SMTP_FROM || `"Sky Art Shop" <${process.env.SMTP_USER}>`, to: sanitizedEmail, subject: `Welcome to Sky Art Shop Newsletter! 🎨✨`, html: `

🎨 Welcome to Sky Art Shop!

Thank you for subscribing to our newsletter! You're now part of our creative community.

Here's what you can expect:

Start Shopping

© ${new Date().getFullYear()} Sky Art Shop
Unsubscribe

`, }); logger.info(`Newsletter welcome email sent to ${sanitizedEmail}`); } catch (emailError) { logger.error("Failed to send newsletter welcome email:", emailError); } } sendSuccess(res, { message: "Successfully subscribed! Check your email for a welcome message.", }); } catch (error) { logger.error("Newsletter subscription error:", error); if (error.code === "23505") { // Unique constraint violation return sendSuccess(res, { message: "You're already subscribed to our newsletter!", alreadySubscribed: true, }); } sendError(res, "Failed to subscribe. Please try again later.", 500); } }), ); // =========================== // NEWSLETTER UNSUBSCRIBE // =========================== router.post( "/newsletter/unsubscribe", asyncHandler(async (req, res) => { const { email } = req.body; if (!email) { return sendError(res, "Email address is required", 400); } const sanitizedEmail = email.trim().toLowerCase(); try { // Update newsletter_subscribers table await query( `UPDATE newsletter_subscribers SET is_active = FALSE, unsubscribed_at = CURRENT_TIMESTAMP WHERE email = $1`, [sanitizedEmail], ); // Also update customers table if they're a customer await query( `UPDATE customers SET newsletter_subscribed = FALSE WHERE email = $1`, [sanitizedEmail], ); logger.info(`Newsletter unsubscription: ${sanitizedEmail}`); sendSuccess(res, { message: "You've been unsubscribed from our newsletter.", }); } catch (error) { logger.error("Newsletter unsubscribe error:", error); sendError(res, "Failed to unsubscribe. Please try again later.", 500); } }), ); module.exports = router;