import { dir } from "dir"; import { apiContext } from "service-srv"; import { validate } from "uuid"; import { parseTypeDef } from "../util/parse-type-def"; import { prodIndex } from "../util/prod-index"; import { code } from "../ws/sync/code/code"; import { initFrontEnd } from "../ws/sync/code/parts/init/frontend"; import { gzipAsync } from "../ws/sync/entity/zlib"; import { ensureLib } from "../ws/sync/code/utlis/ensure-lib"; import { ensureFiles } from "../ws/sync/code/utlis/ensure-files"; import { g } from "utils/global"; import brotliPromise from "brotli-wasm"; import mime from "mime"; const encoder = new TextEncoder(); export const _ = { url: "/prod/:site_id/**", async api() { const { req, prasi } = apiContext(this); const pathname: string = req.params["*"] || ""; const site_id = req.params.site_id as string; const index_html = new Response(prodIndex(site_id, prasi).render(), { headers: { "content-type": "text/html" }, }); if (!validate(site_id)) return new Response("site not found", { status: 403 }); if (pathname.startsWith("_prasi")) { const action = pathname.split("/")[1]; switch (action) { case "prisma.ext": { const path = dir.path( `app/srv/ws/sync/code/templates/typings/prisma_ext_d_ts` ); const file = Bun.file(path); return new Response(file); } case "type_def": { const path = dir.data(`/code/${site_id}/site/typings.d.ts`); const file = Bun.file(path); if (await file.exists()) { // const glob = new Glob("type_def*"); // for await (const item of glob.scan( // dir.data(`/code/${site_id}/site`) // )) { // const stamp = parseInt(item.split(".")[1]); // if (file.lastModified !== stamp) { // await removeAsync(dir.data(`/code/${site_id}/site/${item}`)); // } else { // return new Response( // Bun.gzipSync( // await Bun.file( // dir.data(`/code/${site_id}/site/${item}`) // ).arrayBuffer() // ), // { // headers: { // "content-type": "application/json", // "content-encoding": "gzip", // }, // } // ); // } // } try { const res = JSON.stringify(await parseTypeDef(path)); await Bun.write( dir.data( `/code/${site_id}/site/type_def.${file.lastModified}.json` ), res ); return new Response(Bun.gzipSync(res), { headers: { "content-type": "application/json", "content-encoding": "gzip", }, }); } catch (e) {} } return new Response("{}", { headers: { "content-type": "application/json" }, }); } case "typings.d.ts": { const build_path = dir.data(`/code/${site_id}/site/typings.d.ts`); let file = Bun.file(build_path); if (!(await file.exists())) { const root = `/code/${site_id}/site/src`; await initFrontEnd(root, site_id); file = Bun.file(build_path); } if (await file.exists()) { const body = Bun.gzipSync(await file.arrayBuffer()); return new Response(body, { headers: { "content-type": file.type, "content-encoding": "gzip", }, }); } return new Response(""); } case "code": { const arr = pathname.split("/").slice(2); const codepath = arr.join("/"); const build_path = code.path(site_id, "site", "build", codepath); let file = Bun.file(build_path); if (!(await file.exists())) { const root = `/code/${site_id}/site/src`; await ensureLib(root, site_id); await ensureFiles(root, site_id); await initFrontEnd(root, site_id); await new Promise((resolve) => { const ival = setInterval(async () => { file = Bun.file(build_path); const exists = await file.exists(); if (exists) { clearInterval(ival); resolve(); } }, 100); }); } const ts = file.lastModified; if (!g.code_index_cache) g.code_index_cache = {}; if (!g.code_index_cache[site_id]) g.code_index_cache[site_id] = {}; if ( !g.code_index_cache[site_id][build_path] || (g.code_index_cache[site_id][build_path] && g.code_index_cache[site_id][build_path].ts !== ts) ) { if (!g.code_index_compressing) g.code_index_compressing = new Set(); const key = `${site_id}-${build_path}`; if (!g.code_index_compressing.has(key)) { g.code_index_compressing.add(key); setTimeout(async () => { if (!g.br) { g.br = await brotliPromise; } g.code_index_cache[site_id][build_path] = { ts, content: g.br.compress( new Uint8Array(await file.arrayBuffer()) ), type: mime.getType(build_path) || "", }; g.code_index_compressing.delete(key); }, 100); } } if ( g.code_index_cache[site_id] && g.code_index_cache[site_id][build_path] && g.code_index_cache[site_id][build_path].content ) { return new Response( g.code_index_cache[site_id][build_path].content, { headers: { "content-encoding": "br", "content-type": g.code_index_cache[site_id][build_path].type, }, } ); } return new Response( await gzipAsync(new Uint8Array(await file.arrayBuffer())), { headers: { "content-encoding": "gzip", "content-type": mime.getType(build_path) || "", }, } ); } case "route": { if (!g.route_cache) g.route_cache = {}; if (g.route_cache[site_id]) { return new Response(g.route_cache[site_id], { headers: { "content-type": "application/json", "content-encoding": "br", }, }); } const site = await _db.site.findFirst({ where: { id: site_id }, select: { id: true, name: true, domain: true, responsive: true, config: true, }, }); const layouts = await _db.page.findMany({ where: { name: { startsWith: "layout:" }, is_deleted: false, id_site: site_id, }, select: { id: true, name: true, is_default_layout: true, content_tree: true, }, }); let layout = null as any; for (const l of layouts) { if (!layout) layout = l; if (l.is_default_layout) layout = l; } let api_url = ""; if (site && site.config && (site.config as any).api_url) { api_url = (site.config as any).api_url; delete (site as any).config; } const urls = await _db.page.findMany({ where: { id_site: site_id, is_default_layout: false, is_deleted: false, }, select: { url: true, id: true }, }); const res = JSON.stringify({ site: { ...site, api_url }, urls, layout: layout ? { id: layout.id, root: layout.content_tree } : undefined, }); if (!g.br) { g.br = await brotliPromise; } setTimeout(() => { if (!g.route_cache_compressing) g.route_cache_compressing = new Set(); if (g.route_cache_compressing.has(site_id)) return; g.route_cache_compressing.add(site_id); g.route_cache[site_id] = g.br.compress(encoder.encode(res)); g.route_cache_compressing.delete(site_id); }, 100); return new Response(await gzipAsync(res), { headers: { "content-type": "application/json", "content-encoding": "gzip", }, }); } case "page": { const page_id = pathname.split("/").pop() as string; if (validate(page_id)) { const page = await _db.page.findFirst({ where: { id: page_id }, select: { content_tree: true, url: true }, }); if (page) { return await responseCompressed( req, JSON.stringify({ id: page_id, root: page.content_tree, url: page.url, }) as any ); } } return null; } case "pages": { const page_ids = req.params.ids as string[]; if (page_ids) { const ids = page_ids.filter((id) => validate(id)); const pages = await _db.page.findMany({ where: { id: { in: ids } }, select: { id: true, content_tree: true, url: true }, }); if (pages) { return await responseCompressed( req, JSON.stringify( pages.map((e: any) => ({ id: e.id, url: e.url, root: e.content_tree, })) ) as any ); } } break; } case "comp": { const ids = req.params.ids as string[]; const result = {} as Record; if (Array.isArray(ids)) { const comps = await _db.component.findMany({ where: { id: { in: ids } }, select: { content_tree: true, id: true }, }); for (const comp of comps) { result[comp.id] = comp.content_tree; } } return await responseCompressed(req, JSON.stringify(result) as any); } } return new Response("action " + action + ": not found"); } else if (pathname === "index.html" || pathname === "_") { return index_html; } else { const res = dir.path(`/app/srv/core/${pathname}`); const file = Bun.file(res); if (!(await file.exists())) { return index_html; } return new Response(file); } }, }; const responseCompressed = async (req: Request, body: string | Uint8Array) => { if (req.headers.get("accept-encoding")?.includes("gz")) { return new Response(await gzipAsync(body), { headers: { "content-encoding": "gzip" }, }); } return new Response(body); };