From 46d2f02da445cc32a2e60753cedfc8672f9edd09 Mon Sep 17 00:00:00 2001 From: sudipnext Date: Mon, 27 Apr 2026 12:01:54 +0545 Subject: [PATCH] feat: update presentation export version and enhance export functionality with cookie handling Co-authored-by: Copilot --- package.json | 2 +- scripts/sync-presentation-export.cjs | 62 ++++++++++++++++--- servers/fastapi/api/middlewares.py | 14 ----- .../app/api/export-presentation/route.ts | 2 + .../lib/run-bundled-presentation-export.ts | 4 +- 5 files changed, 58 insertions(+), 26 deletions(-) diff --git a/package.json b/package.json index d154b650..42b7d613 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "presenton", "version": "1.0.0", - "presentationExportVersion": "v0.2.2", + "presentationExportVersion": "v0.2.6", "type": "module", "description": "Open-source AI presentation generator", "scripts": { diff --git a/scripts/sync-presentation-export.cjs b/scripts/sync-presentation-export.cjs index 2aa101a7..96f9501d 100644 --- a/scripts/sync-presentation-export.cjs +++ b/scripts/sync-presentation-export.cjs @@ -120,13 +120,56 @@ function chmodIfPossible(filePath) { } } -function getConverterCandidates() { +function getConverterCandidates(baseDir = targetPyDir) { return [ - path.join(targetPyDir, "convert-linux-x64"), - path.join(targetPyDir, "convert-linux-amd64"), + path.join(baseDir, "convert-linux-x64"), + path.join(baseDir, "convert-linux-amd64"), + path.join(baseDir, "convert"), ]; } +function hasRuntimeBundle(baseDir) { + const indexPath = path.join(baseDir, "index.js"); + if (!fs.existsSync(indexPath)) { + return false; + } + + const pyCandidates = getConverterCandidates(path.join(baseDir, "py")); + const rootCandidates = getConverterCandidates(baseDir); + return [...pyCandidates, ...rootCandidates].some((candidate) => + fs.existsSync(candidate) + ); +} + +function moveFileAtomic(src, dest) { + try { + fs.renameSync(src, dest); + } catch { + fs.copyFileSync(src, dest); + fs.rmSync(src, { force: true }); + } +} + +function normalizeRuntimeLayout() { + if (!fs.existsSync(targetRoot)) { + return; + } + + ensureDir(targetPyDir); + + const rootCandidates = getConverterCandidates(targetRoot); + for (const sourcePath of rootCandidates) { + if (!fs.existsSync(sourcePath)) { + continue; + } + + const destinationPath = path.join(targetPyDir, path.basename(sourcePath)); + if (!fs.existsSync(destinationPath)) { + moveFileAtomic(sourcePath, destinationPath); + } + } +} + function ensureCommonJsEntrypoint() { if (!fs.existsSync(targetIndexJs)) { return { ok: false, reason: `Missing runtime bundle: ${targetIndexJs}` }; @@ -148,6 +191,8 @@ function ensureCommonJsEntrypoint() { } function validateExistingRuntime() { + normalizeRuntimeLayout(); + const entrypoint = ensureCommonJsEntrypoint(); if (!entrypoint.ok) { return { ok: false, reason: entrypoint.reason }; @@ -158,7 +203,7 @@ function validateExistingRuntime() { if (!converterPath) { return { ok: false, - reason: `No Linux converter binary under ${targetPyDir}.`, + reason: `No Linux converter binary under ${targetPyDir} or ${targetRoot}.`, }; } chmodIfPossible(converterPath); @@ -210,18 +255,15 @@ function unzipArchive(zipPath, destDir) { } function resolveExtractedRoot(extractDir) { - const directIndex = path.join(extractDir, "index.js"); - const directPy = path.join(extractDir, "py"); - if (fs.existsSync(directIndex) && fs.existsSync(directPy)) { + if (hasRuntimeBundle(extractDir)) { return extractDir; } + const children = fs.readdirSync(extractDir, { withFileTypes: true }); for (const entry of children) { if (!entry.isDirectory()) continue; const candidate = path.join(extractDir, entry.name); - const candidateIndex = path.join(candidate, "index.js"); - const candidatePy = path.join(candidate, "py"); - if (fs.existsSync(candidateIndex) && fs.existsSync(candidatePy)) { + if (hasRuntimeBundle(candidate)) { return candidate; } } diff --git a/servers/fastapi/api/middlewares.py b/servers/fastapi/api/middlewares.py index ef01d6bf..b8bcd11c 100644 --- a/servers/fastapi/api/middlewares.py +++ b/servers/fastapi/api/middlewares.py @@ -1,5 +1,3 @@ -import re - from fastapi import Request from starlette.responses import JSONResponse from starlette.middleware.base import BaseHTTPMiddleware @@ -25,12 +23,6 @@ class SessionAuthMiddleware(BaseHTTPMiddleware): _EXEMPT_PREFIXES = ( "/api/v1/auth/", ) - # PPTX/PDF export loads /pdf-maker in a headless browser with no session cookie; it - # only needs a single-deck read by id. (UUID is not a secret; this matches prior behavior - # when auth middleware did not protect these routes during export.) - _PRESENTATION_GET_BY_ID = re.compile( - r"^/api/v1/ppt/presentation/[0-9a-fA-F-]{36}/?$" - ) _PROTECTED_NON_API_PATHS = { "/docs", "/openapi.json", @@ -40,11 +32,6 @@ class SessionAuthMiddleware(BaseHTTPMiddleware): def _is_exempt(self, path: str) -> bool: return any(path.startswith(prefix) for prefix in self._EXEMPT_PREFIXES) - def _is_presentation_get_by_id(self, request: Request, path: str) -> bool: - if request.method != "GET": - return False - return bool(self._PRESENTATION_GET_BY_ID.match(path)) - def _requires_auth(self, path: str) -> bool: if path.startswith("/api/"): return True @@ -59,7 +46,6 @@ class SessionAuthMiddleware(BaseHTTPMiddleware): request.method == "OPTIONS" or not self._requires_auth(path) or self._is_exempt(path) - or self._is_presentation_get_by_id(request, path) ): return await call_next(request) diff --git a/servers/nextjs/app/api/export-presentation/route.ts b/servers/nextjs/app/api/export-presentation/route.ts index 5a0c0648..f61cae11 100644 --- a/servers/nextjs/app/api/export-presentation/route.ts +++ b/servers/nextjs/app/api/export-presentation/route.ts @@ -12,6 +12,7 @@ function isValidFormat(value: unknown): value is BundledPresentationExportFormat export async function POST(req: NextRequest) { const { format, id, title } = await req.json(); + const cookieHeader = req.headers.get("cookie") ?? ""; if (!id) { return NextResponse.json( @@ -38,6 +39,7 @@ export async function POST(req: NextRequest) { format, presentationId: id, title, + cookieHeader, }); return NextResponse.json({ diff --git a/servers/nextjs/lib/run-bundled-presentation-export.ts b/servers/nextjs/lib/run-bundled-presentation-export.ts index 8b240515..15b8ec34 100644 --- a/servers/nextjs/lib/run-bundled-presentation-export.ts +++ b/servers/nextjs/lib/run-bundled-presentation-export.ts @@ -116,8 +116,9 @@ export async function runBundledPresentationExport(params: { presentationId: string; title: string | undefined; format: BundledPresentationExportFormat; + cookieHeader?: string; }): Promise { - const { presentationId, title, format } = params; + const { presentationId, title, format, cookieHeader } = params; const exportRoot = getExportPackageRoot(); const entrypoint = await resolveExportEntrypoint(exportRoot); const converter = bundledConverterPath(exportRoot); @@ -146,6 +147,7 @@ export async function runBundledPresentationExport(params: { format, title: sanitizeFilename(title ?? "presentation"), fastapiUrl: fastapiUrl || undefined, + cookieHeader: cookieHeader || undefined, }; await fs.writeFile(exportTaskPath, JSON.stringify(exportTask), "utf8");