const fs = require('fs'); const path = require('path'); const { execFile } = require('child_process'); const { v4: uuidv4 } = require('uuid'); const express = require('express'); // JSON解析用にexpressをインポート const { API_KEY } = require('../config.js'); // API_KEYをインポート // LibreOffice AppImageのパス const libreofficeAppImage = './modules/LibreOffice-still.standard-x86_64.AppImage'; // tmpディレクトリのパス const tmpDir = path.join(__dirname, 'tmp'); // tmpディレクトリが存在しない場合は作成 if (!fs.existsSync(tmpDir)) { fs.mkdirSync(tmpDir); } // POSTエンドポイント module.exports = (app) => { app.post('/pdfConvert', express.json({ limit: '30mb' }), async (req, res) => { try { // APIキーのチェック const apiKey = req.headers['x-api-key']; if (!apiKey || apiKey !== API_KEY) { console.log('APIキーが不正です'); return res.status(401).json({ error: 'APIキーが不正です' }); } const { base64, fileName } = req.body; if (!base64 || !fileName) { return res.status(400).json({ error: 'base64とfileNameは必須です' }); } const pdfBuffer = await convertBase64ToPdf(base64, fileName); const safeFileName = encodeURIComponent(path.parse(fileName).name); // ファイル名をエスケープ res.set({ 'Content-Type': 'application/pdf', 'Content-Disposition': `attachment; filename="${safeFileName}.pdf"` }); res.send(pdfBuffer); } catch (err) { console.error('変換エラー:', err.message); res.status(500).json({ error: err.message }); } }); }; // PDF変換処理 async function convertBase64ToPdf(base64Data, originalFileName) { const tempId = uuidv4(); // 一意のIDを生成 const ext = path.extname(originalFileName) || '.tmp'; // 拡張子を取得、なければ.tmpを使用 const orgPath = path.join(tmpDir, `${tempId}${ext}`); // 一時的なファイルのパス const pdfPath = path.join(tmpDir, `${tempId}.pdf`); // base64データをファイルに保存 fs.writeFileSync(orgPath, Buffer.from(base64Data, 'base64')); // LibreOfficeでPDF変換(タイムアウト60秒) await new Promise((resolve, reject) => { const child = execFile( libreofficeAppImage, [ '--headless', '--convert-to', 'pdf', '--outdir', tmpDir, orgPath ], (error, stdout, stderr) => { if (error) { console.error('stderr:', stderr); reject(new Error(`LibreOfficeエラー: ${stderr}`)); } else { resolve(); } } ); const timeout = setTimeout(() => { child.kill('SIGTERM'); // SIGTERMを使用してプロセスを終了 reject(new Error('PDF変換がタイムアウトしました(60秒経過)')); }, 60000); child.on('exit', () => clearTimeout(timeout)); }); // PDFファイルを読み込む if (!fs.existsSync(pdfPath)) { throw new Error('PDF変換に失敗しました'); } const pdfBuffer = fs.readFileSync(pdfPath); // cleanup try { if (fs.existsSync(orgPath)) fs.unlinkSync(orgPath); if (fs.existsSync(pdfPath)) fs.unlinkSync(pdfPath); } catch (cleanupError) { console.error('クリーンアップエラー:', cleanupError.message); } return pdfBuffer; }