| import Fastify from "fastify"; |
| import fastifyStatic from "@fastify/static"; |
| import { createProxyMiddleware } from "http-proxy-middleware"; |
| import { fileURLToPath } from "node:url"; |
| import path from "node:path"; |
| import fs from "node:fs"; |
|
|
| const __dirname = path.dirname(fileURLToPath(import.meta.url)); |
| const port = Number(process.env.PORT || 7860); |
|
|
| function normalizeApiUrl(value) { |
| let url = String(value || "").trim().replace(/\/+$/, ""); |
| if (!url) return ""; |
| if (!/^https?:\/\//i.test(url)) url = `http://${url}`; |
| const parsed = new URL(url); |
| if (!parsed.port) parsed.port = "8190"; |
| return parsed.toString().replace(/\/+$/, ""); |
| } |
|
|
| const apiTarget = normalizeApiUrl(process.env.NEMOFLIX_API_URL || process.env.NEMOFLIX_AMD_API_URL || ""); |
| const app = Fastify({ logger: true }); |
| const distDir = path.join(__dirname, "studio", "dist"); |
|
|
| const backendProxy = apiTarget |
| ? createProxyMiddleware({ |
| target: apiTarget, |
| changeOrigin: true, |
| ws: true, |
| logLevel: "warn", |
| timeout: 30_000, |
| proxyTimeout: 30_000, |
| on: { |
| error: (err, _req, res) => { |
| app.log.error({ err, apiTarget }, "backend proxy error"); |
| if (!res.headersSent) { |
| res.writeHead(502, { "Content-Type": "application/json" }); |
| } |
| res.end(JSON.stringify({ detail: `Backend unavailable: ${err.message}` })); |
| }, |
| }, |
| }) |
| : null; |
|
|
| app.addHook("onRequest", async (request, reply) => { |
| if (request.url.startsWith("/api/") || request.url === "/api" || request.url.startsWith("/media/") || request.url === "/media") { |
| if (!backendProxy) { |
| return reply.code(503).send({ detail: "NEMOFLIX_API_URL is not configured" }); |
| } |
| await new Promise((resolve, reject) => backendProxy(request.raw, reply.raw, (err) => err ? reject(err) : resolve())); |
| reply.hijack(); |
| } |
| }); |
|
|
| app.get("/space-health", async () => ({ ok: true, apiConfigured: Boolean(apiTarget), apiTarget: apiTarget || null })); |
|
|
| await app.register(fastifyStatic, { root: distDir, prefix: "/" }); |
|
|
| app.setNotFoundHandler((request, reply) => { |
| const index = path.join(distDir, "index.html"); |
| if (fs.existsSync(index)) return reply.type("text/html").send(fs.createReadStream(index)); |
| return reply.code(404).send("Studio build not found"); |
| }); |
|
|
| app.listen({ host: "0.0.0.0", port }); |
|
|