feat: FASE 7 - Polish & Testing (security, i18n, test suite, docs)

Security hardening: CORS lockdown, rate limiting middleware con sliding
window e eviction IP stale, security headers (CSP, HSTS, X-Frame-Options),
session cookie hardening, filename sanitization upload.

i18n completion: internazionalizzati barcode.js e csv-export.js con bridge
window.BARCODE_I18N/CSV_I18N, aggiornati .po IT/EN con 27 nuove stringhe.

Tablet UX: touch target 44px per dispositivi coarse pointer.

Test suite: 101 test totali (76 server + 25 client), copertura completa
di tutti i router API, autenticazione, ruoli, CRUD, SPC, file upload,
security integration. Infrastruttura SQLite async in-memory con fixtures.

Fix critici: MissingGreenlet in recipe_service (selectinload eager),
route ordering tasks.py, auth_service bcrypt diretto, Measurement.id
Integer per SQLite.

Documentazione: API.md (riferimento completo 40+ endpoint),
DEPLOYMENT.md (guida produzione con Docker/Nginx/SSL),
USER_GUIDE.md (manuale utente per ruolo).

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Adriano
2026-02-07 17:10:24 +01:00
parent 26e5b9343d
commit dd2ebf863a
46 changed files with 6322 additions and 90 deletions
+1 -1
View File
@@ -134,7 +134,7 @@
<span class="text-sm font-medium flex-1">{{ message }}</span>
<!-- Dismiss -->
<button @click="show = false" class="shrink-0 hover:opacity-70 transition-opacity">
<button @click="show = false" class="shrink-0 hover:opacity-70 transition-opacity touch-target">
<svg class="w-4 h-4" fill="none" stroke="currentColor" viewBox="0 0 24 24">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12"/>
</svg>
@@ -148,4 +148,12 @@
</div>
</div>
</div>
<script>
window.BARCODE_I18N = {
scanner_lib_not_loaded: "{{ _('Libreria scanner non caricata') }}",
no_camera_available: "{{ _('Nessuna fotocamera disponibile') }}",
camera_access_error: "{{ _('Impossibile accedere alla fotocamera') }}"
};
</script>
</div>
@@ -262,6 +262,39 @@
{% block extra_js %}
<script src="{{ url_for('static', filename='js/csv-export.js') }}"></script>
<script>
window.CSV_I18N = {
subtask_id: "{{ _('Subtask ID') }}",
subtask_name: "{{ _('Nome Sottotask') }}",
measured_value: "{{ _('Valore Misurato') }}",
unit: "{{ _('Unita') }}",
nominal_value: "{{ _('Valore Nominale') }}",
tolerance_plus: "{{ _('Tolleranza +') }}",
tolerance_minus: "{{ _('Tolleranza -') }}",
deviation: "{{ _('Scarto') }}",
result: "{{ _('Esito') }}",
lot_number: "{{ _('Numero Lotto') }}",
serial_number: "{{ _('Numero Seriale') }}",
input_method: "{{ _('Metodo Input') }}",
measurement_date: "{{ _('Data Misurazione') }}",
operator: "{{ _('Operatore') }}",
task_summary_title: "{{ _('RIEPILOGO ESECUZIONE TASK') }}",
task_id: "{{ _('Task ID') }}",
task_name: "{{ _('Nome Task') }}",
recipe: "{{ _('Ricetta') }}",
start_date: "{{ _('Data Inizio') }}",
end_date: "{{ _('Data Fine') }}",
status: "{{ _('Stato') }}",
lot: "{{ _('Lotto') }}",
serial: "{{ _('Seriale') }}",
statistics: "{{ _('STATISTICHE') }}",
total_measurements: "{{ _('Totale Misure') }}",
passed: "{{ _('Passate') }}",
failed: "{{ _('Fallite') }}",
pass_rate: "{{ _('Percentuale Successo') }}",
measurement_details: "{{ _('DETTAGLIO MISURE') }}"
};
</script>
<script>
// Pass data to csv-export.js
window.measurementData = {
recipeName: {{ recipe.name|tojson }},