378 lines
11 KiB
Python
378 lines
11 KiB
Python
#!/usr/bin/env python3
|
|
"""
|
|
Flask API for Salesforce PDF Generation
|
|
Takes HTML content from Salesforce and returns a downloadable PDF
|
|
"""
|
|
|
|
from flask import Flask, request, send_file, jsonify
|
|
from flask_cors import CORS
|
|
import pdfkit
|
|
import os
|
|
import tempfile
|
|
import base64
|
|
from datetime import datetime
|
|
import logging
|
|
import json
|
|
|
|
# Configure logging
|
|
logging.basicConfig(level=logging.INFO)
|
|
logger = logging.getLogger(__name__)
|
|
|
|
app = Flask(__name__)
|
|
CORS(app)
|
|
|
|
# Configure pdfkit options for better PDF generation
|
|
PDF_OPTIONS = {
|
|
'page-size': 'A4',
|
|
'margin-top': '0.75in',
|
|
'margin-right': '0.75in',
|
|
'margin-bottom': '0.75in',
|
|
'margin-left': '0.75in',
|
|
'encoding': "UTF-8",
|
|
'no-outline': None,
|
|
'enable-local-file-access': None,
|
|
'print-media-type': None,
|
|
'dpi': 300,
|
|
'image-quality': 100,
|
|
'javascript-delay': 1000,
|
|
'no-stop-slow-scripts': None,
|
|
'custom-header': [
|
|
('Accept-Encoding', 'gzip')
|
|
]
|
|
}
|
|
|
|
@app.route('/health', methods=['GET'])
|
|
def health_check():
|
|
"""Health check endpoint"""
|
|
return jsonify({
|
|
'status': 'healthy',
|
|
'timestamp': datetime.now().isoformat(),
|
|
'service': 'Salesforce PDF Generator API'
|
|
})
|
|
|
|
@app.route('/generate-pdf', methods=['POST'])
|
|
def generate_pdf():
|
|
"""
|
|
Generate PDF from HTML content sent from Salesforce
|
|
Expected JSON payload:
|
|
{
|
|
"html_content": "<html>...</html>",
|
|
"property_data": {...},
|
|
"template_name": "everkind",
|
|
"filename": "property_brochure.pdf"
|
|
}
|
|
"""
|
|
try:
|
|
logger.info("Received PDF generation request from Salesforce")
|
|
|
|
# Get request data
|
|
data = request.get_json()
|
|
|
|
if not data:
|
|
return jsonify({'error': 'No data provided'}), 400
|
|
|
|
html_content = data.get('html_content')
|
|
property_data = data.get('property_data', {})
|
|
template_name = data.get('template_name', 'default')
|
|
filename = data.get('filename', f'property_brochure_{datetime.now().strftime("%Y%m%d_%H%M%S")}.pdf')
|
|
|
|
if not html_content:
|
|
return jsonify({'error': 'HTML content is required'}), 400
|
|
|
|
logger.info(f"Processing template: {template_name}")
|
|
logger.info(f"Property data keys: {list(property_data.keys()) if property_data else 'None'}")
|
|
logger.info(f"HTML content length: {len(html_content)}")
|
|
|
|
# Create complete HTML document with proper styling
|
|
complete_html = create_complete_html_document(html_content, property_data, template_name)
|
|
|
|
# Generate PDF
|
|
pdf_path = generate_pdf_from_html(complete_html, filename)
|
|
|
|
if not pdf_path or not os.path.exists(pdf_path):
|
|
return jsonify({'error': 'Failed to generate PDF'}), 500
|
|
|
|
logger.info(f"PDF generated successfully: {pdf_path}")
|
|
|
|
# Return the PDF file
|
|
return send_file(
|
|
pdf_path,
|
|
as_attachment=True,
|
|
download_name=filename,
|
|
mimetype='application/pdf'
|
|
)
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error generating PDF: {str(e)}")
|
|
return jsonify({'error': f'PDF generation failed: {str(e)}'}), 500
|
|
|
|
def create_complete_html_document(html_content, property_data, template_name):
|
|
"""Create a complete HTML document with proper styling and data"""
|
|
|
|
# Base CSS styles for consistent PDF output
|
|
base_css = """
|
|
<style>
|
|
@page {
|
|
size: A4;
|
|
margin: 0.75in;
|
|
}
|
|
|
|
body {
|
|
font-family: 'Arial', 'Helvetica', sans-serif;
|
|
line-height: 1.6;
|
|
color: #333;
|
|
margin: 0;
|
|
padding: 0;
|
|
background: white;
|
|
}
|
|
|
|
.property-header {
|
|
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
|
|
color: white;
|
|
padding: 30px;
|
|
text-align: center;
|
|
border-radius: 10px;
|
|
margin-bottom: 25px;
|
|
page-break-inside: avoid;
|
|
}
|
|
|
|
.property-header h1 {
|
|
margin: 0;
|
|
font-size: 28px;
|
|
font-weight: bold;
|
|
text-shadow: 0 2px 4px rgba(0,0,0,0.3);
|
|
}
|
|
|
|
.property-header p {
|
|
margin: 10px 0 0 0;
|
|
font-size: 16px;
|
|
opacity: 0.95;
|
|
}
|
|
|
|
.property-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 15px;
|
|
margin-bottom: 25px;
|
|
page-break-inside: avoid;
|
|
}
|
|
|
|
.property-card {
|
|
background: #f8f9fa;
|
|
padding: 15px;
|
|
border-radius: 8px;
|
|
border: 1px solid #dee2e6;
|
|
text-align: center;
|
|
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
|
|
}
|
|
|
|
.property-card-label {
|
|
font-weight: bold;
|
|
color: #667eea;
|
|
font-size: 12px;
|
|
text-transform: uppercase;
|
|
margin-bottom: 5px;
|
|
}
|
|
|
|
.property-card-value {
|
|
font-size: 16px;
|
|
color: #333;
|
|
font-weight: 600;
|
|
}
|
|
|
|
.property-details {
|
|
background: white;
|
|
padding: 20px;
|
|
border-radius: 10px;
|
|
border: 1px solid #dee2e6;
|
|
margin-bottom: 25px;
|
|
page-break-inside: avoid;
|
|
}
|
|
|
|
.property-details h2 {
|
|
color: #667eea;
|
|
margin-bottom: 15px;
|
|
font-size: 18px;
|
|
border-bottom: 2px solid #667eea;
|
|
padding-bottom: 5px;
|
|
}
|
|
|
|
.property-details-grid {
|
|
display: grid;
|
|
grid-template-columns: 1fr 1fr;
|
|
gap: 10px;
|
|
font-size: 14px;
|
|
}
|
|
|
|
.content-section {
|
|
background: #f8f9fa;
|
|
padding: 20px;
|
|
border-radius: 10px;
|
|
margin-bottom: 25px;
|
|
page-break-inside: avoid;
|
|
}
|
|
|
|
.content-section h2 {
|
|
color: #667eea;
|
|
margin-bottom: 15px;
|
|
font-size: 18px;
|
|
}
|
|
|
|
.footer {
|
|
text-align: center;
|
|
color: #666;
|
|
font-size: 12px;
|
|
border-top: 1px solid #dee2e6;
|
|
padding-top: 15px;
|
|
margin-top: 30px;
|
|
page-break-inside: avoid;
|
|
}
|
|
|
|
/* Ensure proper page breaks */
|
|
.page-break {
|
|
page-break-before: always;
|
|
}
|
|
|
|
/* Print-specific styles */
|
|
@media print {
|
|
body { margin: 0; }
|
|
.property-header { break-inside: avoid; }
|
|
.property-grid { break-inside: avoid; }
|
|
.property-details { break-inside: avoid; }
|
|
}
|
|
</style>
|
|
"""
|
|
|
|
# Create the complete HTML document
|
|
complete_html = f"""
|
|
<!DOCTYPE html>
|
|
<html lang="en">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Property Brochure - {property_data.get('propertyName', 'Property')}</title>
|
|
{base_css}
|
|
</head>
|
|
<body>
|
|
{html_content}
|
|
|
|
<div class="footer">
|
|
<p><strong>Generated on:</strong> {datetime.now().strftime('%B %d, %Y at %I:%M %p')}</p>
|
|
<p><em>Property CRM System - Professional Brochure</em></p>
|
|
<p><em>Template: {template_name}</em></p>
|
|
</div>
|
|
</body>
|
|
</html>
|
|
"""
|
|
|
|
return complete_html
|
|
|
|
def generate_pdf_from_html(html_content, filename):
|
|
"""Generate PDF from HTML content using pdfkit"""
|
|
try:
|
|
# Create temporary file for the PDF
|
|
temp_dir = tempfile.gettempdir()
|
|
pdf_path = os.path.join(temp_dir, filename)
|
|
|
|
logger.info(f"Generating PDF at: {pdf_path}")
|
|
|
|
# Configure pdfkit with wkhtmltopdf
|
|
try:
|
|
# Try to use system wkhtmltopdf
|
|
config = pdfkit.configuration(wkhtmltopdf='/usr/bin/wkhtmltopdf')
|
|
pdfkit.from_string(html_content, pdf_path, options=PDF_OPTIONS, configuration=config)
|
|
except Exception as e:
|
|
logger.warning(f"System wkhtmltopdf failed: {e}")
|
|
# Try without configuration (uses PATH)
|
|
pdfkit.from_string(html_content, pdf_path, options=PDF_OPTIONS)
|
|
|
|
if os.path.exists(pdf_path):
|
|
file_size = os.path.getsize(pdf_path)
|
|
logger.info(f"PDF generated successfully. Size: {file_size} bytes")
|
|
return pdf_path
|
|
else:
|
|
logger.error("PDF file was not created")
|
|
return None
|
|
|
|
except Exception as e:
|
|
logger.error(f"Error in generate_pdf_from_html: {str(e)}")
|
|
return None
|
|
|
|
@app.route('/test-pdf', methods=['GET'])
|
|
def test_pdf():
|
|
"""Test endpoint to verify PDF generation works"""
|
|
try:
|
|
test_html = """
|
|
<div class="property-header">
|
|
<h1>Test Property</h1>
|
|
<p>Villa in Dubai Marina</p>
|
|
</div>
|
|
<div class="property-grid">
|
|
<div class="property-card">
|
|
<div class="property-card-label">Price</div>
|
|
<div class="property-card-value">AED 2,500,000</div>
|
|
</div>
|
|
<div class="property-card">
|
|
<div class="property-card-label">Bedrooms</div>
|
|
<div class="property-card-value">3</div>
|
|
</div>
|
|
</div>
|
|
"""
|
|
|
|
test_data = {
|
|
'propertyName': 'Test Property',
|
|
'propertyType': 'Villa',
|
|
'location': 'Dubai Marina'
|
|
}
|
|
|
|
complete_html = create_complete_html_document(test_html, test_data, 'test')
|
|
pdf_path = generate_pdf_from_html(complete_html, 'test_property.pdf')
|
|
|
|
if pdf_path and os.path.exists(pdf_path):
|
|
return send_file(
|
|
pdf_path,
|
|
as_attachment=True,
|
|
download_name='test_property.pdf',
|
|
mimetype='application/pdf'
|
|
)
|
|
else:
|
|
return jsonify({'error': 'Test PDF generation failed'}), 500
|
|
|
|
except Exception as e:
|
|
return jsonify({'error': f'Test failed: {str(e)}'}), 500
|
|
|
|
@app.route('/api/info', methods=['GET'])
|
|
def api_info():
|
|
"""Get API information"""
|
|
return jsonify({
|
|
'name': 'Salesforce PDF Generator API',
|
|
'version': '1.0.0',
|
|
'description': 'Takes HTML content from Salesforce and returns downloadable PDF',
|
|
'endpoints': {
|
|
'health': '/health',
|
|
'generate_pdf': '/generate-pdf',
|
|
'test_pdf': '/test-pdf',
|
|
'api_info': '/api/info'
|
|
},
|
|
'usage': {
|
|
'method': 'POST',
|
|
'endpoint': '/generate-pdf',
|
|
'content_type': 'application/json',
|
|
'payload': {
|
|
'html_content': 'HTML content from Salesforce',
|
|
'property_data': 'Property information object',
|
|
'template_name': 'Template name',
|
|
'filename': 'Output filename (optional)'
|
|
}
|
|
}
|
|
})
|
|
|
|
if __name__ == '__main__':
|
|
logger.info("Starting Salesforce PDF Generator API Server...")
|
|
logger.info("Available endpoints:")
|
|
logger.info(" GET /health - Health check")
|
|
logger.info(" POST /generate-pdf - Generate PDF from HTML")
|
|
logger.info(" GET /test-pdf - Test PDF generation")
|
|
logger.info(" GET /api/info - API information")
|
|
|
|
app.run(host='0.0.0.0', port=8000, debug=True) |