// ── มิตซูชัยพร — Astro + Express (static) ───────────────────────── // Serves the Astro static build (dist/) on the configured port. // Mirrors the moreminimore-astroreal deployment pattern that works on EasyPanel. // // Injects env vars into HTML at serve time (replaces ${PLACEHOLDER}s): // GA_ID — Google Analytics / GTM measurement id (empty = analytics removed) // FORM_EMAIL — FormSubmit recipient for RFQ/contact forms // GMAP_EMBED — full Google Maps embed URL (no-API-key style) // PORT — listen port (default 4321) import express from 'express'; import { existsSync, readFileSync, statSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { fileURLToPath } from 'node:url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const PORT = parseInt(process.env.PORT || '4321', 10); const DIST = join(__dirname, 'dist'); // Build the Google Map embed (type A — free, no API key) const GMAP_LAT = process.env.GMAP_LAT || '13.557229094780995'; const GMAP_LNG = process.env.GMAP_LNG || '100.28916297408983'; const GMAP_EMBED = process.env.GMAP_EMBED || `https://maps.google.com/maps?q=${GMAP_LAT},${GMAP_LNG}&z=${process.env.GMAP_ZOOM || 16}&output=embed`; const FORM_EMAIL = process.env.FORM_EMAIL || 'mail@mitsuchaiyaporn.com'; const GA_ID = process.env.GA_ID || ''; const app = express(); app.disable('x-powered-by'); // Inject env placeholders into an HTML string; also remove GA snippet if disabled. function injectEnv(html) { let out = html .replaceAll('${GMAP_EMBED}', GMAP_EMBED) .replaceAll('${FORM_EMAIL}', FORM_EMAIL); if (GA_ID) { out = out .replaceAll('${GA_ID}', GA_ID); } else { // GA disabled: strip the gtag snippet + config lines out = out .replace(/]*googletagmanager\.com\/gtag\/js[^>]*><\/script>/g, '') .replace(//g, ''); } return out; } // Serve .html with env injection; everything else statically. app.use((req, res, next) => { let pathname; try { pathname = decodeURIComponent(req.path); } catch { return next(); } if (pathname.endsWith('.html') || pathname === '/' || !/\.[a-z0-9]+$/i.test(pathname)) { let file = pathname === '/' ? 'index.html' : pathname; let abs = join(DIST, file); // if it's a directory, serve its index.html if (existsSync(abs) && statSync(abs).isDirectory()) { abs = join(abs, 'index.html'); } // clean-URL support: /about-us/ => about-us/index.html if (!existsSync(abs)) { abs = join(DIST, file, 'index.html'); } if (existsSync(abs) && statSync(abs).isFile()) { const html = readFileSync(abs, 'utf-8'); res.setHeader('Content-Type', 'text/html; charset=utf-8'); return res.send(injectEnv(html)); } } next(); }); // Static Astro build (css, js, images) with long cache app.use( express.static(DIST, { maxAge: '1y', etag: true }) ); // 404 fallback app.use((req, res) => { res.status(404).sendFile(join(DIST, '404.html'), (err) => { if (err) res.status(404).send('Not found'); }); }); app.listen(PORT, '0.0.0.0', () => { console.log(`[mitsu] Listening on http://0.0.0.0:${PORT}`); console.log(`[mitsu] Static: ${existsSync(DIST) ? 'dist/ ✓' : 'dist/ NOT found'}`); });