diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..10aa9d9 --- /dev/null +++ b/.env.example @@ -0,0 +1,5 @@ +TZ=Europe/Berlin +REPORT_RENDERER_PORT=8080 +GOTENBERG_URL=http://gotenberg:3000 +RENDER_TIMEOUT_SECONDS=60 +MAX_PAYLOAD_BYTES=2097152 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..428b102 --- /dev/null +++ b/.gitignore @@ -0,0 +1,7 @@ +.env +__pycache__/ +*.pyc +.pytest_cache/ +output/*.pdf +output/*.html +!output/.gitkeep diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..d8c1899 --- /dev/null +++ b/LICENSE @@ -0,0 +1,17 @@ +MIT License + +Copyright (c) 2026 + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. diff --git a/MANIFEST.txt b/MANIFEST.txt new file mode 100644 index 0000000..fb84bbe --- /dev/null +++ b/MANIFEST.txt @@ -0,0 +1,92 @@ +.env.example +.gitignore +LICENSE +Makefile +QUICKSTART.md +README.md +docker-compose.yml +docs/CUSTOMIZATION.md +docs/DATA_MODEL.md +docs/PRODUCTION_CHECKLIST.md +examples/curl-render.sh +examples/render-all-themes.sh +examples/sample-claude-input.json +examples/sample-claude-output.json +examples/sample-report.json +n8n/README.md +n8n/code-build-claude-request.js +n8n/code-build-render-payload.js +n8n/code-extract-claude-output.js +n8n/code-normalize-month.js +n8n/postgres-example-queries.sql +n8n/postgres-schema.sql +n8n/workflow-template.json +output/.gitkeep +output/sample-report-classic.html +output/sample-report-classic.pdf +output/sample-report-modern.html +output/sample-report-modern.pdf +output/theme-catalog/alpine-sage.html +output/theme-catalog/classic.html +output/theme-catalog/editorial-ink.html +output/theme-catalog/editorial-ivory.html +output/theme-catalog/editorial-rose.html +output/theme-catalog/executive-blue.html +output/theme-catalog/executive-burgundy.html +output/theme-catalog/executive-charcoal.html +output/theme-catalog/forest-lodge.html +output/theme-catalog/ledger-blue.html +output/theme-catalog/ledger-green.html +output/theme-catalog/ledger-sepia.html +output/theme-catalog/magazine-citrus.html +output/theme-catalog/magazine-cobalt.html +output/theme-catalog/magazine-plum.html +output/theme-catalog/midnight-gold.html +output/theme-catalog/modern.html +output/theme-catalog/nordic-light.html +output/theme-catalog/nordic-night.html +output/theme-catalog/ocean-breeze.html +output/theme-catalog/poster-mono.html +output/theme-catalog/poster-red.html +output/theme-catalog/poster-yellow.html +output/theme-catalog/soft-lavender.html +output/theme-catalog/split-teal.html +output/theme-catalog/split-terracotta.html +output/theme-catalog/split-violet.html +output/theme-catalog/sunset-coral.html +output/theme-catalog/theme-gallery.html +output/theme-catalog/theme-gallery.pdf +prompts/anthropic-request-template.json +prompts/system-prompt.txt +prompts/user-prompt-template.txt +pytest.ini +renderer/Dockerfile +renderer/app/__init__.py +renderer/app/formatting.py +renderer/app/main.py +renderer/app/renderer.py +renderer/app/schemas/claude-output-anthropic.schema.json +renderer/app/schemas/claude-output.schema.json +renderer/app/schemas/report-payload.schema.json +renderer/app/templates/_shared/footer.html.j2 +renderer/app/templates/_shared/macros.html.j2 +renderer/app/templates/classic/footer.html.j2 +renderer/app/templates/classic/report.html.j2 +renderer/app/templates/dashboard/report.html.j2 +renderer/app/templates/editorial/report.html.j2 +renderer/app/templates/ledger/report.html.j2 +renderer/app/templates/magazine/report.html.j2 +renderer/app/templates/minimal/report.html.j2 +renderer/app/templates/modern/footer.html.j2 +renderer/app/templates/modern/report.html.j2 +renderer/app/templates/poster/report.html.j2 +renderer/app/templates/sidebar/report.html.j2 +renderer/app/templates/split/report.html.j2 +renderer/app/theme_catalog.py +renderer/app/validation.py +renderer/requirements.txt +scripts/render-local.py +scripts/render-theme-gallery.py +tests/conftest.py +tests/test_render.py +tests/test_validation.py diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..64c4c14 --- /dev/null +++ b/Makefile @@ -0,0 +1,27 @@ +.PHONY: up down logs test sample preview zip + +up: + docker compose up -d --build + +down: + docker compose down + +logs: + docker compose logs -f report-renderer gotenberg + +test: + python -m pytest -q + +sample: + mkdir -p output + curl -sS -X POST http://localhost:8080/render \ + -H 'Content-Type: application/json' \ + --data-binary @examples/sample-report.json \ + -o output/sample-report.pdf + +preview: + mkdir -p output + curl -sS -X POST http://localhost:8080/preview/html \ + -H 'Content-Type: application/json' \ + --data-binary @examples/sample-report.json \ + -o output/sample-report.html diff --git a/QUICKSTART.md b/QUICKSTART.md new file mode 100644 index 0000000..8d0211c --- /dev/null +++ b/QUICKSTART.md @@ -0,0 +1,52 @@ +# Schnellstart in 10 Schritten + +1. Ordner auf deinen Docker-Host kopieren. +2. `.env.example` nach `.env` kopieren. +3. `docker compose up -d --build` starten. +4. `http://HOST:8080/health` pruefen. +5. `examples/sample-report.json` anpassen. +6. Mit `/preview/html` zuerst das HTML kontrollieren. +7. Mit `/render` das PDF erzeugen. +8. `n8n/workflow-template.json` in n8n importieren. +9. Anthropic-API-Key in n8n als Credential oder Secret hinterlegen. +10. Beispiel-Set-Node durch deine PostgreSQL-Abfragen ersetzen. + +## Minimaler Render-Aufruf + +```bash +curl -sS \ + -X POST http://localhost:8080/render \ + -H 'Content-Type: application/json' \ + --data-binary @examples/sample-report.json \ + -o output/report.pdf +``` + +## Dienste + +- Report Renderer: `http://localhost:8080` +- Gotenberg: standardmaessig nur im internen Docker-Netz erreichbar + +## API-Routen + +- `GET /health` +- `GET /themes` +- `POST /validate` +- `POST /preview/html` +- `POST /render` + + +## Designs ansehen + +Alle IDs abfragen: + +```bash +curl -s http://localhost:8080/themes | python3 -m json.tool +``` + +Im Beispiel-Payload `design.theme` ändern oder alle Designs rendern: + +```bash +bash examples/render-all-themes.sh +``` + +Der mitgelieferte visuelle Katalog liegt unter `output/theme-catalog/theme-gallery.pdf`. diff --git a/README.md b/README.md index e69de29..132e24b 100644 --- a/README.md +++ b/README.md @@ -0,0 +1,124 @@ +# Monthly Report Kit + +Ein reproduzierbares Starterpaket fuer monatliche Kundenreports mit: + +- Claude Structured Outputs fuer valide, schemakonforme Inhalte +- fester HTML/CSS-Vorlage statt KI-generiertem Layout +- 28 austauschbaren Designs in 10 Layoutfamilien +- Python/FastAPI-Render-Service +- Gotenberg fuer die PDF-Erzeugung mit Chromium +- JSON-Schema-Validierung vor jedem Rendern +- n8n-Bausteinen und einem importierbaren Beispielworkflow +- Beispielreport, Prompt-Vorlagen und PostgreSQL-Grundstruktur + +## Architektur + +```text +PostgreSQL / andere Datenquelle + | + v +n8n normalisiert Monatsdaten + | + v +Claude Messages API + output_config.format + | + v +validiertes Claude-JSON + | + v +Report-Payload aus Kunde + Kennzahlen + Claude-Inhalt + | + v +report-renderer -> HTML/Jinja2 -> Gotenberg -> PDF +``` + +Claude entscheidet nur, **was** im Report steht. Das Template entscheidet, **wie** es aussieht. + +## Schnellstart + +```bash +cp .env.example .env +docker compose up -d --build +curl http://localhost:8080/health +``` + +Beispielreport erzeugen: + +```bash +curl \ + --request POST http://localhost:8080/render \ + --header 'Content-Type: application/json' \ + --data @examples/sample-report.json \ + --output output/monatsreport-beispiel.pdf +``` + +HTML-Vorschau im Browser: + +```bash +curl \ + --request POST http://localhost:8080/preview/html \ + --header 'Content-Type: application/json' \ + --data @examples/sample-report.json \ + --output output/preview.html +``` + +## Wichtigste Dateien + +| Datei | Zweck | +|---|---| +| `renderer/app/theme_catalog.py` | Katalog aller 28 Designs und Farbpaletten | +| `renderer/app/templates/` | 10 wiederverwendbare Layoutfamilien | +| `scripts/render-theme-gallery.py` | erzeugt Vorschauen und einen PDF-Designkatalog | +| `renderer/app/schemas/claude-output-anthropic.schema.json` | API-kompatibles Structured-Output-Schema | +| `renderer/app/schemas/claude-output.schema.json` | strenge Nachvalidierung mit Textlaengen und Grenzen | +| `renderer/app/schemas/report-payload.schema.json` | kompletter Render-Payload | +| `prompts/system-prompt.txt` | feste Claude-Rolle und Regeln | +| `prompts/user-prompt-template.txt` | dynamischer Auftrag je Kunde/Monat | +| `n8n/workflow-template.json` | importierbarer Workflow-Startpunkt | +| `n8n/postgres-schema.sql` | Tabellen fuer Konfiguration und Reportlaeufe | + +## Design auswählen + +Alle verfügbaren Designs liefert der Renderer über `GET /themes`. Im Payload genügt die Theme-ID: + +```json +{ + "design": { + "theme": "executive-blue", + "palette_mode": "theme" + } +} +``` + +`palette_mode: "theme"` verwendet die fest definierte Farbwelt des Designs. Mit `palette_mode: "custom"` bleiben Layout und Typografie erhalten, während die im Payload gelieferten Kundenfarben verwendet werden. Die flexiblen Varianten `modern` und `classic` verwenden standardmäßig Kundenfarben. + +### Enthaltene Layoutfamilien + +| Familie | Charakter | Designs | +|---|---|---| +| Modern | Karten, Verlaufskopf, weich | `modern`, `soft-lavender`, `midnight-gold` | +| Classic | Serifenschrift, feine Linien | `classic` | +| Minimal | viel Weißraum, ruhige Flächen | `nordic-light`, `nordic-night`, `alpine-sage` | +| Dashboard | Management-orientierte KPI-Flächen | `executive-blue`, `executive-charcoal`, `executive-burgundy` | +| Sidebar | markante Seitenleiste | `ocean-breeze`, `forest-lodge`, `sunset-coral` | +| Editorial | magazinartige Serifengestaltung | `editorial-ivory`, `editorial-ink`, `editorial-rose` | +| Magazine | große Typografie, asymmetrische Module | `magazine-cobalt`, `magazine-citrus`, `magazine-plum` | +| Ledger | dicht, tabellarisch, zahlenorientiert | `ledger-green`, `ledger-blue`, `ledger-sepia` | +| Split | geteilte Titel- und Inhaltsflächen | `split-teal`, `split-violet`, `split-terracotta` | +| Poster | plakativ, sehr große Kennzahlen | `poster-red`, `poster-yellow`, `poster-mono` | + +Den vollständigen visuellen Katalog findest du nach dem Entpacken unter `output/theme-catalog/theme-gallery.pdf`. Neu erzeugen kannst du ihn mit: + +```bash +python scripts/render-theme-gallery.py +``` + +## Produktionshinweise + +- API-Key niemals in Workflows exportieren oder im Repository speichern. +- `report_version`, `prompt_version` und `template_version` je Report sichern. +- Vor dem Versand immer Payload- und PDF-Pruefungen ausfuehren. +- Wiederholungen ueber einen eindeutigen Schluessel aus `customer_id + period` verhindern. +- Externe Bild-URLs vermeiden. Logos als Base64 Data URI uebergeben. + +Weitere Hinweise stehen unter `docs/`. diff --git a/SHA256SUMS.txt b/SHA256SUMS.txt new file mode 100644 index 0000000..0574cf8 --- /dev/null +++ b/SHA256SUMS.txt @@ -0,0 +1,92 @@ +3772de64cc98a7a239dd0ac1b671f5727c436eb97fdb6c04e4a026ac7c927d5e .env.example +cddeca481da53748d012f440fb4cc6346d79a4a435b088790df22a8fbeb5d14e .gitignore +ec4ab498897ee0d12aa7f8e354f3a7d4fd1baaf9db18dbf99927ffb78bed08ed LICENSE +734d0a6525a8303825860336d7199dd3a1f0c64ca6eef6f5216028b9c1de410d Makefile +f89b8af8904b9c2db9d798faa666e3dd680c1b1bb07dedde707a2db802780c15 QUICKSTART.md +8a173a2995256f0a31633d5bb7a389701b7ce7c0ed8f4b6bc75bb8ea77f33cec README.md +0234c23e00d70cd029f88c3d720326ec3fe0d14d4b857e36bb191836cece95d5 docker-compose.yml +339d856cdafc72a681fc14f5b21c525852ed7f9157e70464a21978328a8754a0 docs/CUSTOMIZATION.md +86bb53e3a4c40bf29feb11af0d4e160365b323010bdb846f473838afb66c5a4d docs/DATA_MODEL.md +9de8e978680517af375577c166af6fee0698a9e86ce0e37cd877dc2b91971b59 docs/PRODUCTION_CHECKLIST.md +901e0b2ffe44168bcdca34b2f37d257eafd183b50b66ab0fec38698a87014142 examples/curl-render.sh +ca7b0ddae3f0456def13ae40183ecf97e1d718e6fb80382ae2d8995e8260e00e examples/render-all-themes.sh +a188ee2512e5acc5015495e10701b412fb673dfb37885b580a4b2ece983f915f examples/sample-claude-input.json +c5db2c90552fc7499225c96a19f415cadeb40501c7834842c6e24ac8448467bd examples/sample-claude-output.json +9d83b926c4eab21bd7a9468bbde907a4a7075aa0a9f088fec3b587e4b76037c1 examples/sample-report.json +114c245d8200036e65e6e40530b747c643a09ce328e719b6713a0405ea75083f n8n/README.md +d95757c0b89bbb9257833f552f6fa28e5a69998421ce2ddfbf5eea1bdcf4b70a n8n/code-build-claude-request.js +19a764449c5040ac6cd1beee28a6a79073ea33063134b846a59a5bae23c44643 n8n/code-build-render-payload.js +2181b163b2a38bc280099a45fce6ade4729376f299e7b5b29ffbe62697471a7f n8n/code-extract-claude-output.js +e5a3dbef59ddb1b926a1fd282c3cf0f763d54429ed7f5f3265cb560544d663a4 n8n/code-normalize-month.js +c5f095a90926e7909d28623c8e2ae1e4dd2a755597b8f258de46e6040669168e n8n/postgres-example-queries.sql +1a69a0f745ea8b6b0f4aeaf841bfe30efd215dfab4d428dbf94451402a5ff54d n8n/postgres-schema.sql +80a655e305f2a9e6f33b73f7e1b6c468498727237260a49612b51ab137f1a2dc n8n/workflow-template.json +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 output/.gitkeep +67e0aa47dbab19484bd9668105f0c4452ec28295a23ca320b25f1e7067bb92b1 output/sample-report-classic.html +eda43c23832bd4ad9f0b8b6657235a13dd7fe3ccff6d25b8b62efd486ada1cdb output/sample-report-classic.pdf +3b51d683b8b13ba657d71067cb6b3ab5ba007aca4baa4d200bc654dc2e5da9af output/sample-report-modern.html +a4515612d9c153d77b423d6247501be2ecab64777b76ebb28ca9744b9ddfea06 output/sample-report-modern.pdf +182b07aa4da2759c1be79e82e72acefc068c89b1cdd4a79acec319ee76695a23 output/theme-catalog/alpine-sage.html +67e0aa47dbab19484bd9668105f0c4452ec28295a23ca320b25f1e7067bb92b1 output/theme-catalog/classic.html +cd65005a85bbe343a94773f8d97c6f12c75a313d7ea418858e3d4c892ecc744e output/theme-catalog/editorial-ink.html +e9e5e90889cf6b8075b3653d5cfc8632f17004ade3e4a3ec6dda2c0223ebb940 output/theme-catalog/editorial-ivory.html +9f6657791c563288c6a3e2bb0a154cbd356db0a9a33512343738956ae931272e output/theme-catalog/editorial-rose.html +9085f9d73c9afac53e336c424cde1f0fe8b1d7b062864db56eb9e45b5cdbdc46 output/theme-catalog/executive-blue.html +97227d9ed5d70ab9a24fe274e6718db808029cc12c5ffc09b451d8211a3c3031 output/theme-catalog/executive-burgundy.html +64607b1569dc17840cf8fc10a4eccd6409eb084193e46beb8556fbb8bc77fddc output/theme-catalog/executive-charcoal.html +2106baf980e86f5bdd0a520a073df40d35428a215e4efcc7f3f472adcdfa8594 output/theme-catalog/forest-lodge.html +3732f81d77b1cc80dba468f2cbda94779a613630ae74d84c65efcba7ec742c7c output/theme-catalog/ledger-blue.html +78783ddbc17c0e9866b25b6b93fa9010c488610115f4fa1a794108a7fb56e040 output/theme-catalog/ledger-green.html +514e8cf3e981ae304262866edae11cb69628ca870ba6ec7736969c9ca6f2ebe9 output/theme-catalog/ledger-sepia.html +f2b4413e8743a8e36fc5721df8fd3bad8cd4f500ecf60714d346086e34e2cde2 output/theme-catalog/magazine-citrus.html +889db73c6c649bb114fdd29055abc5e0a8fb30fe66e98fefb9608e4d25bdd823 output/theme-catalog/magazine-cobalt.html +2b7911ed20750b1939f96cc030243d95dd8ade9841c4cca3e059524f6e3b7d20 output/theme-catalog/magazine-plum.html +786f9f8ad6e27a27ab7ad0579d4ff5bd37900a653c53519f1aa5c7d9e7de83e6 output/theme-catalog/midnight-gold.html +3b51d683b8b13ba657d71067cb6b3ab5ba007aca4baa4d200bc654dc2e5da9af output/theme-catalog/modern.html +c12b3922f8f96afd221c1e9049fd5eaa7fcca6fdf608221630fe0deed6cfa07f output/theme-catalog/nordic-light.html +7ae6c3eae738abb26f7272e4171ef23dc3ded84e12938a2d35e1b9a5437a70b1 output/theme-catalog/nordic-night.html +52e63e72d056acbbb93f9c9954ffdf9406e18966955f9b3c76c54ef257e14995 output/theme-catalog/ocean-breeze.html +b0ccf9b7e7d492ce27a002b3da5b0aa7f6b056102997375e4492b54af4ba1f73 output/theme-catalog/poster-mono.html +11a3b7ebfc570371b0d2d59e065b0083630bfd5b180a679f045d22fc427fe47b output/theme-catalog/poster-red.html +d456da8ab54fc6b6e4c10d3fb5d10d964db40c819d2f3bb53816a1198315e0dc output/theme-catalog/poster-yellow.html +fe926c94c73f51b1aabf03bafef7a04acda08c6291ac19a70cfe5897ffb2fac8 output/theme-catalog/soft-lavender.html +b96d8a0c74e8f41e19085620366448bd4492ae49bd950268585a76f2a4a8846c output/theme-catalog/split-teal.html +c6dc77a1de12a41cf9540d8906debaf4512cc431c2797eacf524b1df15400924 output/theme-catalog/split-terracotta.html +2472963115164b2ae2c7fb38278e691065db845a95db8ad94be183aef66ae612 output/theme-catalog/split-violet.html +7aba082062da87bd78d08d8d984ee7ca755fe32917654e72d937ad4e9678a960 output/theme-catalog/sunset-coral.html +08d242bed264e0f31e1ab31ed79a37fb8a6bd18b91b63e79e05af175e97d4608 output/theme-catalog/theme-gallery.html +f39bb252bd52865c4551691a929d0cdf10d00eddd6a7f3ae0378f8bf2a8c5b55 output/theme-catalog/theme-gallery.pdf +23134c1e9992b5eb7c877b1c157d53ab0e36ac9c8ddcb2821b083f992b3dda39 prompts/anthropic-request-template.json +cd385809063a0badc73a37d7f87dc436886999785f8707c41bd57bd886fddc91 prompts/system-prompt.txt +0c6faf43172f0fa137d9c361dacceae4c5b972aef8623dcc14ea849bb1ccfb9b prompts/user-prompt-template.txt +c809c969ec7e11b9ff37784e9c6fcc0b7c5148d168e18b05aed797e03fbd6ead pytest.ini +6622f5ec0bc98804a17760ae13be6c9a9d1532eb36b69f72de01c749b565d454 renderer/Dockerfile +e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855 renderer/app/__init__.py +f46c52197e900e03b4a0b86d3b421534aa0387421c571ba743e3f0e041c2a0aa renderer/app/formatting.py +d515a0b7f4ea1976ba09903100409a3596f5f39e90aa496b4b7f2250a919d53f renderer/app/main.py +f18636cfa6ee4c5e509608cf047194eafb289b6798913595e4b43f3db7506178 renderer/app/renderer.py +8b7af092bdc0088832bd8f0e2fca15c10cb7c058ad4ad9d736ab7cbd677f97d6 renderer/app/schemas/claude-output-anthropic.schema.json +75b1bc32ae1e6d334279b385cf99712bf33cc78366e59c8f7013a39d11e334fc renderer/app/schemas/claude-output.schema.json +b1d9da86c2b0723f8c3bc3f03ffe89da15a099b1411b4e0a6c49a1e4be52b40a renderer/app/schemas/report-payload.schema.json +632d0dc09ca2bd9b98ac98aaf4540c14cd7c41c7cbad63358a56139303fce6e0 renderer/app/templates/_shared/footer.html.j2 +cff8e11e83cca48fe3a6fc59cf0d72be2349c3d7c1ad5b168cb1826d8975594c renderer/app/templates/_shared/macros.html.j2 +4c085ecc7a6418eb6d7fd673242dce76a65076b65c428baf6b1fff191354dd5a renderer/app/templates/classic/footer.html.j2 +140c3d2810b67d62729a57cc03aa45d651236f93d6e124c9caaca7267157a62b renderer/app/templates/classic/report.html.j2 +1690c913f1b92e45d40093338478ecff2b63e4a3efb92cf2e9e8da2af74c687e renderer/app/templates/dashboard/report.html.j2 +e43f73535b336a52da00f2e6c11699c4f304a819f0f738629cef65b23b6af8b0 renderer/app/templates/editorial/report.html.j2 +9f2827abc07b4888d2ffe9d5bb20062a242fcfcc0e50b183cb88bdd8532cbeb5 renderer/app/templates/ledger/report.html.j2 +b3acd510ba16424bb32baa2e08b1ac9de95d84ac912e39913e19348d02d81e13 renderer/app/templates/magazine/report.html.j2 +a89095e83da21a3c3f5db08d2abce769fcb6dbdb4f0bc89ae55e5aff0c740b86 renderer/app/templates/minimal/report.html.j2 +a63847d53e571ddb28fd5cf3593e0913e6273ad6c9a6d50c550f045e1fb38942 renderer/app/templates/modern/footer.html.j2 +cbb71a402abbd4ad4bcd95bfdfde6688a68d88c05168d6da51abbe72335e8446 renderer/app/templates/modern/report.html.j2 +dd1d096c8c34ac741d3d645ec2bc23c698c208376c61036bcaa2c9235dda7173 renderer/app/templates/poster/report.html.j2 +75a16ed98c86ba108e64d528c5292432c96101d4d466e27706c0ee4be216108e renderer/app/templates/sidebar/report.html.j2 +63c48859557e147be9ed8b929370d49de8d7c192d0d2ad0361075353e819b68a renderer/app/templates/split/report.html.j2 +30fbfcaf3973531384b8d0ed8df1b8868987a1be1d42d297953bf172db3d6e6d renderer/app/theme_catalog.py +1c8d516e13a21c16a306b35dedbc26929012f7afe03a8cb28899da09960b2d26 renderer/app/validation.py +35137cd8aeecd002116d489d98d0dc505909ec5d999d237387a094bd3e7f72f9 renderer/requirements.txt +e9ba2837656241375edd0f1027efe34c874d401efe87f3840a0a95e42a326ad0 scripts/render-local.py +0991e6bfdd450d1ce376b1cbf17170ba8c35342ac9bf320b30e858f0e45be49c scripts/render-theme-gallery.py +23b58558bad1c0c26a9095cb788f1b17bc8ba048c6db5fc9b24fa80b7097aa9b tests/conftest.py +811ecd465756c60292e65450434c6003ded577572315eee7f7809525ec253a49 tests/test_render.py +391a85063ce5ccafc11b23c24262292d05115c540d046a2dc177592233026852 tests/test_validation.py diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..239ff32 --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,38 @@ +services: + report-renderer: + build: + context: ./renderer + container_name: monthly-report-renderer + restart: unless-stopped + environment: + TZ: ${TZ:-Europe/Berlin} + GOTENBERG_URL: ${GOTENBERG_URL:-http://gotenberg:3000} + RENDER_TIMEOUT_SECONDS: ${RENDER_TIMEOUT_SECONDS:-60} + MAX_PAYLOAD_BYTES: ${MAX_PAYLOAD_BYTES:-2097152} + ports: + - "${REPORT_RENDERER_PORT:-8080}:8080" + depends_on: + gotenberg: + condition: service_healthy + networks: + - report-net + + gotenberg: + image: gotenberg/gotenberg:8 + container_name: monthly-report-gotenberg + restart: unless-stopped + command: + - gotenberg + - --chromium-disable-javascript=true + - --api-timeout=90s + healthcheck: + test: ["CMD", "curl", "--fail", "http://localhost:3000/health"] + interval: 10s + timeout: 5s + retries: 10 + networks: + - report-net + +networks: + report-net: + name: monthly-report-net diff --git a/pytest.ini b/pytest.ini new file mode 100644 index 0000000..5ee6477 --- /dev/null +++ b/pytest.ini @@ -0,0 +1,2 @@ +[pytest] +testpaths = tests