Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,13 @@ jobs:
cache: 'pip'
cache-dependency-path: '**/requirements*.txt'

- name: Install system dependencies (WeasyPrint)
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b \
libffi-dev libgdk-pixbuf-2.0-0 shared-mime-info

- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down Expand Up @@ -102,6 +109,13 @@ jobs:
cache: 'pip'
cache-dependency-path: '**/requirements*.txt'

- name: Install system dependencies (WeasyPrint)
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b \
libffi-dev libgdk-pixbuf-2.0-0 shared-mime-info

- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down Expand Up @@ -166,6 +180,13 @@ jobs:
cache: 'pip'
cache-dependency-path: '**/requirements*.txt'

- name: Install system dependencies (WeasyPrint)
run: |
sudo apt-get update -qq
sudo apt-get install -y --no-install-recommends \
libpango-1.0-0 libpangoft2-1.0-0 libharfbuzz0b \
libffi-dev libgdk-pixbuf-2.0-0 shared-mime-info

- name: Install dependencies
run: |
python -m pip install --upgrade pip
Expand Down
10 changes: 8 additions & 2 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,14 @@ WORKDIR /app
ENV PYTHONDONTWRITEBYTECODE=1 \
PYTHONUNBUFFERED=1

RUN apt-get update && apt-get install -y --no-install-recommends gcc && \
rm -rf /var/lib/apt/lists/*
RUN apt-get update && apt-get install -y --no-install-recommends \
libpango-1.0-0 \
libpangoft2-1.0-0 \
libharfbuzz0b \
libffi8 \
libgdk-pixbuf-2.0-0 \
shared-mime-info \
&& rm -rf /var/lib/apt/lists/*

COPY requirements.txt .
RUN pip install --no-cache-dir --upgrade pip && \
Expand Down
5 changes: 4 additions & 1 deletion app/api/dependencies.py
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,7 @@
)
from app.infrastructure.services.null_email_service import NullEmailService
from app.infrastructure.services.resend_email_service import ResendEmailService
from app.infrastructure.services.weasyprint_pdf_service import WeasyPrintPDFService
from app.shared.interfaces.email_service import IEmailService

# =====================================================================
Expand Down Expand Up @@ -601,4 +602,6 @@ async def get_get_complete_cv_use_case(
async def get_generate_cv_pdf_use_case(
get_cv_uc: GetCompleteCVUseCase = Depends(get_get_complete_cv_use_case),
) -> GenerateCVPDFUseCase:
return GenerateCVPDFUseCase(get_cv_use_case=get_cv_uc)
return GenerateCVPDFUseCase(
get_cv_use_case=get_cv_uc, pdf_service=WeasyPrintPDFService()
)
13 changes: 8 additions & 5 deletions app/api/v1/routers/cv_router.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from io import BytesIO

from fastapi import APIRouter, Depends, HTTPException
from fastapi.responses import FileResponse
from fastapi.responses import StreamingResponse

from app.api.dependencies import (
get_generate_cv_pdf_use_case,
Expand Down Expand Up @@ -29,15 +31,16 @@ async def get_complete_cv(
"/download",
summary="Descargar CV en PDF",
description="Genera y descarga el CV en formato PDF profesional",
response_class=FileResponse,
response_class=StreamingResponse,
)
async def download_cv_pdf(
use_case: GenerateCVPDFUseCase = Depends(get_generate_cv_pdf_use_case),
):
result = await use_case.execute(GenerateCVPDFRequest())
if not result.success:
raise HTTPException(status_code=500, detail=result.message)
raise HTTPException(
status_code=501,
detail=result.message,
return StreamingResponse(
BytesIO(result.pdf_bytes),
media_type="application/pdf",
headers={"Content-Disposition": 'attachment; filename="Alex_Zapata_CV.pdf"'},
)
2 changes: 1 addition & 1 deletion app/application/dto/cv_dto.py
Original file line number Diff line number Diff line change
Expand Up @@ -95,5 +95,5 @@ class GenerateCVPDFResponse:
"""Response containing PDF generation result."""

success: bool
file_path: str
pdf_bytes: bytes
message: str = "PDF generated successfully"
3 changes: 3 additions & 0 deletions app/application/services/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .pdf_service import IPDFService

__all__ = ["IPDFService"]
8 changes: 8 additions & 0 deletions app/application/services/pdf_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from abc import ABC, abstractmethod

from app.application.dto.cv_dto import CompleteCVResponse


class IPDFService(ABC):
@abstractmethod
def generate_cv_pdf(self, cv_data: CompleteCVResponse) -> bytes: ...
77 changes: 8 additions & 69 deletions app/application/use_cases/cv/generate_cv_pdf.py
Original file line number Diff line number Diff line change
@@ -1,84 +1,23 @@
"""
Generate CV PDF Use Case.

Generates the CV as a PDF file (placeholder implementation).
"""
import asyncio

from app.application.dto import (
GenerateCVPDFRequest,
GenerateCVPDFResponse,
GetCompleteCVRequest,
)
from app.application.services.pdf_service import IPDFService
from app.shared.interfaces import IQueryUseCase

from .get_complete_cv import GetCompleteCVUseCase


class GenerateCVPDFUseCase(IQueryUseCase[GenerateCVPDFRequest, GenerateCVPDFResponse]):
"""
Use case for generating CV as PDF.

Note: This is a placeholder for future PDF generation functionality.
In a real implementation, this would:
1. Get complete CV data
2. Generate PDF using a template engine (e.g., ReportLab, WeasyPrint)
3. Save to file system or cloud storage
4. Return file path or URL

For now, it returns a success response with a placeholder path.

Business Rules:
- Profile must exist
- Multiple format options supported
- Photo inclusion is configurable

Dependencies:
- GetCompleteCVUseCase: To get CV data

Future Implementation:
- PDF template engine integration
- File storage service
- Multiple format support (standard, compact, detailed)
"""

def __init__(self, get_cv_use_case: GetCompleteCVUseCase):
"""
Initialize use case with dependencies.

Args:
get_cv_use_case: Use case to get complete CV data
"""
def __init__(self, get_cv_use_case: GetCompleteCVUseCase, pdf_service: IPDFService):
self.get_cv_use_case = get_cv_use_case
self.pdf_service = pdf_service

async def execute(self, request: GenerateCVPDFRequest) -> GenerateCVPDFResponse:
"""
Execute the use case.

Args:
request: Generate PDF request with format options

Returns:
GenerateCVPDFResponse with file path

Note:
This is a placeholder implementation.
Actual PDF generation should be implemented in infrastructure layer.
"""
# Get CV data
async def execute(self, _request: GenerateCVPDFRequest) -> GenerateCVPDFResponse:
cv_data = await self.get_cv_use_case.execute(GetCompleteCVRequest())

# TODO: Implement actual PDF generation
# This would involve:
# 1. Creating a PDF template
# 2. Populating it with cv_data
# 3. Saving to storage
# 4. Returning the path/URL

# For now, return a placeholder response
file_path = f"/tmp/cv_{cv_data.profile.id}_{request.format}.pdf"

return GenerateCVPDFResponse(
success=True,
file_path=file_path,
message=f"PDF generation not yet implemented. Would generate {request.format} format at {file_path}",
)
# WeasyPrint is synchronous; run in thread pool to avoid blocking the event loop
pdf_bytes = await asyncio.to_thread(self.pdf_service.generate_cv_pdf, cv_data)
return GenerateCVPDFResponse(success=True, pdf_bytes=pdf_bytes)
2 changes: 2 additions & 0 deletions app/infrastructure/services/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,9 @@
from .null_email_service import NullEmailService
from .resend_email_service import ResendEmailService
from .weasyprint_pdf_service import WeasyPrintPDFService

__all__ = [
"NullEmailService",
"ResendEmailService",
"WeasyPrintPDFService",
]
Loading
Loading