diff --git a/.gitignore b/.gitignore index 7aa51c74..207b9cae 100644 --- a/.gitignore +++ b/.gitignore @@ -102,6 +102,13 @@ uploads/ downloads/ cache/ +# LaTeX worktrees +backend/latex-qsm/ +backend/latex-vsm/ + +# Generated PDFs +backend/assets/*.pdf + # Certificates *.pem *.key diff --git a/.gitmodules b/.gitmodules new file mode 100644 index 00000000..8566e78a --- /dev/null +++ b/.gitmodules @@ -0,0 +1,3 @@ +[submodule "backend/latex-templates"] + path = backend/latex-templates + url = ssh://git@git.beimgraben.net:222/frederik/PA_Vorlage.git diff --git a/README.md b/README.md index dd6ca795..eeb9ff46 100644 --- a/README.md +++ b/README.md @@ -13,6 +13,7 @@ A comprehensive system for managing student parliament (STUPA) funding applicati - **API Documentation**: Auto-generated OpenAPI/Swagger documentation - **Rate Limiting**: Configurable rate limits for API protection - **Database UI**: Integrated Adminer for database management +- **LaTeX Integration**: Integrated LaTeX templates for QSM and VSM forms ## 📋 Prerequisites @@ -34,6 +35,9 @@ stupa-pdf-api/ │ │ ├── pdf_to_struct.py # PDF parsing │ │ └── ... │ ├── assets/ # PDF templates +│ ├── latex-templates/ # LaTeX source (git submodule) +│ ├── latex-qsm/ # QSM LaTeX worktree +│ ├── latex-vsm/ # VSM LaTeX worktree │ ├── requirements.txt # Python dependencies │ └── Dockerfile # Backend container definition ├── frontend/ # React frontend application @@ -129,6 +133,7 @@ Comprehensive documentation is available in the `/docs` directory: - [Quick Start Guide](./docs/QUICK_START.md) - [API Reference](./docs/API_REFERENCE.md) - [Development Guide](./docs/DEVELOPMENT.md) +- [PDF Integration](./docs/PDF_INTEGRATION.md) ## 🔧 Configuration diff --git a/backend/.dockerignore b/backend/.dockerignore new file mode 100644 index 00000000..ae526e21 --- /dev/null +++ b/backend/.dockerignore @@ -0,0 +1,103 @@ +# Python +__pycache__/ +*.py[cod] +*$py.class +*.so +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST +.pytest_cache/ +.coverage +.coverage.* +htmlcov/ +.tox/ +.nox/ +.hypothesis/ +.mypy_cache/ +.dmypy.json +dmypy.json +.pyre/ +.ruff_cache/ + +# Virtual environments +venv/ +ENV/ +env/ +.venv/ + +# IDE +.idea/ +.vscode/ +*.swp +*.swo +*~ +.DS_Store + +# Git worktrees (we need the main latex-templates repo, but not the worktrees) +# Note: latex-qsm and latex-vsm are needed for Docker build, so they're not ignored + +# LaTeX build artifacts +*.aux +*.log +*.out +*.fdb_latexmk +*.fls +*.synctex.gz +*.bbl +*.blg +*.idx +*.ilg +*.ind +*.toc +*.lot +*.lof +*.nav +*.snm +*.vrb + +# Documentation +*.md +docs/ +LICENSE + +# Development +dev.sh +*.log + +# Environment files +.env +.env.* + +# Temporary files +tmp/ +temp/ +*.tmp +*.bak +*.backup + +# Test data +tests/ +test_*.py +*_test.py + +# CI/CD +.github/ +.gitlab-ci.yml +.travis.yml + +# Assets folder (we'll generate PDFs in Docker, not copy existing ones) +assets/ diff --git a/backend/Dockerfile b/backend/Dockerfile index 4c25c47b..b64c81f7 100644 --- a/backend/Dockerfile +++ b/backend/Dockerfile @@ -1,10 +1,41 @@ -# ---------- Base ---------- +# ---------- LaTeX Builder Stage ---------- +FROM texlive/texlive:latest AS latex-builder + +# Install additional dependencies for LaTeX +RUN apt-get update && apt-get install -y --no-install-recommends \ + git \ + inkscape \ + make \ + fonts-liberation \ + fonts-dejavu-core \ + && rm -rf /var/lib/apt/lists/* \ + && inkscape --version + +WORKDIR /latex + +# Copy the LaTeX source files from local worktrees +COPY latex-qsm /latex/qsm +COPY latex-vsm /latex/vsm + +# Build QSM PDF +WORKDIR /latex/qsm +RUN latexmk -xelatex -interaction=nonstopmode -halt-on-error -shell-escape Main.tex && \ + cp Main.pdf /latex/qsm.pdf && \ + latexmk -c + +# Build VSM PDF +WORKDIR /latex/vsm +RUN latexmk -xelatex -interaction=nonstopmode -shell-escape Main.tex && \ + cp Main.pdf /latex/vsm.pdf && \ + latexmk -c + +# ---------- Base Python Stage ---------- FROM python:3.11-slim AS base ENV PYTHONDONTWRITEBYTECODE=1 \ PYTHONUNBUFFERED=1 -# System deps (optional: tzdata for correct time) +# System deps RUN apt-get update && apt-get install -y --no-install-recommends \ tzdata ca-certificates \ && rm -rf /var/lib/apt/lists/* @@ -16,31 +47,21 @@ COPY requirements.txt /app/requirements.txt RUN pip install --no-cache-dir -r /app/requirements.txt # ---------- App ---------- -# Struktur-Annahme: -# - src/ (alle .py Module, inkl. service_api.py, pdf_to_struct.py, pdf_filler.py, etc.) -# - assets/ (qsm.pdf, vsm.pdf) +# Copy application code COPY src/ /app/src/ -COPY assets/ /app/assets/ -# Falls deine Module relative Imports nutzen, src ins PYTHONPATH aufnehmen +# Copy pre-built PDFs from latex-builder stage +COPY --from=latex-builder /latex/qsm.pdf /app/assets/qsm.pdf +COPY --from=latex-builder /latex/vsm.pdf /app/assets/vsm.pdf + +# Set Python path ENV PYTHONPATH=/app/src -# pdf_filler.py sucht standardmäßig assets relativ zum Modul. -# Wir überschreiben die Template-Pfade per ENV, da die PDFs im Build-Root unter /app/assets liegen. +# Configure PDF template paths ENV QSM_TEMPLATE=/app/assets/qsm.pdf \ VSM_TEMPLATE=/app/assets/vsm.pdf -# Optional: Master-Key / DB-Config kommen zur Laufzeit per -e oder .env (docker run --env-file) -# ENV MASTER_KEY=change_me \ -# MYSQL_HOST=mysql \ -# MYSQL_PORT=3306 \ -# MYSQL_DB=stupa \ -# MYSQL_USER=stupa \ -# MYSQL_PASSWORD=secret - EXPOSE 8000 # ---------- Run ---------- -# Starte die FastAPI -# Hinweis: service_api.py muss in src/ liegen und die App als "app" exportieren. CMD ["uvicorn", "service_api:app", "--host", "0.0.0.0", "--port", "8000"] diff --git a/backend/assets/qsm.pdf b/backend/assets/qsm.pdf deleted file mode 100644 index 59fe67c4..00000000 Binary files a/backend/assets/qsm.pdf and /dev/null differ diff --git a/backend/assets/vsm.pdf b/backend/assets/vsm.pdf deleted file mode 100644 index 02946ea4..00000000 Binary files a/backend/assets/vsm.pdf and /dev/null differ diff --git a/backend/latex-templates b/backend/latex-templates new file mode 160000 index 00000000..b14c230d --- /dev/null +++ b/backend/latex-templates @@ -0,0 +1 @@ +Subproject commit b14c230d5a7d550122abf53eca5de15924ab2085 diff --git a/backend/scripts/make_fields_readonly.py b/backend/scripts/make_fields_readonly.py new file mode 100644 index 00000000..5b907695 --- /dev/null +++ b/backend/scripts/make_fields_readonly.py @@ -0,0 +1,156 @@ +#!/usr/bin/env python3 +""" +Make all form fields in LaTeX documents readonly by adding readonly=true attribute. +This script carefully parses LaTeX commands to avoid breaking the syntax. +""" + +import re +import sys +from pathlib import Path + + +def add_readonly_to_textfield(content): + """Add readonly=true to CustomTextFieldDefault commands.""" + # Pattern to match CustomTextFieldDefault{...}{...}{...}{params} + pattern = r'(\\CustomTextFieldDefault\{[^}]*\}\{[^}]*\}\{[^}]*\}\{)([^}]*)\}' + + def replacer(match): + prefix = match.group(1) + params = match.group(2) + + # Check if readonly is already present + if 'readonly=' in params: + return match.group(0) + + # Add readonly=true to the parameters + if params.strip(): + new_params = params + ',readonly=true' + else: + new_params = 'readonly=true' + + return prefix + new_params + '}' + + return re.sub(pattern, replacer, content) + + +def add_readonly_to_choicemenu(content): + """Add readonly=true to CustomChoiceMenuDefault commands.""" + # Pattern to match CustomChoiceMenuDefault{...}{...}{params}{...} + pattern = r'(\\CustomChoiceMenuDefault\{[^}]*\}\{[^}]*\}\{)([^}]*)\}(\{[^}]*\})' + + def replacer(match): + prefix = match.group(1) + params = match.group(2) + suffix = match.group(3) + + # Check if readonly is already present + if 'readonly=' in params: + return match.group(0) + + # Add readonly=true to the parameters + if params.strip(): + new_params = params + ',readonly=true' + else: + new_params = 'readonly=true' + + return prefix + new_params + '}' + suffix + + return re.sub(pattern, replacer, content) + + +def add_readonly_to_checkbox(content): + """Add readonly=true to CheckBox commands.""" + # Pattern to match CheckBox[params] + pattern = r'(\\CheckBox\[)([^\]]*)\]' + + def replacer(match): + prefix = match.group(1) + params = match.group(2) + + # Check if readonly is already present + if 'readonly=' in params: + return match.group(0) + + # Add readonly=true to the parameters + params_lines = params.split('\n') + + # Find a good place to insert readonly=true (after first parameter) + for i, line in enumerate(params_lines): + if line.strip() and not line.strip().startswith('%'): + # Insert after this line + params_lines.insert(i + 1, '\t\t\t\treadonly=true,') + break + + new_params = '\n'.join(params_lines) + return prefix + new_params + ']' + + return re.sub(pattern, replacer, content, flags=re.MULTILINE | re.DOTALL) + + +def add_readonly_to_textfield_multiline(content): + """Add readonly=true to TextField commands (multiline text fields).""" + # Pattern to match TextField[params] for multiline fields + pattern = r'(\\TextField\[)([^\]]*name=pa-project-description[^\]]*)\]' + + def replacer(match): + prefix = match.group(1) + params = match.group(2) + + # Check if readonly is already present + if 'readonly=' in params: + return match.group(0) + + # Add readonly=true to the parameters + params_lines = params.split('\n') + + # Find a good place to insert readonly=true (after multiline parameter) + for i, line in enumerate(params_lines): + if 'multiline' in line: + # Insert after this line + params_lines.insert(i + 1, '\t\t\t\treadonly=true,') + break + + new_params = '\n'.join(params_lines) + return prefix + new_params + ']' + + return re.sub(pattern, replacer, content, flags=re.MULTILINE | re.DOTALL) + + +def process_file(filepath): + """Process a single LaTeX file to make all fields readonly.""" + print(f"Processing {filepath}...") + + with open(filepath, 'r', encoding='utf-8') as f: + content = f.read() + + # Apply transformations + content = add_readonly_to_textfield(content) + content = add_readonly_to_choicemenu(content) + content = add_readonly_to_checkbox(content) + content = add_readonly_to_textfield_multiline(content) + + # Write back + with open(filepath, 'w', encoding='utf-8') as f: + f.write(content) + + print(f"✓ Processed {filepath}") + + +def main(): + """Main function to process QSM and VSM LaTeX files.""" + base_dir = Path(__file__).parent.parent + + files_to_process = [ + base_dir / "latex-qsm" / "Content" / "01_content.tex", + base_dir / "latex-vsm" / "Content" / "01_content.tex", + ] + + for filepath in files_to_process: + if filepath.exists(): + process_file(filepath) + else: + print(f"✗ File not found: {filepath}") + + +if __name__ == "__main__": + main() diff --git a/backend/src/pdf_field_mapping.py b/backend/src/pdf_field_mapping.py index 1365c5c1..d3e2fa70 100644 --- a/backend/src/pdf_field_mapping.py +++ b/backend/src/pdf_field_mapping.py @@ -22,12 +22,6 @@ TEXT_MAPPING_COMMON: dict = { }, # --- Applicant --- - 'pa-applicant-type': { - 'required': True, - 'target-key': 'pa.applicant.type', - 'type': 'enum', - 'values': [('person', 'Person'), ('institution', 'Institution')] - }, 'pa-institution-type': { 'required': True, 'target-key': 'pa.applicant.institution.type', @@ -88,8 +82,7 @@ TEXT_MAPPING_COMMON: dict = { 'pa-cost-{a;1:24}-name': {'required': True, 'target-key': 'pa.project.costs[{a}].name', 'type': str}, 'pa-cost-{a;1:24}-amount-euro': {'required': True, 'target-key': 'pa.project.costs[{a}].amountEur', 'type': float}, - # --- Attachments common --- - 'pa-anh-vergleichsangebote': {'required': False, 'target-key': 'pa.attachments.comparativeOffers', 'type': bool}, + # --- Misc --- 'warning-not-supported': {'required': False, 'target-key': 'warning.notSupported', 'type': str}, @@ -119,7 +112,7 @@ TEXT_MAPPING_QSM = { 'pa-qsm-individuell': {'required': False, 'target-key': 'pa.project.financing.qsm.flags.individuell', 'type': bool}, 'pa-qsm-exkursion-genehmigt': {'required': False, 'target-key': 'pa.project.financing.qsm.flags.exkursionGenehmigt', 'type': bool}, 'pa-qsm-exkursion-bezuschusst': {'required': False, 'target-key': 'pa.project.financing.qsm.flags.exkursionBezuschusst', 'type': bool}, - 'pa-anh-fakultaet': {'required': False, 'target-key': 'pa.attachments.fakultaet', 'type': bool}, + } # --- VSM-specific fields (first variant; include for completeness) --- diff --git a/backend/src/pdf_filler.py b/backend/src/pdf_filler.py index c1a22cde..bd3b796b 100644 --- a/backend/src/pdf_filler.py +++ b/backend/src/pdf_filler.py @@ -160,13 +160,11 @@ def fill_pdf(payload: Dict[str, Any], variant: str, out_path: Optional[str] = No # Calculate total amount from costs total_amount = 0.0 - costs = flat.get("pa.project.costs", []) - if isinstance(costs, list): - for cost in costs: - if isinstance(cost, dict) and "amountEur" in cost: - amount = cost.get("amountEur") - if amount is not None and isinstance(amount, (int, float)): - total_amount += float(amount) + # After flattening, costs are in format: pa.project.costs[0].amountEur, pa.project.costs[1].amountEur, etc. + for key, value in flat.items(): + if key.startswith("pa.project.costs[") and key.endswith("].amountEur"): + if value is not None and isinstance(value, (int, float)): + total_amount += float(value) # AcroForm übernehmen + NeedAppearances try: diff --git a/docker-compose.watch.yml b/docker-compose.watch.yml index 30b66bfe..d2273fe2 100644 --- a/docker-compose.watch.yml +++ b/docker-compose.watch.yml @@ -73,9 +73,17 @@ services: - action: sync path: ./backend/assets target: /app/assets + - action: sync + path: ./backend/latex-qsm + target: /app/latex-qsm + - action: sync + path: ./backend/latex-vsm + target: /app/latex-vsm volumes: - ./backend/src:/app/src - ./backend/assets:/app/assets + - ./backend/latex-qsm:/app/latex-qsm + - ./backend/latex-vsm:/app/latex-vsm adminer: image: adminer:4 diff --git a/docs/PDF_INTEGRATION.md b/docs/PDF_INTEGRATION.md new file mode 100644 index 00000000..cbd7a72a --- /dev/null +++ b/docs/PDF_INTEGRATION.md @@ -0,0 +1,260 @@ +# PDF Integration Guide + +This document describes how the STUPA PDF API integrates with LaTeX templates to generate and process PDF forms. + +## Overview + +The STUPA PDF API system works with two types of funding application forms: +- **QSM** (Qualitätssicherungsmittel) - Quality assurance funding +- **VSM** (Verfasste Studierendenschaft Mittel) - Student body funding + +## LaTeX Templates + +### Repository Structure + +The LaTeX templates are maintained in a separate Git repository and integrated as a submodule: +- Repository: `git@git.beimgraben.net:frederik/PA_Vorlage.git` +- Location: `/backend/latex-templates/` + +### Branch Organization + +Different form types are maintained in separate branches: +- `v1.2/QSM` - QSM application forms +- `v1.2/VSM` - VSM application forms +- `v1.2/VGL` - VGL forms (deprecated, not used) + +### Working with Templates + +The project uses Git worktrees to access multiple template versions simultaneously: + +```bash +# Templates are checked out to: +/backend/latex-qsm/ # QSM templates (branch: v1.2/QSM) +/backend/latex-vsm/ # VSM templates (branch: v1.2/VSM) +``` + +## PDF Generation Workflow + +### 1. Template Compilation + +LaTeX templates are compiled to PDF using XeLaTeX: + +```bash +# Build PDFs from LaTeX sources +./scripts/build-pdfs.sh +``` + +This script: +- Sets up Git worktrees for QSM and VSM branches +- Compiles LaTeX to PDF using `latexmk -xelatex` +- Copies generated PDFs to `/backend/assets/` + +### 2. Form Field Mapping + +The system uses pre-compiled PDFs with form fields: +- `/backend/assets/qsm.pdf` - QSM form template +- `/backend/assets/vsm.pdf` - VSM form template + +These PDFs contain named form fields that correspond to the application data structure. + +### 3. PDF Processing Pipeline + +``` +User uploads PDF → Parse form data → Store in database → Generate filled PDF +``` + +## LaTeX Template Structure + +### Main Components + +``` +Main.tex # Main document file +Content/ +├── 01_content.tex # Form content and fields +└── 99_glossary.tex # Glossary definitions +HSRTReport/ # Custom document class +TeX/ +├── Preamble.tex # Package imports and settings +└── Settings/ # Configuration files +``` + +### Form Fields + +LaTeX form fields are defined using custom commands: + +```latex +\CustomTextFieldDefault{pa-project-name}{}{Projektname}{width=\linewidth} +\CustomChoiceMenuDefault{pa-course}{}{width=\linewidth,default=-}{-,INF,ESB,LS,TEC,TEX,NXT} +\CheckBox[name=pa-qsm-studierende,width=1em,height=1em]{} +``` + +Field naming convention: `pa-` prefix followed by the field identifier. + +## Docker Integration + +### Build Requirements + +The Docker image includes all necessary LaTeX packages: + +```dockerfile +RUN apt-get install -y \ + texlive-full \ + texlive-xetex \ + texlive-lang-german \ + texlive-fonts-extra \ + latexmk \ + git +``` + +### Environment Variables + +```env +# PDF template paths +QSM_TEMPLATE=/app/assets/qsm.pdf +VSM_TEMPLATE=/app/assets/vsm.pdf + +# LaTeX source paths +LATEX_QSM_PATH=/app/latex-qsm +LATEX_VSM_PATH=/app/latex-vsm +``` + +## Development Workflow + +### 1. Modifying Templates + +To modify PDF templates: + +1. Navigate to the appropriate worktree: + ```bash + cd backend/latex-qsm # or latex-vsm + ``` + +2. Edit the LaTeX files + +3. Build the PDF: + ```bash + ./scripts/build-pdfs.sh + ``` + +4. Test the new PDF with the application + +### 2. Adding New Form Fields + +1. Add field definition in LaTeX: + ```latex + \CustomTextFieldDefault{pa-new-field}{}{Field Label}{width=\linewidth} + ``` + +2. Update the field mapping in `pdf_field_mapping.py` + +3. Add corresponding database fields if needed + +4. Rebuild the PDF template + +### 3. Testing PDF Generation + +```bash +# Test PDF generation in Docker +docker compose exec api python -c " +from pdf_filler import fill_pdf +# Test code here +" +``` + +## Field Mapping Reference + +### Common Fields (Both QSM and VSM) + +| LaTeX Field | Database Field | Description | +|-------------|----------------|-------------| +| `pa-applicant-type` | `applicantType` | Person or Institution | +| `pa-institution` | `institution` | Institution name | +| `pa-first-name` | `firstName` | Applicant first name | +| `pa-last-name` | `lastName` | Applicant last name | +| `pa-email` | `email` | Contact email | +| `pa-phone` | `phone` | Phone number | +| `pa-project-name` | `name` | Project name | + +### QSM-Specific Fields + +| LaTeX Field | Database Field | Description | +|-------------|----------------|-------------| +| `pa-qsm-*` | Various | QSM-specific checkboxes | +| `pa-cost-*` | `costs[].name/amountEur` | Cost positions | + +### VSM-Specific Fields + +| LaTeX Field | Database Field | Description | +|-------------|----------------|-------------| +| `pa-vsm-*` | Various | VSM-specific fields | +| `pa-financing-*` | `financing.*` | Financing options | + +## Troubleshooting + +### Common Issues + +1. **PDF build fails with XeLaTeX error** + - Ensure all LaTeX dependencies are installed + - Check for syntax errors in .tex files + - Verify fonts are available + +2. **Form fields not filling** + - Check field names match between LaTeX and mapping + - Verify PDF has form fields (use PDF reader) + - Check data types match expected format + +3. **Git worktree errors** + - Remove existing worktrees: `git worktree prune` + - Re-run setup script + +### Debugging Commands + +```bash +# List form fields in PDF +docker compose exec api python -c " +import PyPDF2 +with open('/app/assets/qsm.pdf', 'rb') as f: + pdf = PyPDF2.PdfReader(f) + fields = pdf.get_form_text_fields() + for name, value in fields.items(): + print(f'{name}: {value}') +" + +# Check LaTeX compilation log +cd backend/latex-qsm +cat Main.log +``` + +## Best Practices + +1. **Version Control** + - Keep LaTeX templates in sync with main repo + - Tag releases when updating PDF templates + - Document field changes in commit messages + +2. **Testing** + - Test both empty and filled PDFs + - Verify all form fields are accessible + - Check PDF compatibility across readers + +3. **Performance** + - Pre-compile PDFs rather than generating on demand + - Cache compiled PDFs in Docker image + - Minimize LaTeX package dependencies + +## Future Enhancements + +1. **Dynamic PDF Generation** + - Generate PDFs on-demand from LaTeX + - Support custom form layouts + - Template versioning system + +2. **Field Validation** + - Implement LaTeX-side validation + - Sync validation rules with frontend + - Generate field documentation from LaTeX + +3. **Multi-language Support** + - Internationalize LaTeX templates + - Support multiple PDF languages + - Dynamic language selection \ No newline at end of file diff --git a/docs/README.md b/docs/README.md index e80a4c8a..970f7cc4 100644 --- a/docs/README.md +++ b/docs/README.md @@ -28,6 +28,7 @@ Welcome to the STUPA PDF API documentation. This directory contains comprehensiv - **[Attachment Feature](./ATTACHMENT_FEATURE.md)** - File attachment functionality - **[Comparison Offers Feature](./COMPARISON_OFFERS_FEATURE.md)** - Managing comparison offers - **[PDF Processing](./PDF_PROCESSING.md)** - PDF generation and parsing +- **[PDF Integration](./PDF_INTEGRATION.md)** - LaTeX templates and PDF form integration ### 🚢 Deployment diff --git a/frontend/src/pages/AdminDashboard.tsx b/frontend/src/pages/AdminDashboard.tsx index 7e251d17..92d844b1 100644 --- a/frontend/src/pages/AdminDashboard.tsx +++ b/frontend/src/pages/AdminDashboard.tsx @@ -136,7 +136,7 @@ const AdminDashboard: React.FC = () => { Admin Dashboard - Verwaltung aller Studienanträge + Verwaltung aller Projektanträge {/* Messages */} diff --git a/scripts/build-pdfs.sh b/scripts/build-pdfs.sh new file mode 100755 index 00000000..5fa12243 --- /dev/null +++ b/scripts/build-pdfs.sh @@ -0,0 +1,206 @@ +#!/bin/bash + +# STUPA PDF API - Build PDFs from LaTeX sources +# This script compiles the LaTeX templates into PDF forms + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Project directories +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +BACKEND_DIR="${PROJECT_ROOT}/backend" +ASSETS_DIR="${BACKEND_DIR}/assets" + +# LaTeX template directories +LATEX_TEMPLATES_DIR="${BACKEND_DIR}/latex-templates" +LATEX_QSM_DIR="${BACKEND_DIR}/latex-qsm" +LATEX_VSM_DIR="${BACKEND_DIR}/latex-vsm" + +# Function to check if required tools are installed +check_requirements() { + echo -e "${BLUE}Checking requirements...${NC}" + + local missing=() + + # Check for git + if ! command -v git &> /dev/null; then + missing+=("git") + fi + + # Check for xelatex + if ! command -v xelatex &> /dev/null; then + missing+=("xelatex (texlive-xetex)") + fi + + # Check for latexmk + if ! command -v latexmk &> /dev/null; then + missing+=("latexmk") + fi + + if [ ${#missing[@]} -ne 0 ]; then + echo -e "${RED}Missing required tools: ${missing[*]}${NC}" + echo "Please install them first:" + echo " sudo apt-get install git texlive-full texlive-xetex texlive-lang-german texlive-fonts-extra latexmk" + exit 1 + fi + + echo -e "${GREEN}All requirements satisfied!${NC}" +} + +# Function to setup git worktrees +setup_worktrees() { + echo -e "${BLUE}Setting up LaTeX template worktrees...${NC}" + + cd "${LATEX_TEMPLATES_DIR}" + + # Remove existing worktrees if they exist + if [ -d "${LATEX_QSM_DIR}" ]; then + echo -e "${YELLOW}Removing existing QSM worktree...${NC}" + git worktree remove --force "${LATEX_QSM_DIR}" 2>/dev/null || true + fi + + if [ -d "${LATEX_VSM_DIR}" ]; then + echo -e "${YELLOW}Removing existing VSM worktree...${NC}" + git worktree remove --force "${LATEX_VSM_DIR}" 2>/dev/null || true + fi + + # Create new worktrees + echo -e "${BLUE}Creating QSM worktree...${NC}" + git worktree add "${LATEX_QSM_DIR}" v1.2/QSM + + echo -e "${BLUE}Creating VSM worktree...${NC}" + git worktree add "${LATEX_VSM_DIR}" v1.2/VSM + + echo -e "${GREEN}Worktrees created successfully!${NC}" +} + +# Function to build a PDF from LaTeX +build_pdf() { + local template_name=$1 + local source_dir=$2 + local output_name=$3 + + echo -e "${BLUE}Building ${template_name} PDF...${NC}" + + # Change to source directory + cd "${source_dir}" + + # Clean previous builds + rm -f *.aux *.log *.out *.fdb_latexmk *.fls *.synctex.gz *.pdf + + # Build PDF using latexmk with xelatex + echo -e "${YELLOW}Running latexmk with xelatex...${NC}" + if latexmk -xelatex -interaction=nonstopmode -halt-on-error Main.tex; then + echo -e "${GREEN}Build successful!${NC}" + + # Copy to assets directory + if [ -f "Main.pdf" ]; then + cp "Main.pdf" "${ASSETS_DIR}/${output_name}" + echo -e "${GREEN}Copied to ${ASSETS_DIR}/${output_name}${NC}" + else + echo -e "${RED}Error: Main.pdf not found after build${NC}" + return 1 + fi + else + echo -e "${RED}Build failed for ${template_name}${NC}" + return 1 + fi + + # Clean up build artifacts + echo -e "${YELLOW}Cleaning up...${NC}" + rm -f *.aux *.log *.out *.fdb_latexmk *.fls *.synctex.gz +} + +# Function to verify PDFs +verify_pdfs() { + echo -e "${BLUE}Verifying generated PDFs...${NC}" + + local all_good=true + + # Check QSM PDF + if [ -f "${ASSETS_DIR}/qsm.pdf" ]; then + local size=$(stat -c%s "${ASSETS_DIR}/qsm.pdf" 2>/dev/null || stat -f%z "${ASSETS_DIR}/qsm.pdf" 2>/dev/null) + if [ "$size" -gt 10000 ]; then + echo -e "${GREEN}✓ qsm.pdf ($(($size / 1024)) KB)${NC}" + else + echo -e "${RED}✗ qsm.pdf is too small (${size} bytes)${NC}" + all_good=false + fi + else + echo -e "${RED}✗ qsm.pdf not found${NC}" + all_good=false + fi + + # Check VSM PDF + if [ -f "${ASSETS_DIR}/vsm.pdf" ]; then + local size=$(stat -c%s "${ASSETS_DIR}/vsm.pdf" 2>/dev/null || stat -f%z "${ASSETS_DIR}/vsm.pdf" 2>/dev/null) + if [ "$size" -gt 10000 ]; then + echo -e "${GREEN}✓ vsm.pdf ($(($size / 1024)) KB)${NC}" + else + echo -e "${RED}✗ vsm.pdf is too small (${size} bytes)${NC}" + all_good=false + fi + else + echo -e "${RED}✗ vsm.pdf not found${NC}" + all_good=false + fi + + if [ "$all_good" = true ]; then + echo -e "${GREEN}All PDFs verified successfully!${NC}" + return 0 + else + echo -e "${RED}PDF verification failed!${NC}" + return 1 + fi +} + +# Main execution +main() { + echo -e "${GREEN}STUPA PDF Builder${NC}" + echo "==================" + echo "" + + # Check requirements + check_requirements + + # Create assets directory if it doesn't exist + mkdir -p "${ASSETS_DIR}" + + # Setup worktrees if needed + if [ ! -d "${LATEX_QSM_DIR}" ] || [ ! -d "${LATEX_VSM_DIR}" ]; then + setup_worktrees + fi + + # Build QSM PDF + if ! build_pdf "QSM" "${LATEX_QSM_DIR}" "qsm.pdf"; then + echo -e "${RED}Failed to build QSM PDF${NC}" + exit 1 + fi + + # Build VSM PDF + if ! build_pdf "VSM" "${LATEX_VSM_DIR}" "vsm.pdf"; then + echo -e "${RED}Failed to build VSM PDF${NC}" + exit 1 + fi + + # Verify results + echo "" + verify_pdfs + + echo "" + echo -e "${GREEN}PDF build complete!${NC}" + echo "" + echo "Generated PDFs:" + echo " - ${ASSETS_DIR}/qsm.pdf" + echo " - ${ASSETS_DIR}/vsm.pdf" +} + +# Run main function +main "$@" diff --git a/scripts/setup-latex-worktrees.sh b/scripts/setup-latex-worktrees.sh new file mode 100755 index 00000000..fc9361d1 --- /dev/null +++ b/scripts/setup-latex-worktrees.sh @@ -0,0 +1,91 @@ +#!/bin/bash + +# STUPA PDF API - Setup LaTeX Worktrees +# This script sets up the Git worktrees needed for building PDFs + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +BLUE='\033[0;34m' +NC='\033[0m' # No Color + +# Project directories +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(cd "${SCRIPT_DIR}/.." && pwd)" +BACKEND_DIR="${PROJECT_ROOT}/backend" + +# LaTeX directories +LATEX_TEMPLATES_DIR="${BACKEND_DIR}/latex-templates" +LATEX_QSM_DIR="${BACKEND_DIR}/latex-qsm" +LATEX_VSM_DIR="${BACKEND_DIR}/latex-vsm" + +echo -e "${GREEN}STUPA PDF API - LaTeX Worktree Setup${NC}" +echo "=====================================" +echo "" + +# Check if latex-templates submodule is initialized +if [ ! -d "${LATEX_TEMPLATES_DIR}" ] || [ ! -f "${LATEX_TEMPLATES_DIR}/.git" ]; then + echo -e "${YELLOW}Initializing latex-templates submodule...${NC}" + cd "${PROJECT_ROOT}" + git submodule update --init --recursive +fi + +# Navigate to latex-templates directory +cd "${LATEX_TEMPLATES_DIR}" + +# Fetch latest branches +echo -e "${BLUE}Fetching latest branches...${NC}" +git fetch origin + +# Remove existing worktrees if they exist +if [ -d "${LATEX_QSM_DIR}" ]; then + echo -e "${YELLOW}Removing existing QSM worktree...${NC}" + git worktree remove --force "${LATEX_QSM_DIR}" 2>/dev/null || rm -rf "${LATEX_QSM_DIR}" +fi + +if [ -d "${LATEX_VSM_DIR}" ]; then + echo -e "${YELLOW}Removing existing VSM worktree...${NC}" + git worktree remove --force "${LATEX_VSM_DIR}" 2>/dev/null || rm -rf "${LATEX_VSM_DIR}" +fi + +# Clean up any stale worktree entries +git worktree prune + +# Create QSM worktree +echo -e "${BLUE}Creating QSM worktree...${NC}" +git worktree add "${LATEX_QSM_DIR}" origin/v1.2/QSM + +# Create VSM worktree +echo -e "${BLUE}Creating VSM worktree...${NC}" +git worktree add "${LATEX_VSM_DIR}" origin/v1.2/VSM + +# Verify worktrees +echo "" +echo -e "${GREEN}Verifying worktrees...${NC}" + +if [ -f "${LATEX_QSM_DIR}/Main.tex" ]; then + echo -e "${GREEN}✓ QSM worktree created successfully${NC}" +else + echo -e "${RED}✗ QSM worktree creation failed${NC}" + exit 1 +fi + +if [ -f "${LATEX_VSM_DIR}/Main.tex" ]; then + echo -e "${GREEN}✓ VSM worktree created successfully${NC}" +else + echo -e "${RED}✗ VSM worktree creation failed${NC}" + exit 1 +fi + +echo "" +echo -e "${GREEN}LaTeX worktrees setup complete!${NC}" +echo "" +echo "Worktree locations:" +echo " QSM: ${LATEX_QSM_DIR}" +echo " VSM: ${LATEX_VSM_DIR}" +echo "" +echo "You can now build the Docker image with:" +echo " docker compose build api"