145 lines
4.5 KiB
JavaScript
145 lines
4.5 KiB
JavaScript
require('dotenv').config();
|
|
const express = require('express');
|
|
const path = require('path');
|
|
const fs = require('fs');
|
|
|
|
const app = express();
|
|
const PORT = process.env.PORT || 8899;
|
|
const MAIN_DOMAIN = process.env.MAIN_DOMAIN || 'solvedby.us';
|
|
|
|
// Ensure data directory exists
|
|
const dataDir = path.join(__dirname, 'data');
|
|
if (!fs.existsSync(dataDir)) {
|
|
fs.mkdirSync(dataDir, { recursive: true });
|
|
}
|
|
|
|
// Subdomain-to-clientId index (loaded from disk)
|
|
const subdomainIndexPath = path.join(dataDir, 'subdomain-index.json');
|
|
let subdomainIndex = {};
|
|
if (fs.existsSync(subdomainIndexPath)) {
|
|
subdomainIndex = JSON.parse(fs.readFileSync(subdomainIndexPath, 'utf8'));
|
|
}
|
|
|
|
function saveSubdomainIndex() {
|
|
fs.writeFileSync(subdomainIndexPath, JSON.stringify(subdomainIndex, null, 2));
|
|
}
|
|
|
|
function getSubdomain(host) {
|
|
// Strip port
|
|
const hostname = (host || '').split(':')[0];
|
|
// Check if it's a subdomain of MAIN_DOMAIN
|
|
if (hostname.endsWith('.' + MAIN_DOMAIN)) {
|
|
return hostname.replace('.' + MAIN_DOMAIN, '').toLowerCase();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
// View engine
|
|
app.set('view engine', 'ejs');
|
|
app.set('views', path.join(__dirname, 'templates'));
|
|
|
|
// Middleware
|
|
app.use(express.json({ limit: '10mb' }));
|
|
app.use(express.urlencoded({ extended: true, limit: '10mb' }));
|
|
|
|
// ─── Subdomain routing: {clientname}.solvedby.us serves that client's site ───
|
|
app.use((req, res, next) => {
|
|
const subdomain = getSubdomain(req.headers.host);
|
|
|
|
if (!subdomain) {
|
|
// No subdomain — serve the main wizard app
|
|
return next();
|
|
}
|
|
|
|
// Look up clientId from subdomain
|
|
const clientId = subdomainIndex[subdomain];
|
|
if (!clientId) {
|
|
return res.status(404).send(`<h1>Site not found</h1><p>No business site exists at <strong>${subdomain}.${MAIN_DOMAIN}</strong></p><p><a href="https://${MAIN_DOMAIN}">Create one at ${MAIN_DOMAIN}</a></p>`);
|
|
}
|
|
|
|
const siteDir = path.join(dataDir, clientId, 'site');
|
|
if (!fs.existsSync(siteDir)) {
|
|
return res.status(404).send('Site files not found');
|
|
}
|
|
|
|
// Map paths to files
|
|
let filePath;
|
|
const urlPath = req.path.replace(/\/$/, '') || '';
|
|
|
|
if (urlPath === '' || urlPath === '/') {
|
|
filePath = path.join(siteDir, 'index.html');
|
|
} else if (urlPath === '/contact') {
|
|
filePath = path.join(siteDir, 'contact.html');
|
|
} else if (urlPath === '/privacy-policy') {
|
|
filePath = path.join(siteDir, 'privacy-policy.html');
|
|
} else if (urlPath === '/terms') {
|
|
filePath = path.join(siteDir, 'terms.html');
|
|
} else if (urlPath.startsWith('/assets/')) {
|
|
filePath = path.join(siteDir, urlPath);
|
|
} else if (urlPath.startsWith('/data/')) {
|
|
// Allow serving logos etc from data dir
|
|
filePath = path.join(__dirname, urlPath);
|
|
} else {
|
|
return res.status(404).send('Page not found');
|
|
}
|
|
|
|
if (fs.existsSync(filePath)) {
|
|
return res.sendFile(filePath);
|
|
}
|
|
return res.status(404).send('Page not found');
|
|
});
|
|
|
|
// ─── Main domain routes ───
|
|
app.use('/public', express.static(path.join(__dirname, 'public')));
|
|
app.use('/data', express.static(path.join(__dirname, 'data')));
|
|
|
|
// Routes
|
|
const apiRoutes = require('./routes/api');
|
|
const sitesRoutes = require('./routes/sites');
|
|
|
|
app.use('/api', apiRoutes);
|
|
app.use('/sites', sitesRoutes);
|
|
|
|
// Main form page
|
|
app.get('/', (req, res) => {
|
|
res.render('form');
|
|
});
|
|
|
|
// Results page
|
|
app.get('/results/:clientId', (req, res) => {
|
|
const { clientId } = req.params;
|
|
const configPath = path.join(dataDir, clientId, 'config.json');
|
|
|
|
if (!fs.existsSync(configPath)) {
|
|
return res.status(404).send('Project not found');
|
|
}
|
|
|
|
const config = JSON.parse(fs.readFileSync(configPath, 'utf8'));
|
|
res.render('packet/results', { config, clientId });
|
|
});
|
|
|
|
// Download zip
|
|
app.get('/download/:clientId', (req, res) => {
|
|
const { clientId } = req.params;
|
|
const zipPath = path.join(dataDir, clientId, 'compliance-packet.zip');
|
|
|
|
if (!fs.existsSync(zipPath)) {
|
|
return res.status(404).send('Zip not found');
|
|
}
|
|
|
|
const config = JSON.parse(fs.readFileSync(path.join(dataDir, clientId, 'config.json'), 'utf8'));
|
|
const safeName = config.businessName.replace(/[^a-zA-Z0-9]/g, '-').toLowerCase();
|
|
res.download(zipPath, `${safeName}-a2p-compliance-packet.zip`);
|
|
});
|
|
|
|
// Export for use in API routes
|
|
app.locals.subdomainIndex = subdomainIndex;
|
|
app.locals.saveSubdomainIndex = saveSubdomainIndex;
|
|
app.locals.MAIN_DOMAIN = MAIN_DOMAIN;
|
|
|
|
app.listen(PORT, () => {
|
|
console.log(`\n🚀 A2P Compliance Wizard running at http://localhost:${PORT}`);
|
|
console.log(` Main domain: https://${MAIN_DOMAIN}`);
|
|
console.log(` Client sites: https://{name}.${MAIN_DOMAIN}\n`);
|
|
});
|