import path from "path"; import { fileURLToPath } from "url"; import { readFileSync } from "fs"; import { createServer } from "./index"; import * as express from "express"; import { adminSupabase } from "./supabase"; const app = createServer(); const port = process.env.PORT || 5000; const host = "0.0.0.0"; const __filename = fileURLToPath(import.meta.url); const __dirname = path.dirname(__filename); const distPath = path.join(__dirname, "../spa"); // Serve static files app.use(express.static(distPath)); // ── SSR Meta Injection ──────────────────────────────────────────────────────── const BASE_URL = "https://aethex.dev"; const DEFAULT_OG_IMAGE = "https://docs.aethex.tech/~gitbook/image?url=https%3A%2F%2F1143808467-files.gitbook.io%2F%7E%2Ffiles%2Fv0%2Fb%2Fgitbook-x-prod.appspot.com%2Fo%2Forganizations%252FDhUg3jal6kdpG645FzIl%252Fsites%252Fsite_HeOmR%252Flogo%252FqxDYz8Oj2SnwUTa8t3UB%252FAeThex%2520Origin%2520logo.png%3Falt%3Dmedia%26token%3D200e8ea2-0129-4cbe-b516-4a53f60c512b&width=1200&dpr=1&quality=100&sign=6c7576ce&sv=2"; type RouteMeta = { title: string; description: string; image?: string; type?: string; }; /** Static route overrides — matched in order, first match wins. */ const STATIC_META: Array<{ pattern: RegExp; meta: RouteMeta }> = [ { pattern: /^\/$/, meta: { title: "AeThex | Developer Platform for Builders, Creators & Innovation", description: "AeThex: an advanced development platform and community for builders. Collaborate on projects, learn, and ship innovation.", }, }, { pattern: /^\/projects\/?$/, meta: { title: "AeThex | Projects", description: "Explore open-source and community projects built on the AeThex platform.", }, }, { pattern: /^\/projects\/new/, meta: { title: "AeThex | New Project", description: "Start a new project on AeThex.", }, }, { pattern: /^\/gameforge/, meta: { title: "AeThex | GameForge Studio", description: "GameForge — AeThex's game development studio for indie game creators. Build, manage, and ship games.", }, }, { pattern: /^\/ethos/, meta: { title: "AeThex | Ethos Guild", description: "Ethos Guild — collaborative music creation, licensing, and creative work on AeThex.", }, }, { pattern: /^\/dev-platform/, meta: { title: "AeThex | Developer Platform", description: "Build with AeThex APIs. Documentation, SDKs, examples, and developer tools for modern builders.", }, }, { pattern: /^\/feed/, meta: { title: "AeThex | Community Feed", description: "What's happening in the AeThex builder community.", }, }, { pattern: /^\/login/, meta: { title: "AeThex | Sign In", description: "Sign in to your AeThex account to access projects, community, and more.", }, }, { pattern: /^\/register/, meta: { title: "AeThex | Create Account", description: "Join AeThex and start building, learning, and collaborating with the community.", }, }, { pattern: /^\/dashboard/, meta: { title: "AeThex | Dashboard", description: "Your personal AeThex dashboard — manage projects, track progress, and connect.", }, }, { pattern: /^\/passport\/me$/, meta: { title: "AeThex | My Passport", description: "View your AeThex developer passport and profile.", }, }, { pattern: /^\/docs/, meta: { title: "AeThex | Documentation", description: "Guides, API references, and developer resources for AeThex.", }, }, { pattern: /^\/pricing/, meta: { title: "AeThex | Pricing", description: "Simple, transparent pricing for AeThex — individuals, teams, and enterprises.", }, }, { pattern: /^\/about/, meta: { title: "AeThex | About", description: "Learn about AeThex — our mission, team, and the technology we build.", }, }, { pattern: /^\/blog/, meta: { title: "AeThex | Blog", description: "News, tutorials, and announcements from the AeThex team and community.", }, }, ]; // UUID pattern const PROJECT_UUID_RE = /^\/projects\/([0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12})$/i; // Passport: /passport/ (not "me", handled above) const PASSPORT_USER_RE = /^\/passport\/([^/]+)$/; function escHtml(s: string): string { return s .replace(/&/g, "&") .replace(/"/g, """) .replace(//g, ">"); } /** * Replaces meta tag content values in the HTML template. * Handles both inline and multi-line attribute formats (Vite preserves them). */ function injectMeta( html: string, meta: RouteMeta & { url: string } ): string { const { title, description, url, image = DEFAULT_OG_IMAGE, type = "website", } = meta; const t = escHtml(title); const d = escHtml(description); const u = escHtml(url); const img = escHtml(image); return ( html // .replace(/<title>[^<]*<\/title>/, `<title>${t}`) // meta name="description" .replace( /( { const url = `${BASE_URL}${pathname}`; // Dynamic: project detail page const projectMatch = PROJECT_UUID_RE.exec(pathname); if (projectMatch && adminSupabase) { const projectId = projectMatch[1]; try { const { data: project } = await (adminSupabase as any) .from("projects") .select("title, description, image_url, status") .eq("id", projectId) .maybeSingle(); if (project) { const desc = project.description ? String(project.description).slice(0, 160) : `View the ${project.title} project on AeThex.`; return { title: `${project.title} — AeThex Project`, description: desc, image: project.image_url || DEFAULT_OG_IMAGE, type: "article", url, }; } } catch { // fall through to default } } // Dynamic: public passport / profile page const passportMatch = PASSPORT_USER_RE.exec(pathname); if (passportMatch && adminSupabase) { const username = passportMatch[1]; try { const { data: profile } = await adminSupabase .from("user_profiles") .select("username, full_name, avatar_url, bio") .eq("username", username) .maybeSingle(); if (profile) { const displayName = (profile as any).full_name || profile.username || username; const bio = (profile as any).bio; const desc = bio ? String(bio).slice(0, 160) : `View ${displayName}'s developer passport and projects on AeThex.`; return { title: `${displayName} — AeThex Passport`, description: desc, url, }; } } catch { // fall through } } // Static route map for (const { pattern, meta } of STATIC_META) { if (pattern.test(pathname)) { return { ...meta, url }; } } // Default fallback return { title: "AeThex | Developer Platform for Builders, Creators & Innovation", description: "AeThex: an advanced development platform and community for builders. Collaborate on projects, learn, and ship innovation.", url, }; } // Cache the HTML template (reset on read error so a rebuild is picked up on restart) let htmlTemplate: string | null = null; function getTemplate(): string { if (!htmlTemplate) { htmlTemplate = readFileSync(path.join(distPath, "index.html"), "utf-8"); } return htmlTemplate; } // ── Route Handler ───────────────────────────────────────────────────────────── app.get("*", async (req, res) => { if (req.path.startsWith("/api/") || req.path.startsWith("/health")) { return res.status(404).json({ error: "API endpoint not found" }); } // Don't serve the SPA shell for missing static-asset requests — they should 404 // cleanly rather than returning HTML (which causes "Unexpected token '<'" JS errors). if (/\.(js|mjs|cjs|css|map|png|jpg|jpeg|gif|svg|ico|woff|woff2|ttf|eot|webp|avif)$/i.test(req.path)) { return res.status(404).send("Not found"); } try { const template = getTemplate(); const meta = await resolveRouteMeta(req.path); const html = injectMeta(template, meta); res.setHeader("Content-Type", "text/html; charset=utf-8"); // Crawlers should not cache — browsers can res.setHeader( "Cache-Control", "public, max-age=60, stale-while-revalidate=300" ); res.send(html); } catch (err) { console.error("[SSR Meta] Error:", err); // Fallback: send unmodified file res.sendFile(path.join(distPath, "index.html")); } }); // ── Server ──────────────────────────────────────────────────────────────────── app.listen(Number(port), host, () => { console.log(`🚀 AeThex server running on ${host}:${port}`); console.log(`📱 Frontend: http://${host}:${port}`); console.log(`🔧 API: http://${host}:${port}/api`); }); process.on("SIGTERM", () => { console.log("🛑 Received SIGTERM, shutting down gracefully"); process.exit(0); }); process.on("SIGINT", () => { console.log("🛑 Received SIGINT, shutting down gracefully"); process.exit(0); });