Issues git resolved
This commit is contained in:
188
server.mjs
Normal file
188
server.mjs
Normal file
@@ -0,0 +1,188 @@
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import express from 'express';
|
||||
import nodemailer from 'nodemailer';
|
||||
|
||||
const DEFAULT_APP_PORT = 8080;
|
||||
const DEFAULT_API_ONLY_PORT = 3013;
|
||||
const DEFAULT_CONTACT_EMAIL = 'info@bayareaaffiliates.com';
|
||||
const SMTP_HOST = 'email-smtp.us-east-2.amazonaws.com';
|
||||
const SMTP_PORT = 587;
|
||||
const REQUIRED_FIELDS = ['name', 'email', 'message'];
|
||||
const __filename = fileURLToPath(import.meta.url);
|
||||
const __dirname = path.dirname(__filename);
|
||||
const distDir = path.join(__dirname, 'dist');
|
||||
|
||||
function getFlag(name) {
|
||||
return process.argv.includes(name);
|
||||
}
|
||||
|
||||
function getOption(name) {
|
||||
const entry = process.argv.find((arg) => arg.startsWith(`${name}=`));
|
||||
return entry ? entry.slice(name.length + 1) : undefined;
|
||||
}
|
||||
|
||||
function getPort(apiOnly) {
|
||||
const portValue = getOption('--port') || process.env.PORT || String(apiOnly ? DEFAULT_API_ONLY_PORT : DEFAULT_APP_PORT);
|
||||
const port = Number.parseInt(portValue, 10);
|
||||
|
||||
if (Number.isNaN(port)) {
|
||||
throw new Error(`Invalid port value: ${portValue}`);
|
||||
}
|
||||
|
||||
return port;
|
||||
}
|
||||
|
||||
function trimField(value) {
|
||||
return typeof value === 'string' ? value.trim() : '';
|
||||
}
|
||||
|
||||
function sanitizeHeaderValue(value) {
|
||||
return value.replace(/[\r\n]+/g, ' ').trim();
|
||||
}
|
||||
|
||||
function validatePayload(payload) {
|
||||
if (!payload || typeof payload !== 'object') {
|
||||
return { ok: false, error: 'Invalid request payload.' };
|
||||
}
|
||||
|
||||
const data = {
|
||||
name: trimField(payload.name).slice(0, 120),
|
||||
email: trimField(payload.email).slice(0, 160),
|
||||
phone: trimField(payload.phone).slice(0, 80),
|
||||
company: trimField(payload.company).slice(0, 120),
|
||||
message: trimField(payload.message).slice(0, 4000),
|
||||
website: trimField(payload.website).slice(0, 160),
|
||||
};
|
||||
|
||||
for (const field of REQUIRED_FIELDS) {
|
||||
if (!data[field]) {
|
||||
return { ok: false, error: 'Please fill out all required fields.' };
|
||||
}
|
||||
}
|
||||
|
||||
if (!/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(data.email)) {
|
||||
return { ok: false, error: 'Please provide a valid email address.' };
|
||||
}
|
||||
|
||||
return { ok: true, data };
|
||||
}
|
||||
|
||||
function createTransporter() {
|
||||
const user = process.env.AMAZON_USER;
|
||||
const pass = process.env.AMAZON_PASSWORD;
|
||||
|
||||
if (!user || !pass) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return nodemailer.createTransport({
|
||||
host: SMTP_HOST,
|
||||
secure: false,
|
||||
port: SMTP_PORT,
|
||||
auth: {
|
||||
user,
|
||||
pass,
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
function buildMessageBody(data) {
|
||||
return [
|
||||
'New website inquiry',
|
||||
'',
|
||||
`Name: ${data.name}`,
|
||||
`Email: ${data.email}`,
|
||||
`Phone: ${data.phone || 'Not provided'}`,
|
||||
`Company: ${data.company || 'Not provided'}`,
|
||||
'',
|
||||
'Message:',
|
||||
data.message,
|
||||
].join('\n');
|
||||
}
|
||||
|
||||
function resolveRouteIndex(requestPath) {
|
||||
const relativePath = requestPath.replace(/^\/+/, '');
|
||||
|
||||
if (!relativePath) {
|
||||
return path.join(distDir, 'index.html');
|
||||
}
|
||||
|
||||
return path.join(distDir, relativePath, 'index.html');
|
||||
}
|
||||
|
||||
const apiOnly = getFlag('--api-only');
|
||||
const port = getPort(apiOnly);
|
||||
const contactToEmail = process.env.CONTACT_TO_EMAIL || DEFAULT_CONTACT_EMAIL;
|
||||
const contactFromEmail = process.env.CONTACT_FROM_EMAIL || contactToEmail;
|
||||
const app = express();
|
||||
|
||||
app.disable('x-powered-by');
|
||||
app.use(express.json({ limit: '32kb' }));
|
||||
|
||||
app.post('/api/contact', async (req, res) => {
|
||||
const validation = validatePayload(req.body);
|
||||
|
||||
if (!validation.ok) {
|
||||
return res.status(400).json({ ok: false, error: validation.error });
|
||||
}
|
||||
|
||||
const { data } = validation;
|
||||
|
||||
if (data.website) {
|
||||
return res.status(400).json({ ok: false, error: 'Invalid submission.' });
|
||||
}
|
||||
|
||||
const transporter = createTransporter();
|
||||
|
||||
if (!transporter) {
|
||||
return res.status(503).json({
|
||||
ok: false,
|
||||
error: 'Email service is not configured right now. Please email us directly.',
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
await transporter.sendMail({
|
||||
from: contactFromEmail,
|
||||
to: contactToEmail,
|
||||
replyTo: data.email,
|
||||
subject: sanitizeHeaderValue(`Website inquiry from ${data.name}`),
|
||||
text: buildMessageBody(data),
|
||||
});
|
||||
|
||||
return res.json({ ok: true });
|
||||
} catch (error) {
|
||||
console.error('Contact form delivery failed.', error);
|
||||
|
||||
return res.status(500).json({
|
||||
ok: false,
|
||||
error: 'Unable to send your message right now. Please call or email us directly.',
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (!apiOnly) {
|
||||
app.use(express.static(distDir, { index: 'index.html', redirect: false }));
|
||||
|
||||
app.get(/^(?!\/api\/).*/, (req, res) => {
|
||||
const routeIndex = resolveRouteIndex(req.path);
|
||||
const fallbackIndex = path.join(distDir, 'index.html');
|
||||
|
||||
if (fs.existsSync(routeIndex)) {
|
||||
return res.sendFile(routeIndex);
|
||||
}
|
||||
|
||||
return res.sendFile(fallbackIndex);
|
||||
});
|
||||
}
|
||||
|
||||
app.listen(port, () => {
|
||||
if (!process.env.AMAZON_USER || !process.env.AMAZON_PASSWORD) {
|
||||
console.warn('Amazon SES SMTP credentials are missing. /api/contact will return 503 until configured.');
|
||||
}
|
||||
|
||||
console.log(`Bay Area IT server listening on port ${port}${apiOnly ? ' (API only)' : ''}.`);
|
||||
});
|
||||
Reference in New Issue
Block a user