From e4b29c0b2d46db5c17f2620e6b1b461161c700dd Mon Sep 17 00:00:00 2001 From: AdrianoDev Date: Sat, 25 Apr 2026 12:43:57 +0200 Subject: [PATCH] chore(docs): consolidate documentation, drop dead files MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Cleanup - Remove src/backend/Dockerfile.legacy and src/frontend/flask_app/Dockerfile.legacy (history is in git, build uses the new uv-based root Dockerfile / Dockerfile.frontend). - Remove src/frontend/flask_app/verify_i18n.py (had hardcoded paths pointing at the old client/ tree). Group docs/ - New docs/README.md indexes everything in one place. - New docs/architecture/STATO_PROGETTO.md: snapshot of what works in V2.0.0 (inherited V1.0.7 features, rev04 Phase 1 stations, worker scaling, src/ restructure, test status, stack, decisions). - New docs/architecture/ROADMAP.md: what's next — Phases 2-7 of the rev04 migration with status, open client decisions (D-0.1 through D-0.10), tech debt and time estimates for M1 / M2. - Move PIANO_IMPLEMENTAZIONE.md (90KB V1.0.0 plan) to docs/archive/2026-02-06-piano-implementazione-v1.md (historical). - Move Schema sviluppo SW TieFlow_rev04-2026.docx to docs/specs/ with ISO date filename so the customer spec is now tracked. - Move src/frontend/flask_app/I18N_SETUP.md to docs/I18N_SETUP.md and rewrite paths to the new src/frontend/flask_app/ tree. .dockerignore: simplified now that legacy Dockerfiles are gone; docs/ stays excluded from the build context. Co-Authored-By: Claude Opus 4.7 (1M context) --- .dockerignore | 8 +- .../frontend/flask_app => docs}/I18N_SETUP.md | 4 +- docs/README.md | 43 ++++++ docs/architecture/ROADMAP.md | 93 +++++++++++++ docs/architecture/STATO_PROGETTO.md | 122 ++++++++++++++++++ .../2026-02-06-piano-implementazione-v1.md | 0 .../2026-04-16-schema-sviluppo-rev04.docx | Bin src/backend/Dockerfile.legacy | 26 ---- src/frontend/flask_app/Dockerfile.legacy | 33 ----- src/frontend/flask_app/verify_i18n.py | 118 ----------------- 10 files changed, 262 insertions(+), 185 deletions(-) rename {src/frontend/flask_app => docs}/I18N_SETUP.md (99%) create mode 100644 docs/README.md create mode 100644 docs/architecture/ROADMAP.md create mode 100644 docs/architecture/STATO_PROGETTO.md rename PIANO_IMPLEMENTAZIONE.md => docs/archive/2026-02-06-piano-implementazione-v1.md (100%) rename Schema sviluppo SW TieFlow_rev04-2026.docx => docs/specs/2026-04-16-schema-sviluppo-rev04.docx (100%) delete mode 100644 src/backend/Dockerfile.legacy delete mode 100644 src/frontend/flask_app/Dockerfile.legacy delete mode 100644 src/frontend/flask_app/verify_i18n.py diff --git a/.dockerignore b/.dockerignore index 775bf7c..ce88020 100644 --- a/.dockerignore +++ b/.dockerignore @@ -28,14 +28,10 @@ htmlcov/ # Node node_modules/ -# Local-only notes (Markdown docs are still copied if a Dockerfile asks) -PIANO_IMPLEMENTAZIONE.md +# Local-only notes +docs/ *.docx -# Legacy Dockerfiles preserved in tree but not needed in builds -src/backend/Dockerfile.legacy -src/frontend/flask_app/Dockerfile.legacy - # Uploads are a runtime volume, never baked in. uploads/ diff --git a/src/frontend/flask_app/I18N_SETUP.md b/docs/I18N_SETUP.md similarity index 99% rename from src/frontend/flask_app/I18N_SETUP.md rename to docs/I18N_SETUP.md index 1c1b712..83e9a04 100644 --- a/src/frontend/flask_app/I18N_SETUP.md +++ b/docs/I18N_SETUP.md @@ -10,7 +10,7 @@ TieMeasureFlow supports complete internationalization (i18n) with: ## Directory Structure ``` -client/ +src/frontend/flask_app/ ├── translations/ # Flask-Babel translations │ ├── babel.cfg # Extraction config │ ├── it/LC_MESSAGES/ @@ -99,7 +99,7 @@ flash(_("Profilo aggiornato con successo")) After editing .po files: ```bash -cd client +cd src/frontend/flask_app python compile_translations.py ``` diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..ea9d68b --- /dev/null +++ b/docs/README.md @@ -0,0 +1,43 @@ +# Documentazione TieMeasureFlow + +Indice della documentazione del progetto. + +## Stato e direzione + +| Documento | Scopo | +|---|---| +| [`architecture/STATO_PROGETTO.md`](architecture/STATO_PROGETTO.md) | Cosa è fatto oggi (V2.0.0). Snapshot del sistema, componenti e capacità. | +| [`architecture/ROADMAP.md`](architecture/ROADMAP.md) | Cosa resta da fare. Fasi 2-7 della migrazione rev04 verso V1.1.0/M1 demo cliente. | + +## Riferimenti operativi + +| Documento | Scopo | +|---|---| +| [`API.md`](API.md) | Riferimento endpoint REST esposti dal backend FastAPI. | +| [`DEPLOYMENT.md`](DEPLOYMENT.md) | Guida deploy su VPS (Hostinger/Tielogic con Traefik + Let's Encrypt). | +| [`USER_GUIDE.md`](USER_GUIDE.md) | Manuale utente (operatore, maker, metrologist, admin). | +| [`I18N_SETUP.md`](I18N_SETUP.md) | Setup e workflow traduzioni (Flask-Babel + Alpine.js). | + +## Piani dettagliati TDD (rev04) + +| Documento | Scopo | +|---|---| +| [`superpowers/plans/2026-04-17-rev04-master-roadmap.md`](superpowers/plans/2026-04-17-rev04-master-roadmap.md) | Master plan rev04 V1.0.7 → V1.1.0, le 7 fasi e le decisioni aperte. | +| [`superpowers/plans/2026-04-17-rev04-phase1-stations.md`](superpowers/plans/2026-04-17-rev04-phase1-stations.md) | Piano TDD dettagliato Fase 1 (stazioni e identità per-tablet). **COMPLETATO.** | + +## Specifiche esterne (binari) + +| Documento | Scopo | +|---|---| +| [`specs/2026-04-16-schema-sviluppo-rev04.docx`](specs/2026-04-16-schema-sviluppo-rev04.docx) | Schema sviluppo SW TieFlow rev04-2026 fornito dal committente. | + +## Storico + +| Documento | Scopo | +|---|---| +| [`archive/2026-02-06-piano-implementazione-v1.md`](archive/2026-02-06-piano-implementazione-v1.md) | Piano implementazione V1.0.0 originale (riferimento storico). | + +## Ulteriori riferimenti nel repo + +- [`/CLAUDE.md`](../CLAUDE.md) — guidance per Claude Code (architettura, comandi, pattern critici). +- [`/README.md`](../README.md) — entry point del progetto. diff --git a/docs/architecture/ROADMAP.md b/docs/architecture/ROADMAP.md new file mode 100644 index 0000000..1f0f2db --- /dev/null +++ b/docs/architecture/ROADMAP.md @@ -0,0 +1,93 @@ +# Roadmap TieMeasureFlow — V2.0.0 → V1.1.0 (rev04 / M1 demo cliente) + +> Aggiornare ad ogni Fase chiusa. + +## Riferimenti + +- Master plan dettagliato: [`../superpowers/plans/2026-04-17-rev04-master-roadmap.md`](../superpowers/plans/2026-04-17-rev04-master-roadmap.md) +- Spec sorgente: [`../specs/2026-04-16-schema-sviluppo-rev04.docx`](../specs/2026-04-16-schema-sviluppo-rev04.docx) +- Stato corrente: [`STATO_PROGETTO.md`](STATO_PROGETTO.md) + +## Strategia: due milestone + +| Milestone | Scope | Obiettivo | +|---|---|---| +| **M1 — Demo cliente** | Fasi 1-5 + deploy "demo" | Sistema testabile end-to-end col cliente per raccogliere feedback | +| **M2 — Produzione** | Fasi 6-7 + correzioni post-feedback + GAIA live | Rollout su tablet/PC reali | + +## Stato Fasi (M1) + +| Fase | Scope | Stato | Branch / Commit | +|---|---|---|---| +| **1** | Stazioni + identità per-tablet | ✅ **COMPLETATA** | `V2.0.0` (merge `ea8e468` da `feature/rev04-phase1-stations`) | +| 2 | Ruolo Capoturno (Supervisor) + override token breve | ⏳ Da iniziare | — | +| 3 | Editor ricetta a blocchi (preparation + measurement) | ⏳ Da iniziare | — | +| 4 | Workflow operatore (retry/timer/autologout/avvio produzione) | ⏳ Da iniziare | — | +| 5 (M1) | `ImportOnlyGaiaClient` + UI import dati cliente reali | ⏳ Da iniziare | — | +| Deploy M1 | VPS demo (compose + Traefik + LE, no registry) | ⏳ Da iniziare | — | + +## Stato Fasi (M2) + +| Fase | Scope | Stato | +|---|---|---| +| 5 (M2) | GAIA reale (protocollo TBD, polling, comandi produzione) | ⏳ Bloccata da decisioni cliente (D-0.1, D-0.2) | +| 6 | Deploy B industriale (registry privato + Watchtower + STATION_ID per-tablet + CI release) | ⏳ Pianificata | +| 7 | Hardening, security review, E2E sito pilota, docs aggiornati, i18n delta | ⏳ Pianificata | + +## Decisioni aperte (bloccanti per M2 / future fasi) + +Da: master plan §0 "Precondizioni e Decisioni Aperte". Da risolvere col cliente prima della Fase 5/6. + +| ID | Decisione | Stato | Bloccante per | +|---|---|---|---| +| D-0.1 | Protocollo integrazione GAIA (REST / DB shared / OPC-UA / file) | **Aperta** | Fase 5 reale (M2) | +| D-0.2 | Credenziali e rete GAIA (VPN / firewall / whitelist IP) | **Aperta** | Fase 5 reale (M2) | +| D-0.3 | Target hardware "tablet" (Windows / Linux industriale / Android) | **Aperta** | Fase 6 (deploy B) | +| D-0.4 | Cicalino/luce avviso (audio HTML5 / hardware USB / entrambi) | **Rimandata a M2** | Fase 4 finale | +| D-0.5 | Parametri runtime modificabili vs versione immutabile | **Aperta** (raccomandato B: separare volatili) | Fase 3 | +| D-0.6 | Auth capoturno durante override (modale / PIN / RFID) | **Aperta** | Fase 2 | +| D-0.7 | Timeout auto-logout | **Risolta** | — | +| D-0.8 | Naming ruolo capoturno | **Proposta:** `Supervisor` | Fase 2 | +| D-0.9 | Tag versione immagine docker | **Proposta:** SemVer + `latest` | Fase 6 | +| D-0.10 | Registry esposto su Internet o solo VPN | **Proposta:** solo VPN cliente | Fase 6 | + +## Tech debt da chiudere + +| Item | Priorità | Note | +|---|---|---| +| 3 test backend pre-esistenti rotti (`test_recipes`, `test_tasks`) | Media | Investigare prima di Fase 3 (toccano recipe + task router). | +| 1 test client pre-esistente rotto (`test_save_measurement_proxy`) | Bassa | Probabilmente CSRF/payload. Risolvere con Fase 4. | +| `.env` rename a convenzione spec (SERVICE_NAME, SERVICE_DOMAIN, API_KEY) | Bassa | Rinviato (impatto deploy). | +| Header `X-API-Key` rename a `X-Api-Key` | Bassa | Vedere se M2 lo richiede. | +| Envelope risposta `{success,data,error}` | Bassa | Eventuale API v2 in M2. | +| `Dockerfile.frontend`: `pybabel compile` via `uv run` non testato in build reale | Alta | Verificare al primo `docker compose build`. | + +## Open per scelta utente prima della prossima sessione + +1. **Quale fase iniziare adesso?** Opzioni: Fase 2 (Supervisor — sblocca workflow), Fase 3 (block editor — independente), Fase 5 (import GAIA dati reali — sblocca demo). +2. **Revisione decisioni aperte col cliente** — D-0.1 / D-0.2 / D-0.3 / D-0.6 prima di pianificare Fase 5 e 6. +3. **Smoke test Docker** della nuova struttura V2.0.0 (`docker compose -f docker-compose.dev.yml up --build`) per validare i Dockerfile riscritti. +4. **Test di carico** (k6/locust) a 20 VU su `/measure/save-measurement` per validare la scalatura worker (capacità annunciata: 20-30 tablet contemporanei). + +## Stima tempi residui M1 (post-Fase 1) + +| Task | Stima full-time | +|---|---| +| Fase 2 — Supervisor + override | 1 settimana | +| Fase 3 — Block editor | 1.5 settimane | +| Fase 4 — Workflow operatore | 2 settimane | +| Fase 5 (M1) — Import-only GAIA | 1 settimana | +| Deploy M1 demo | 0.5 settimane | +| **Totale M1 residuo** | **~6 settimane** | + +## Stima tempi M2 (dopo feedback) + +| Task | Stima | +|---|---| +| Aggiustamenti post-feedback | variabile (1-2 sett.) | +| Fase 5 reale GAIA | 1-2 settimane | +| Fase 6 deploy B | 1 settimana | +| Fase 7 hardening | 1-2 settimane | +| **Totale M2** | **~4-7 settimane** | + +**Totale fino a produzione:** ~10-13 settimane full-time da oggi (2026-04-25), assumendo decisioni aperte risolte in tempo utile. diff --git a/docs/architecture/STATO_PROGETTO.md b/docs/architecture/STATO_PROGETTO.md new file mode 100644 index 0000000..cbbd495 --- /dev/null +++ b/docs/architecture/STATO_PROGETTO.md @@ -0,0 +1,122 @@ +# Stato Progetto TieMeasureFlow — V2.0.0 + +> Snapshot al 2026-04-25. Aggiornare ad ogni milestone. + +## Versione corrente + +**V2.0.0** (in sviluppo, branch `V2.0.0` come default su `git.tielogic.xyz`). + +Versione precedente di produzione: `V1.0.7`. + +## Sintesi esecutiva + +Il sistema base (V1.0.7) è completo e collaudato: ricette, task, misurazioni, SPC, report PDF, gestione utenti, dashboard metrologist. La V2.0.0 in corso aggiunge il primo blocco della migrazione **rev04** (stazioni per-tablet) e ristruttura l'intero monorepo secondo lo standard `python-project-spec-design.md` (uv + `src/backend/` + `src/frontend/flask_app/`). + +## Cosa funziona oggi (V2.0.0 — branch corrente) + +### Funzionalità ereditate da V1.0.7 +- Autenticazione username/password + API key per-utente, ruoli combinabili (Maker, MeasurementTec, Metrologist) + flag `is_admin`. +- Recipe versioning copy-on-write: una nuova versione si crea solo se la corrente ha già measurements; altrimenti update in-place. +- Editor ricette (Maker) con annotation editor Fabric.js (~1200 LOC, collaudato su tablet). +- Workflow operatore tablet: select_recipe → task_list → task_execute → task_complete, con barcode scanner e numpad touch (input USB calibro con burst detection). +- Calcolo pass/fail con limiti UTL/UWL/LWL/LTL. +- Dashboard SPC: capability (Cp/Cpk/Pp/Ppk), control chart (UCL/LCL = mean ± 3σ), istogramma con curva normale, calcoli puro stdlib (no numpy). +- Report PDF (WeasyPrint + Kaleido SVG). +- Setup page protetta da `SETUP_PASSWORD` per inizializzazione DB e seed. +- i18n IT/EN (Flask-Babel + Alpine.js JSON). +- Tema light/dark via `Alpine.store('theme')` + localStorage. + +### Aggiunte V2.0.0 (rev04 Fase 1 — Stazioni per-tablet) +- Tabelle `stations` + `station_recipe_assignments` (Alembic migration `002_add_stations.py`). +- Modelli ORM: `Station`, `StationRecipeAssignment` con vincolo unique `(station_id, recipe_id)`. +- Schemas Pydantic: `StationCreate/Update/Response`, `StationRecipeAssignmentCreate/Response`, `RecipeSummary`. +- Service `station_service` (CRUD + assegnazioni + cascade delete). +- Router `/api/stations` con CRUD admin + endpoint operatore `GET /api/stations/by-code/{code}/recipes`. +- Seed automatico `ST-DEFAULT` con tutte le ricette esistenti (idempotente). +- Variabile env client `STATION_CODE` letta da `Config`, helper `APIClient.get_station_recipes()`. +- Filtro `select_recipe`: il client mostra solo le ricette assegnate alla propria stazione, errore se `STATION_CODE` non configurato. +- **GUI admin completa** in `/admin/stations`: tabella con search, modal create/edit, modal gestione assegnazioni ricette, conferma eliminazione, link in navbar (desktop + mobile). +- 47 nuovi test (32 server + 15 client) tutti pass. + +### Aggiunte V2.0.0 (performance + multi-utente) +- Gunicorn 5 workers × 4 thread (gthread) — capacità ~20 richieste concorrenti Flask, regge 20+ tablet. +- Uvicorn 4 workers + `--proxy-headers --forwarded-allow-ips='*'`. +- Rate limit middleware: identificazione IP reale via `X-Forwarded-For` → `X-Real-IP` → `request.client.host`. +- Rate limit general 100 → 300 req/min/IP (per-tablet ora, non più condiviso). +- Flask `ProxyFix(x_for=1, x_proto=1, x_host=1)` per IP reale dietro Nginx. +- `APIClient` propaga `X-Forwarded-For` + `X-Real-IP` (sia JSON che multipart). +- 12 test aggiuntivi (7 server + 5 client). + +### Aggiunte V2.0.0 (struttura monorepo) +- `pyproject.toml` unico con extra `server`/`client`/`dev`. Niente più `requirements.txt`. +- `uv.lock` (77 pacchetti) + `.python-version` (3.11) committati per build riproducibili. +- Layout `src/backend/` + `src/frontend/flask_app/` (vedi sotto). +- `Dockerfile` (root) + `Dockerfile.frontend` riscritti con `uv sync --frozen --no-dev --extra server|client`. +- `docker-compose.{dev,}.yml` con build context `.`. +- Alembic env.py aggiunge project root a `sys.path`; `script_location = %(here)s` resta valido. +- `.dockerignore` aggiornato. + +## Layout repository (V2.0.0) + +``` +TieMeasureFlow/ +├── pyproject.toml + uv.lock + .python-version +├── Dockerfile (backend) + Dockerfile.frontend +├── docker-compose.dev.yml + docker-compose.yml +├── nginx/ +├── uploads/ # volume Docker +├── docs/ # raggruppata e indicizzata +│ ├── README.md (indice) +│ ├── API.md / DEPLOYMENT.md / USER_GUIDE.md / I18N_SETUP.md +│ ├── architecture/ # questo file + ROADMAP.md +│ ├── archive/ # piani storici +│ ├── specs/ # spec esterne (.docx) +│ └── superpowers/plans/ # piani TDD dettagliati +└── src/ + ├── backend/ + │ ├── main.py / config.py / database.py + │ ├── api/{routers,middleware}/ + │ ├── models/{orm,api}/ + │ ├── services/ + │ ├── migrations/ + │ ├── templates/ + │ └── tests/ + └── frontend/ + └── flask_app/ + ├── app.py / config.py / compile_translations.py + ├── blueprints/ (auth, maker, measure, statistics, admin) + ├── services/ (api_client.py) + ├── templates/ + static/ + translations/ + └── tests/ +``` + +## Test status + +| Suite | Pass | Fail | Note | +|---|---|---|---| +| Backend (`src/backend/tests/`) | 127 | 3 | Fail pre-esistenti: `test_recipes` (2) + `test_tasks` (1). Nessuno introdotto dalla V2.0.0. | +| Frontend (`src/frontend/flask_app/tests/`) | 44 | 1 | Fail pre-esistente: `test_save_measurement_proxy`. | +| **Totale** | **171** | **4** | Tutti i fallimenti tracciati come tech debt da risolvere. | + +## Stack confermato + +- **Backend:** FastAPI + SQLAlchemy 2.0 async + MySQL 8 + Alembic + Pydantic v2 + WeasyPrint + Plotly/Kaleido. +- **Frontend:** Flask + Jinja2 + Alpine.js + TailwindCSS + Fabric.js 5.3.1 + html5-qrcode + Plotly.js + Flask-Babel. +- **Deploy:** Docker Compose. Dev = Nginx; Prod = Traefik + Let's Encrypt SSL. +- **Tooling:** uv (package mgmt), pytest + pytest-asyncio + httpx + aiosqlite (test). + +## Decisioni architetturali rilevanti + +| Decisione | Stato | Note | +|---|---|---| +| Frontend Flask invece di React (deroga vs spec §8) | **Confermata** | Tablet UX server-side, USB calipers/barcode, Fabric.js editor, i18n Babel collaudato. Vedi conversazione 2026-04-25. | +| NATS messaging (spec §7) | **Skippato** | Monorepo single-host, no microservizi. Nessuno stub `nats_client/` creato. | +| Envelope risposta `{success,data,error}` (spec §6) | **Rimandato** | Costo 4-5gg refactor + rotture client. Eventuale v2 API in M2. | +| Header `X-API-Key` vs spec `X-Api-Key` | **Mantenuto attuale** | Rinominare costa 50+ punti di codice + breaking per deploy. Rivedere in M2. | +| Variabili `.env` (DB_HOST, SERVER_PORT, ...) | **Mantenute attuali** | Rename a SERVICE_NAME/SERVICE_DOMAIN/API_KEY rinviato (impatta deploy esistenti). | + +## Branch git + +- **Default:** `V2.0.0` (lavoro corrente) +- **Mantenuti:** `V1.0.0` … `V1.0.7` (release branches storiche) +- **Mergiato e chiuso:** `feature/rev04-phase1-stations` (in `V2.0.0` con commit `ea8e468`) diff --git a/PIANO_IMPLEMENTAZIONE.md b/docs/archive/2026-02-06-piano-implementazione-v1.md similarity index 100% rename from PIANO_IMPLEMENTAZIONE.md rename to docs/archive/2026-02-06-piano-implementazione-v1.md diff --git a/Schema sviluppo SW TieFlow_rev04-2026.docx b/docs/specs/2026-04-16-schema-sviluppo-rev04.docx similarity index 100% rename from Schema sviluppo SW TieFlow_rev04-2026.docx rename to docs/specs/2026-04-16-schema-sviluppo-rev04.docx diff --git a/src/backend/Dockerfile.legacy b/src/backend/Dockerfile.legacy deleted file mode 100644 index ba699d0..0000000 --- a/src/backend/Dockerfile.legacy +++ /dev/null @@ -1,26 +0,0 @@ -FROM python:3.11-slim - -# Installa dipendenze sistema per WeasyPrint -RUN apt-get update && apt-get install -y --no-install-recommends \ - libpango-1.0-0 \ - libpangocairo-1.0-0 \ - libcairo2 \ - libgdk-pixbuf-2.0-0 \ - libffi-dev \ - shared-mime-info \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /app - -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt - -COPY . . - -# Crea directory uploads -RUN mkdir -p uploads/images uploads/pdfs uploads/logos uploads/reports - -EXPOSE 8000 - -# Entry point: Alembic upgrade + Uvicorn -CMD ["sh", "-c", "alembic -c migrations/alembic.ini upgrade head && uvicorn main:app --host 0.0.0.0 --port 8000 --workers 4 --proxy-headers --forwarded-allow-ips='*'"] diff --git a/src/frontend/flask_app/Dockerfile.legacy b/src/frontend/flask_app/Dockerfile.legacy deleted file mode 100644 index 7d93e3f..0000000 --- a/src/frontend/flask_app/Dockerfile.legacy +++ /dev/null @@ -1,33 +0,0 @@ -FROM python:3.11-slim AS base - -# Installa Node.js per Tailwind CSS build -RUN apt-get update && apt-get install -y --no-install-recommends \ - curl \ - && curl -fsSL https://deb.nodesource.com/setup_20.x | bash - \ - && apt-get install -y --no-install-recommends nodejs \ - && rm -rf /var/lib/apt/lists/* - -WORKDIR /app - -COPY requirements.txt . -RUN pip install --no-cache-dir -r requirements.txt gunicorn - -COPY . . - -# Install and build Tailwind CSS -RUN npm install tailwindcss@3 && npx tailwindcss -i static/css/input.css -o static/css/tailwind.css --minify - -# Compile Flask-Babel translations -RUN pybabel compile -d translations - -EXPOSE 5000 - -CMD ["gunicorn", \ - "--workers", "5", \ - "--threads", "4", \ - "--worker-class", "gthread", \ - "--timeout", "60", \ - "--bind", "0.0.0.0:5000", \ - "--access-logfile", "-", \ - "--forwarded-allow-ips", "*", \ - "app:create_app()"] diff --git a/src/frontend/flask_app/verify_i18n.py b/src/frontend/flask_app/verify_i18n.py deleted file mode 100644 index 9fb642e..0000000 --- a/src/frontend/flask_app/verify_i18n.py +++ /dev/null @@ -1,118 +0,0 @@ -#!/usr/bin/env python -"""Verify i18n setup for TieMeasureFlow.""" -import json -from pathlib import Path - -def check_file(path: Path, description: str) -> bool: - """Check if a file exists and report.""" - exists = path.exists() - status = "[OK]" if exists else "[FAIL]" - print(f"{status} {description}: {path}") - return exists - -def check_json_valid(path: Path) -> bool: - """Check if JSON file is valid.""" - try: - with open(path, 'r', encoding='utf-8') as f: - json.load(f) - return True - except Exception as e: - print(f" ERROR: {e}") - return False - -def main(): - """Verify i18n setup.""" - print("=== TieMeasureFlow i18n Verification ===\n") - - root = Path(__file__).parent - all_good = True - - # Check Flask-Babel files - print("Flask-Babel (Server-side):") - files = [ - (root / "translations/babel.cfg", "Babel config"), - (root / "translations/it/LC_MESSAGES/messages.po", "Italian .po"), - (root / "translations/it/LC_MESSAGES/messages.mo", "Italian .mo"), - (root / "translations/en/LC_MESSAGES/messages.po", "English .po"), - (root / "translations/en/LC_MESSAGES/messages.mo", "English .mo"), - ] - - for path, desc in files: - if not check_file(path, desc): - all_good = False - - # Count messages in .po files - if (root / "translations/it/LC_MESSAGES/messages.po").exists(): - with open(root / "translations/it/LC_MESSAGES/messages.po", 'r', encoding='utf-8') as f: - content = f.read() - msgid_count = content.count('msgid "') - 1 # Exclude header - print(f" Italian: {msgid_count} messages") - - if (root / "translations/en/LC_MESSAGES/messages.po").exists(): - with open(root / "translations/en/LC_MESSAGES/messages.po", 'r', encoding='utf-8') as f: - content = f.read() - msgid_count = content.count('msgid "') - 1 - print(f" English: {msgid_count} messages") - - # Check Alpine.js i18n files - print("\nAlpine.js i18n (Client-side):") - json_files = [ - (root / "static/js/locales/it.json", "Italian locale"), - (root / "static/js/locales/en.json", "English locale"), - ] - - for path, desc in json_files: - if check_file(path, desc): - if not check_json_valid(path): - all_good = False - else: - with open(path, 'r', encoding='utf-8') as f: - data = json.load(f) - keys = count_keys(data) - print(f" {keys} total keys") - - # Check app.py integration - print("\nApp Integration:") - app_py = root / "app.py" - if check_file(app_py, "app.py"): - with open(app_py, 'r', encoding='utf-8') as f: - content = f.read() - checks = [ - ("from flask_babel import Babel", "Flask-Babel imported"), - ("def get_locale()", "get_locale() function"), - ("Babel(app, locale_selector=get_locale)", "Babel initialized"), - ("def set_language(lang)", "set_language endpoint"), - ] - for check_str, check_desc in checks: - found = check_str in content - status = "[OK]" if found else "[FAIL]" - print(f" {status} {check_desc}") - if not found: - all_good = False - - # Summary - print("\n" + "="*40) - if all_good: - print("[OK] i18n setup verified successfully!") - print("\nNext steps:") - print("1. Use _() in Python code and templates") - print("2. Use $t() in Alpine.js components") - print("3. Test language switching: /set-language/en or /set-language/it") - else: - print("[FAIL] Some checks failed - review errors above") - return 1 - - return 0 - -def count_keys(obj, depth=0): - """Recursively count keys in nested dict.""" - if not isinstance(obj, dict): - return 0 - count = len(obj) - for value in obj.values(): - if isinstance(value, dict): - count += count_keys(value, depth + 1) - return count - -if __name__ == '__main__': - exit(main())