feat: update presentation export version and enhance export functionality with cookie handling

Co-authored-by: Copilot <copilot@github.com>
This commit is contained in:
sudipnext 2026-04-27 12:01:54 +05:45
parent 4fd1a84f59
commit 46d2f02da4
5 changed files with 58 additions and 26 deletions

View file

@ -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": {

View file

@ -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;
}
}

View file

@ -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)

View file

@ -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({

View file

@ -116,8 +116,9 @@ export async function runBundledPresentationExport(params: {
presentationId: string;
title: string | undefined;
format: BundledPresentationExportFormat;
cookieHeader?: string;
}): Promise<BundledPresentationExportResult> {
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");