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 = `

New Lead from houses.flowautomate.ai

Name${escapeHtml(name)}
Phone${escapeHtml(phone)}
Email${escapeHtml(email)}
Property${escapeHtml(address)}
Condition${escapeHtml(condition || 'Not specified')}
Timeline${escapeHtml(timeline || 'Not specified')}
TCPA ConsentYes
Submitted${new Date().toLocaleString('en-US', { timeZone: 'America/Chicago' })}
`; // Auto-response email to seller const autoResponseHtml = `

Flowautomate LLC

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:

(425) 610-7779

Jociah

Founder & CEO

Flowautomate LLC

(425) 610-7779offers@flowautomate.ai

Fast cash offers • No repairs needed • Close on your timeline

`; // Send both emails (don't block response on email delivery) if (process.env.SMTP_PASS) { transporter.sendMail({ from: '"Flowautomate LLC" ', to: 'offers@flowautomate.ai', subject: `New Lead: ${address} - ${name}`, html: notifyHtml, }).catch(err => console.error('Notification email failed:', err.message)); transporter.sendMail({ from: '"Jociah at Flowautomate" ', to: email, subject: 'We received your info - Flowautomate LLC', html: autoResponseHtml, }).catch(err => console.error('Auto-response email failed:', err.message)); } res.json({ success: true }); } catch (err) { console.error('Lead submission error:', err); res.status(500).json({ success: false, error: 'Something went wrong. Please try again.' }); } }); function escapeHtml(str) { return String(str) .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"'); } // SPA fallback — serve index.html for unknown routes app.get('*', (req, res) => { res.sendFile(path.join(__dirname, 'public', 'index.html')); }); // Graceful shutdown process.on('SIGTERM', () => { db.close(); process.exit(0); }); app.listen(PORT, () => { console.log(`houses.flowautomate.ai running on port ${PORT}`); });