const express = require('express'); const path = require('path'); const Database = require('better-sqlite3'); const nodemailer = require('nodemailer'); const rateLimit = require('express-rate-limit'); const helmet = require('helmet'); const app = express(); const PORT = process.env.PORT || 3000; // Database setup const dbPath = path.join(__dirname, 'data', 'leads.db'); const db = new Database(dbPath); db.pragma('journal_mode = WAL'); db.exec(` CREATE TABLE IF NOT EXISTS leads ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT NOT NULL, phone TEXT NOT NULL, address TEXT NOT NULL, property_condition TEXT, timeline TEXT, tcpa_consent INTEGER NOT NULL DEFAULT 0, ip TEXT, created_at TEXT DEFAULT (datetime('now')) ) `); // SMTP transporter const transporter = nodemailer.createTransport({ host: process.env.SMTP_HOST || 'mail.flowautomate.ai', port: parseInt(process.env.SMTP_PORT || '587'), secure: false, auth: { user: process.env.SMTP_USER || 'offers@flowautomate.ai', pass: process.env.SMTP_PASS || '', }, tls: { rejectUnauthorized: false }, }); // Middleware app.use(helmet({ contentSecurityPolicy: { directives: { defaultSrc: ["'self'"], styleSrc: ["'self'", "'unsafe-inline'", "https://fonts.googleapis.com"], scriptSrc: ["'self'"], imgSrc: ["'self'", "data:"], fontSrc: ["'self'", "https://fonts.gstatic.com"], connectSrc: ["'self'"], }, }, })); app.use(express.json()); app.use(express.urlencoded({ extended: false })); app.use(express.static(path.join(__dirname, 'public'))); // Rate limit for form submissions const submitLimiter = rateLimit({ windowMs: 60 * 60 * 1000, // 1 hour max: 5, message: { success: false, error: 'Too many submissions. Please try again later.' }, standardHeaders: true, legacyHeaders: false, keyGenerator: (req) => req.ip, }); // Health check app.get('/api/health', (req, res) => { res.json({ status: 'ok', timestamp: new Date().toISOString() }); }); // Lead submission app.post('/api/submit-lead', submitLimiter, async (req, res) => { try { const { name, email, phone, address, condition, timeline, tcpa_consent, honeypot } = req.body; // Honeypot check if (honeypot) { return res.json({ success: true }); } // Validate required fields if (!name || !email || !phone || !address) { return res.status(400).json({ success: false, error: 'All fields are required.' }); } // Validate email format if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { return res.status(400).json({ success: false, error: 'Please enter a valid email address.' }); } // Validate phone (digits, dashes, parens, spaces, plus) const cleanPhone = phone.replace(/[\s\-\(\)\+]/g, ''); if (!/^\d{7,15}$/.test(cleanPhone)) { return res.status(400).json({ success: false, error: 'Please enter a valid phone number.' }); } // TCPA consent required if (!tcpa_consent) { return res.status(400).json({ success: false, error: 'Please agree to be contacted.' }); } // Save to database const stmt = db.prepare(` INSERT INTO leads (name, email, phone, address, property_condition, timeline, tcpa_consent, ip) VALUES (?, ?, ?, ?, ?, ?, ?, ?) `); stmt.run(name, email, phone, address, condition || '', timeline || '', 1, req.ip); // Send notification email to team const notifyHtml = `
| Name | ${escapeHtml(name)} |
| Phone | ${escapeHtml(phone)} |
| ${escapeHtml(email)} | |
| Property | ${escapeHtml(address)} |
| Condition | ${escapeHtml(condition || 'Not specified')} |
| Timeline | ${escapeHtml(timeline || 'Not specified')} |
| TCPA Consent | Yes |
| Submitted | ${new Date().toLocaleString('en-US', { timeZone: 'America/Chicago' })} |
AI-Powered Real Estate Solutions
Hi ${escapeHtml(name.split(' ')[0])},
Thank you for reaching out about your property at ${escapeHtml(address)}. We received your information and our team is reviewing it now.
What happens next:
If you have any questions in the meantime, feel free to call or text us:
|
Jociah Founder & CEO Flowautomate LLC |
Fast cash offers • No repairs needed • Close on your timeline