First build
Some checks failed
Client Admin / build-deploy (push) Failing after 8s
Client Client / build-deploy (push) Failing after 3s
Client Registration / build-deploy (push) Failing after 20s
Client Tech / build-deploy (push) Failing after 1s
Client Home / build-deploy (push) Successful in 14s

This commit is contained in:
Grae Jones
2026-03-21 17:54:42 -07:00
parent 3647b304a3
commit fdb3e117a9
203 changed files with 35733 additions and 18189 deletions

199
wwwroot/server.js Normal file
View File

@@ -0,0 +1,199 @@
const http = require('http');
const fs = require('fs');
const fsp = require('fs/promises');
const path = require('path');
const { URL } = require('url');
// Azure sets PORT
const PORT = process.env.PORT || 8080;
// Default site if host doesn't map
const DEFAULT_SITE = 'default';
// Explicit host ? folder mapping
const HOST_MAP = {
'adpadmin.usimdev.com': 'admin',
'adpclient.usimdev.com': 'client',
'adpregist.usimdev.com': 'regist',
'adptestapi.usimdev.com': 'testapi',
'localhost': 'default'
};
const server = http.createServer(async (req, res) => {
try {
const hostHeader = (req.headers.host || '').toLowerCase();
console.log(`[SRV ${new Date().toISOString()}] ${req.method} ${req.url} (Host: ${hostHeader})`);
// Parse URL with WHATWG API (avoids url.parse deprecation)
const parsedUrl = new URL(req.url || '/', `http://${hostHeader || 'localhost'}`);
const pathname = decodeURIComponent(parsedUrl.pathname || '/');
const parts = pathname.replace(/^\/+/, '').split('/');
const firstSegment = parts[0] || '';
// Decide which site folder to serve from
const siteFolder = getSiteFolder(hostHeader);
const BASEDIR = path.join(__dirname, 'websites', siteFolder);
console.log(`Site folder resolved: /websites/${siteFolder} (host=${hostHeader})`);
// --- favicon special case ---
if (firstSegment === 'favicon.ico') {
const favPath = safeJoin(BASEDIR, 'favicon.ico');
if (await exists(favPath)) {
res.writeHead(200, { 'Content-Type': 'image/x-icon' });
return fs.createReadStream(favPath).pipe(res);
} else {
res.writeHead(204);
return res.end();
}
}
// --- robots.txt (optional per-site) ---
if (firstSegment === 'robots.txt') {
const robotsPath = safeJoin(BASEDIR, 'robots.txt');
if (await exists(robotsPath)) {
res.writeHead(200, { 'Content-Type': 'text/plain' });
return fs.createReadStream(robotsPath).pipe(res);
}
// no robots for this site ? 404
res.writeHead(404, { 'Content-Type': 'text/plain' });
return res.end('Not found');
}
// Static file resolution
let requested = firstSegment || 'index.html';
if (parts.length > 1) {
requested = parts.join('/');
}
let filePath = safeJoin(BASEDIR, requested);
if (await isDir(filePath)) {
filePath = safeJoin(filePath, 'index.html');
}
// Handle Index.html vs index.html (case difference on dev machines)
if (!(await exists(filePath)) && /Index\.html$/.test(filePath)) {
const alt = filePath.replace(/Index\.html$/, 'index.html');
if (await exists(alt)) filePath = alt;
}
// If the requested file doesn't exist:
// 1) try this site's index.html (SPA routing)
// 2) try shared 404.html in root
if (!(await exists(filePath))) {
const siteIndex = safeJoin(BASEDIR, 'index.html');
if (await exists(siteIndex)) {
res.writeHead(200, { 'Content-Type': 'text/html' });
return fs.createReadStream(siteIndex).pipe(res);
}
const shared404 = path.join(__dirname, '404.html');
if (await exists(shared404)) {
res.writeHead(404, { 'Content-Type': 'text/html' });
return fs.createReadStream(shared404).pipe(res);
}
res.writeHead(404, { 'Content-Type': 'text/plain' });
return res.end('404 Not Found');
}
// Serve the static file
res.writeHead(200, { 'Content-Type': getType(filePath) });
return fs.createReadStream(filePath).pipe(res);
} catch (err) {
console.error('[SERVER ERROR]', err);
res.writeHead(500, { 'Content-Type': 'application/json' });
return res.end(JSON.stringify({ status: 'error', error: err?.message || String(err) }));
}
});
server.listen(PORT, () => {
console.log('===========================================');
console.log(`Static HTTP server listening on ${PORT}`);
console.log(`Default site: /websites/${DEFAULT_SITE}/`);
console.log('Host mappings:');
Object.entries(HOST_MAP).forEach(([host, folder]) => {
console.log(` ${host} ? /websites/${folder}/`);
});
console.log('Fallback (subdomain): sub.domain.tld ? /websites/<sub>/');
console.log('Shared 404 (optional): /404.html');
console.log('===========================================');
});
module.exports = server;
/* ------------ helpers ------------ */
function getSiteFolder(hostnameRaw) {
if (!hostnameRaw) return DEFAULT_SITE;
const hostname = hostnameRaw.toLowerCase();
// 1) Explicit map wins
if (HOST_MAP[hostname]) {
return HOST_MAP[hostname];
}
// 2) localhost or IP ? default
if (hostname === 'localhost' || /^\d+\.\d+\.\d+\.\d+$/.test(hostname)) {
return DEFAULT_SITE;
}
// 3) For multi-tenant subdomains: foo.example.com ? "foo"
const parts = hostname.split('.');
if (parts.length >= 3) {
return parts[0];
}
// 4) Anything else ? default
return DEFAULT_SITE;
}
function safeJoin(base, target) {
const resolved = path.resolve(base, target);
if (!resolved.startsWith(base)) {
// Prevent directory traversal fall back to base
return base;
}
return resolved;
}
async function exists(p) {
try {
await fsp.access(p, fs.constants.R_OK);
return true;
} catch {
return false;
}
}
async function isDir(p) {
try {
return (await fsp.stat(p)).isDirectory();
} catch {
return false;
}
}
function getType(filename) {
const ext = path.extname(filename).toLowerCase();
switch (ext) {
case '.html': return 'text/html';
case '.css': return 'text/css';
case '.js': return 'application/javascript';
case '.png': return 'image/png';
case '.jpg':
case '.jpeg': return 'image/jpeg';
case '.svg': return 'image/svg+xml';
case '.ico': return 'image/x-icon';
case '.json': return 'application/json';
case '.map': return 'application/json';
case '.woff2': return 'font/woff2';
case '.woff': return 'font/woff';
case '.ttf': return 'font/ttf';
case '.eot': return 'application/vnd.ms-fontobject';
default: return 'text/plain';
}
}