refactor: Convert monolithic app to modular architecture
This commit refactors the entire codebase from a monolithic structure to a modular one. Key changes include: - Extracting core components (e.g., user authentication, data processing, API handlers) into their own distinct modules. - Implementing a new directory structure to support a modular design. - Updating all internal references and import paths to reflect the new architecture. The new structure improves maintainability, scalability, and allows for easier independent development of each module in the future.
This commit is contained in:
130
server.js
130
server.js
@@ -4,9 +4,6 @@ const path = require('path');
|
||||
const { Pool } = require('pg');
|
||||
const rateLimit = require('express-rate-limit');
|
||||
require('dotenv').config();
|
||||
|
||||
const { body, validationResult } = require('express-validator');
|
||||
|
||||
const app = express();
|
||||
const port = 3000;
|
||||
|
||||
@@ -33,131 +30,16 @@ const transporter = nodemailer.createTransport({
|
||||
requireTLS: true,
|
||||
auth: {
|
||||
user: process.env.EMAIL_USER,
|
||||
pass: process.env.EMAIL_PASS,
|
||||
pass: process.env.EMAIL_PASS,
|
||||
},
|
||||
});
|
||||
|
||||
// A simple test to check if the database connection is working
|
||||
pool.query('SELECT NOW()', (err, res) => {
|
||||
if (err) {
|
||||
console.error('Database connection error:', err);
|
||||
} else {
|
||||
console.log('Database connected successfully!');
|
||||
}
|
||||
});
|
||||
|
||||
// Rate limiting middleware to prevent brute-force attacks
|
||||
const formLimiter = rateLimit({
|
||||
windowMs: 15 * 60 * 1000, // 15 minutes
|
||||
max: 50, // Limit each IP to 50 requests per window
|
||||
message: 'Too many form submissions from this IP. Please try again later.'
|
||||
});
|
||||
// Import contactRoutes and contactController
|
||||
const contactRoutes = require('./routes/contactRoutes');
|
||||
const contactController = require('./controllers/contactController')(pool, transporter);
|
||||
|
||||
// Define the POST route for form submissions with rate limiting applied
|
||||
app.post('/submit-form', formLimiter,
|
||||
|
||||
// Middleware for sanitation and validation
|
||||
[
|
||||
body('firstName').trim().escape(),
|
||||
body('lastName').trim().escape(),
|
||||
body('email').isEmail().normalizeEmail(),
|
||||
body('organization').trim().escape(),
|
||||
body('phone').trim(),
|
||||
body('message').trim().escape(),
|
||||
],
|
||||
async (req, res) => {
|
||||
|
||||
// Check for validation errors from the middleware above
|
||||
const errors = validationResult(req);
|
||||
if (!errors.isEmpty()) {
|
||||
console.error('Validation failed:', errors.array());
|
||||
return res.status(400).json({ success: false, message: 'Invalid form data.' });
|
||||
}
|
||||
|
||||
// 1. Honeypot check (first line of defense)
|
||||
if (req.body.url) {
|
||||
console.warn('Bot detected! Honeypot field was filled.');
|
||||
// Respond with a success status to avoid alerting the bot
|
||||
return res.status(200).json({ success: true, message: 'Thank you for your submission.' });
|
||||
}
|
||||
|
||||
// Get the data from the request body
|
||||
const { firstName, lastName, organization, email, phone, contactMethod, message, hCaptchaResponse } = req.body;
|
||||
|
||||
// Use a single try-catch block for all core logic
|
||||
try {
|
||||
// 2. hCaptcha verification (second line of defense)
|
||||
if (!hCaptchaResponse) {
|
||||
return res.status(400).json({ success: false, message: 'CAPTCHA token missing.' });
|
||||
}
|
||||
|
||||
const secretKey = process.env.HCAPTCHA_SECRET;
|
||||
const verificationUrl = 'https://hcaptcha.com/siteverify';
|
||||
|
||||
const verificationResponse = await fetch(verificationUrl, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
|
||||
body: new URLSearchParams({
|
||||
secret: secretKey,
|
||||
response: hCaptchaResponse
|
||||
})
|
||||
});
|
||||
|
||||
const verificationData = await verificationResponse.json();
|
||||
|
||||
if (!verificationData.success) {
|
||||
console.error('hCaptcha verification failed:', verificationData['error-codes']);
|
||||
return res.status(400).json({ success: false, message: 'CAPTCHA verification failed. Please try again.' });
|
||||
}
|
||||
|
||||
// 3. Insert form data into the database
|
||||
const result = await pool.query(
|
||||
`INSERT INTO submissions(first_name, last_name, organization, email, phone, contact_method, message)
|
||||
VALUES($1, $2, $3, $4, $5, $6, $7) RETURNING *`,
|
||||
[firstName, lastName, organization, email, phone, contactMethod, message]
|
||||
);
|
||||
|
||||
console.log('Successfully saved submission to the database:', result.rows[0]);
|
||||
|
||||
// 4. Send the email notification
|
||||
const mailOptions = {
|
||||
from: `"Contact Form" <contact@dlseitz.dev>`,
|
||||
to: process.env.EMAIL_RCPT,
|
||||
subject: 'New Contact Form Submission',
|
||||
html: `
|
||||
<h3>New Submission</h3>
|
||||
<ul>
|
||||
<li><strong>First Name:</strong> ${firstName}</li>
|
||||
<li><strong>Last Name:</strong> ${lastName}</li>
|
||||
<li><strong>Organization:</strong> ${organization}</li>
|
||||
<li><strong>Email:</strong> ${email}</li>
|
||||
<li><strong>Phone:</strong> ${phone}</li>
|
||||
<li><strong>Contact Method:</strong> ${contactMethod}</li>
|
||||
</ul>
|
||||
<p><strong>Message:</strong></p>
|
||||
<p>${message}</p>
|
||||
`,
|
||||
};
|
||||
|
||||
const emailInfo = await transporter.sendMail(mailOptions);
|
||||
console.log('Message sent: %s', emailInfo.messageId);
|
||||
|
||||
// 5. Send a success response back to the client
|
||||
res.status(200).json({
|
||||
message: 'Form submitted successfully and saved to the database!',
|
||||
dataReceived: req.body
|
||||
});
|
||||
|
||||
} catch (err) {
|
||||
// This single catch block will handle errors from hCaptcha, the database, or nodemailer
|
||||
console.error('An error occurred during form submission:', err.stack);
|
||||
|
||||
res.status(500).json({
|
||||
message: 'An error occurred. Please try again.',
|
||||
error: err.message
|
||||
});
|
||||
}
|
||||
});
|
||||
// Use contactRoutes to connect the modular router to the main app
|
||||
app.use(contactRoutes);
|
||||
|
||||
// Start the server
|
||||
app.listen(port, () => {
|
||||
|
Reference in New Issue
Block a user