← back to Watches

utils/pdf-generator.js

200 lines

/**
 * PDF Report Generator using Puppeteer
 * Converts HTML analytics reports to professional PDFs
 */

import puppeteer from 'puppeteer';
import fs from 'fs';
import path from 'path';
import { fileURLToPath } from 'url';

const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);

class PDFGenerator {
  constructor() {
    this.browser = null;
    this.outputDir = path.join(__dirname, '../analytics/reports/pdf');

    if (!fs.existsSync(this.outputDir)) {
      fs.mkdirSync(this.outputDir, { recursive: true });
    }
  }

  /**
   * Initialize browser
   */
  async init() {
    if (!this.browser) {
      this.browser = await puppeteer.launch({
        headless: true,
        args: [
          '--no-sandbox',
          '--disable-setuid-sandbox',
          '--disable-dev-shm-usage',
          '--disable-gpu'
        ]
      });
    }
    return this.browser;
  }

  /**
   * Generate PDF from HTML file
   */
  async generateFromHTMLFile(htmlPath, outputName) {
    await this.init();

    const page = await this.browser.newPage();

    try {
      const htmlContent = fs.readFileSync(htmlPath, 'utf8');
      await page.setContent(htmlContent, { waitUntil: 'networkidle0' });

      const pdfPath = path.join(this.outputDir, `${outputName}.pdf`);

      await page.pdf({
        path: pdfPath,
        format: 'A4',
        printBackground: true,
        margin: {
          top: '20mm',
          right: '15mm',
          bottom: '20mm',
          left: '15mm'
        }
      });

      console.log(`✓ PDF generated: ${pdfPath}`);
      return pdfPath;
    } catch (error) {
      console.error(`✗ PDF generation failed:`, error);
      throw error;
    } finally {
      await page.close();
    }
  }

  /**
   * Generate PDF from HTML string
   */
  async generateFromHTML(htmlContent, outputName) {
    await this.init();

    const page = await this.browser.newPage();

    try {
      await page.setContent(htmlContent, { waitUntil: 'networkidle0' });

      const pdfPath = path.join(this.outputDir, `${outputName}.pdf`);

      await page.pdf({
        path: pdfPath,
        format: 'A4',
        printBackground: true,
        margin: {
          top: '20mm',
          right: '15mm',
          bottom: '20mm',
          left: '15mm'
        }
      });

      console.log(`✓ PDF generated: ${pdfPath}`);
      return pdfPath;
    } catch (error) {
      console.error(`✗ PDF generation failed:`, error);
      throw error;
    } finally {
      await page.close();
    }
  }

  /**
   * Generate PDF from URL
   */
  async generateFromURL(url, outputName) {
    await this.init();

    const page = await this.browser.newPage();

    try {
      await page.goto(url, { waitUntil: 'networkidle0', timeout: 30000 });

      const pdfPath = path.join(this.outputDir, `${outputName}.pdf`);

      await page.pdf({
        path: pdfPath,
        format: 'A4',
        printBackground: true,
        margin: {
          top: '20mm',
          right: '15mm',
          bottom: '20mm',
          left: '15mm'
        }
      });

      console.log(`✓ PDF generated from URL: ${pdfPath}`);
      return pdfPath;
    } catch (error) {
      console.error(`✗ PDF generation from URL failed:`, error);
      throw error;
    } finally {
      await page.close();
    }
  }

  /**
   * Generate analytics report PDF
   */
  async generateAnalyticsReport(reportType = 'daily') {
    const url = `http://localhost:7600/analytics/reports/${reportType}-report-${new Date().toISOString().split('T')[0]}.html`;
    const outputName = `${reportType}-analytics-${new Date().toISOString().split('T')[0]}`;

    return await this.generateFromURL(url, outputName);
  }

  /**
   * Close browser
   */
  async close() {
    if (this.browser) {
      await this.browser.close();
      this.browser = null;
    }
  }
}

export default PDFGenerator;

// CLI usage
if (import.meta.url === `file://${process.argv[1]}`) {
  const generator = new PDFGenerator();
  const args = process.argv.slice(2);

  if (args.length === 0) {
    console.log('Usage: node pdf-generator.js <html-file-path> [output-name]');
    console.log('   or: node pdf-generator.js --url <url> [output-name]');
    console.log('   or: node pdf-generator.js --analytics [daily|weekly|monthly]');
    process.exit(1);
  }

  (async () => {
    try {
      if (args[0] === '--url') {
        await generator.generateFromURL(args[1], args[2] || 'output');
      } else if (args[0] === '--analytics') {
        await generator.generateAnalyticsReport(args[1] || 'daily');
      } else {
        await generator.generateFromHTMLFile(args[0], args[1] || 'output');
      }
    } catch (error) {
      console.error('Error:', error);
      process.exit(1);
    } finally {
      await generator.close();
    }
  })();
}