import express from 'express'; import cors from 'cors'; import { tmpdir } from 'os'; import { join } from 'path'; import { promises as fs } from 'fs'; import { v4 as uuidV4 } from 'uuid'; import Epub from 'epub-gen'; const app = express(); const PORT = process.env.PORT || 4000; const ORIGIN = process.env.CLIENT_ORIGIN || 'http://localhost:5173'; app.use(cors({ origin: ORIGIN, credentials: true })); app.use(express.json({ limit: '10mb' })); const sanitizeHtml = (text = '') => text .replace(/&/g, '&') .replace(//g, '>') .replace(/"/g, '"') .replace(/'/g, ''') .replace(/\n/g, '
'); app.post('/generate-epub', async (req, res) => { const { text, meta, cover } = req.body || {}; if (!text || !text.trim()) { return res.status(400).json({ message: 'text is required' }); } const title = meta?.title || 'imgPub OCR Export'; const author = meta?.author || 'imgPub'; const filename = meta?.filename || `imgpub${Date.now()}.epub`; const content = [ { title, data: `
${sanitizeHtml(text)}
`, }, ]; const outputPath = join(tmpdir(), `imgpub-${uuidV4()}.epub`); let coverPath; try { if (cover?.data) { const coverBuffer = Buffer.from(cover.data, 'base64'); const coverExtension = cover?.mimeType?.split('/').pop() || cover?.filename?.split('.').pop() || 'png'; coverPath = join(tmpdir(), `imgpub-cover-${uuidV4()}.${coverExtension}`); await fs.writeFile(coverPath, coverBuffer); } const epubOptions = { title, author, content }; if (coverPath) { epubOptions.cover = coverPath; } const epub = new Epub(epubOptions, outputPath); await epub.promise; const buffer = await fs.readFile(outputPath); await fs.unlink(outputPath).catch(() => {}); if (coverPath) { await fs.unlink(coverPath).catch(() => {}); } res.json({ filename, data: buffer.toString('base64') }); } catch (error) { console.error('EPUB generation failed:', error); res.status(500).json({ message: 'EPUB generation failed' }); } }); app.get('/', (_, res) => { res.json({ status: 'ok' }); }); app.listen(PORT, () => { console.log(`imgPub EPUB server listening on port ${PORT}`); });