mirror of
https://github.com/velocitatem/cvfs.git
synced 2026-05-31 08:43:37 +00:00
Prepare repository for public deployment
- Replace ReportLab PDF export with LibreOffice headless for proper DOCX formatting preservation - Add libreoffice-writer + fonts-liberation to backend Dockerfile - Proxy public CV PDFs through frontend (/cv/[slug]) instead of redirecting to MinIO storage directly - Fix docker-compose: route backend/worker to internal MinIO URL (http://cvfs-minio:9000), remove MinIO from public network, parameterize all domain/env vars - Add storage cleanup (MinIO artifact deletion) when a document is deleted - Add docker-compose.standalone.yml for self-deployment without Traefik/dokploy - Update .env.example with comprehensive self-deployment documentation https://claude.ai/code/session_017HGM9VPptZG52asT5pbL6Y
This commit is contained in:
@@ -1,79 +1,21 @@
|
||||
from __future__ import annotations
|
||||
|
||||
from io import BytesIO
|
||||
|
||||
from docx import Document
|
||||
from reportlab.lib import colors
|
||||
from reportlab.lib.enums import TA_CENTER, TA_LEFT
|
||||
from reportlab.lib.pagesizes import A4
|
||||
from reportlab.lib.styles import ParagraphStyle, getSampleStyleSheet
|
||||
from reportlab.lib.units import cm
|
||||
from reportlab.platypus import HRFlowable, Paragraph, SimpleDocTemplate, Spacer
|
||||
|
||||
from .parser import _detect_block_type
|
||||
|
||||
_STYLES = getSampleStyleSheet()
|
||||
|
||||
_STYLE_MAP: dict[str, ParagraphStyle] = {
|
||||
"heading": ParagraphStyle(
|
||||
"CVHeading", parent=_STYLES["Normal"],
|
||||
fontSize=15, leading=20, spaceBefore=10, spaceAfter=4,
|
||||
textColor=colors.HexColor("#111111"), fontName="Helvetica-Bold",
|
||||
),
|
||||
"meta": ParagraphStyle(
|
||||
"CVMeta", parent=_STYLES["Normal"],
|
||||
fontSize=9, leading=13, spaceAfter=2, textColor=colors.HexColor("#555555"),
|
||||
),
|
||||
"summary": ParagraphStyle(
|
||||
"CVSummary", parent=_STYLES["Normal"],
|
||||
fontSize=10, leading=14, spaceAfter=6, textColor=colors.HexColor("#333333"),
|
||||
),
|
||||
"bullet": ParagraphStyle(
|
||||
"CVBullet", parent=_STYLES["Normal"],
|
||||
fontSize=10, leading=14, spaceAfter=3, leftIndent=14,
|
||||
bulletIndent=0, textColor=colors.HexColor("#222222"),
|
||||
),
|
||||
"skills": ParagraphStyle(
|
||||
"CVSkills", parent=_STYLES["Normal"],
|
||||
fontSize=10, leading=14, spaceAfter=3, textColor=colors.HexColor("#222222"),
|
||||
),
|
||||
"text": ParagraphStyle(
|
||||
"CVText", parent=_STYLES["Normal"],
|
||||
fontSize=10, leading=14, spaceAfter=4, textColor=colors.HexColor("#222222"),
|
||||
),
|
||||
}
|
||||
import os
|
||||
import subprocess
|
||||
import tempfile
|
||||
|
||||
|
||||
def docx_bytes_to_pdf(docx_bytes: bytes) -> bytes:
|
||||
doc = Document(BytesIO(docx_bytes))
|
||||
buf = BytesIO()
|
||||
pdf = SimpleDocTemplate(
|
||||
buf, pagesize=A4,
|
||||
leftMargin=2.2 * cm, rightMargin=2.2 * cm,
|
||||
topMargin=2 * cm, bottomMargin=2 * cm,
|
||||
)
|
||||
story: list = []
|
||||
prev_type: str | None = None
|
||||
|
||||
for para in doc.paragraphs:
|
||||
text = para.text.strip()
|
||||
if not text:
|
||||
if prev_type and prev_type != "empty":
|
||||
story.append(Spacer(1, 4))
|
||||
prev_type = "empty"
|
||||
continue
|
||||
|
||||
block_type = _detect_block_type(getattr(para.style, "name", None), para)
|
||||
style = _STYLE_MAP.get(block_type, _STYLE_MAP["text"])
|
||||
|
||||
# draw separator line before new heading sections (except first)
|
||||
if block_type == "heading" and prev_type not in (None, "heading"):
|
||||
story.append(Spacer(1, 6))
|
||||
story.append(HRFlowable(width="100%", thickness=0.5, color=colors.HexColor("#cccccc")))
|
||||
|
||||
prefix = "\u2022\u00a0" if block_type == "bullet" else ""
|
||||
story.append(Paragraph(f"{prefix}{text}", style))
|
||||
prev_type = block_type
|
||||
|
||||
pdf.build(story)
|
||||
return buf.getvalue()
|
||||
with tempfile.TemporaryDirectory() as tmpdir:
|
||||
docx_path = os.path.join(tmpdir, "cv.docx")
|
||||
pdf_path = os.path.join(tmpdir, "cv.pdf")
|
||||
with open(docx_path, "wb") as f:
|
||||
f.write(docx_bytes)
|
||||
subprocess.run(
|
||||
["libreoffice", "--headless", "--convert-to", "pdf", "--outdir", tmpdir, docx_path],
|
||||
check=True,
|
||||
capture_output=True,
|
||||
timeout=60,
|
||||
)
|
||||
with open(pdf_path, "rb") as f:
|
||||
return f.read()
|
||||
|
||||
Reference in New Issue
Block a user