import fs from "fs/promises"; import path from "path"; import type { PageOutline } from "./gemini"; const DATA_DIR = process.env.DATA_DIR || path.join(process.cwd(), "data"); const ZINES_DIR = path.join(DATA_DIR, "zines"); export interface StoredZine { id: string; topic: string; style: string; tone: string; outline: PageOutline[]; pages: string[]; // Paths to page images (p1.png - p8.png) printLayout?: string; // Path to final print layout createdAt: string; updatedAt: string; } async function ensureDir(dir: string): Promise { try { await fs.access(dir); } catch { await fs.mkdir(dir, { recursive: true }); } } export async function saveZine(zine: StoredZine): Promise { const zineDir = path.join(ZINES_DIR, zine.id); await ensureDir(zineDir); const metadataPath = path.join(zineDir, "metadata.json"); await fs.writeFile(metadataPath, JSON.stringify(zine, null, 2)); } export async function getZine(id: string): Promise { try { const metadataPath = path.join(ZINES_DIR, id, "metadata.json"); const data = await fs.readFile(metadataPath, "utf-8"); return JSON.parse(data); } catch { return null; } } export async function savePageImage( zineId: string, pageNumber: number, imageData: Buffer | string ): Promise { const zineDir = path.join(ZINES_DIR, zineId); const pagesDir = path.join(zineDir, "pages"); await ensureDir(pagesDir); const filename = `p${pageNumber}.png`; const filepath = path.join(pagesDir, filename); if (typeof imageData === "string") { // Handle base64 data URL if (imageData.startsWith("data:")) { const base64Data = imageData.split(",")[1]; await fs.writeFile(filepath, Buffer.from(base64Data, "base64")); } else { // Assume it's already base64 await fs.writeFile(filepath, Buffer.from(imageData, "base64")); } } else { await fs.writeFile(filepath, imageData); } return filepath; } export async function getPageImagePath(zineId: string, pageNumber: number): Promise { const filepath = path.join(ZINES_DIR, zineId, "pages", `p${pageNumber}.png`); try { await fs.access(filepath); return filepath; } catch { return null; } } export async function getAllPagePaths(zineId: string): Promise { const paths: string[] = []; for (let i = 1; i <= 8; i++) { const pagePath = await getPageImagePath(zineId, i); if (pagePath) { paths.push(pagePath); } } return paths; } export async function savePrintLayout(zineId: string, imageData: Buffer): Promise { const zineDir = path.join(ZINES_DIR, zineId); await ensureDir(zineDir); const filepath = path.join(zineDir, "print.png"); await fs.writeFile(filepath, imageData); return filepath; } export async function getPrintLayoutPath(zineId: string): Promise { const filepath = path.join(ZINES_DIR, zineId, "print.png"); try { await fs.access(filepath); return filepath; } catch { return null; } } export async function readFileAsBuffer(filepath: string): Promise { return fs.readFile(filepath); } export async function readFileAsBase64(filepath: string): Promise { const buffer = await fs.readFile(filepath); return buffer.toString("base64"); } export async function listAllZines(): Promise { try { await ensureDir(ZINES_DIR); const entries = await fs.readdir(ZINES_DIR, { withFileTypes: true }); return entries.filter((e) => e.isDirectory()).map((e) => e.name); } catch { return []; } } export async function deleteZine(id: string): Promise { const zineDir = path.join(ZINES_DIR, id); try { await fs.rm(zineDir, { recursive: true }); } catch { // Ignore if doesn't exist } } // Get URL-safe path for serving images export function getPublicImageUrl(zineId: string, filename: string): string { return `/api/zine/${zineId}/image/${filename}`; } export function getPrintLayoutUrl(zineId: string): string { return `/api/zine/${zineId}/print`; }