From b467d4753d789ccf3212678faf310470d63613a3 Mon Sep 17 00:00:00 2001 From: alfy Date: Sat, 17 Jan 2026 23:32:33 +0100 Subject: [PATCH] fix: correzioni critiche e checklist test manuali CORREZIONI: - Badge confrontato ESATTAMENTE come stringa (rimosso .lstrip("0")) - Success modal si chiude quando arriva nuovo badge (fix dipendenze useCallback) - Polling ogni 30s per invalidare sessione se server riparte - Area carosello allargata per testi lunghi (es. russo) DOCUMENTAZIONE: - API_SPECIFICATION.md aggiornata: badge come stringa esatta - Creata TEST_CHECKLIST.md con 22 test manuali - Aggiornati piani backend e frontend Badge sono STRINGHE, non numeri: - "0008988288" != "8988288" (zeri iniziali significativi) --- TEST_CHECKLIST.md | 158 ++++++++++ ai-prompts/00-welcome-agent.md | 11 +- ai-prompts/01-backend-plan.md | 16 +- ai-prompts/02-frontend-plan.md | 56 +++- backend-mock/API_SPECIFICATION.md | 325 ++++++++++++++++++++ backend-mock/api/routes.py | 5 +- backend-mock/data/users_default.json | 8 - frontend/src/App.tsx | 46 ++- frontend/src/components/WelcomeCarousel.tsx | 83 +++-- frontend/src/screens/ActiveGateScreen.tsx | 41 +-- frontend/src/screens/SuccessModal.tsx | 16 +- 11 files changed, 681 insertions(+), 84 deletions(-) create mode 100644 TEST_CHECKLIST.md create mode 100644 backend-mock/API_SPECIFICATION.md diff --git a/TEST_CHECKLIST.md b/TEST_CHECKLIST.md new file mode 100644 index 0000000..670b4d9 --- /dev/null +++ b/TEST_CHECKLIST.md @@ -0,0 +1,158 @@ +# ✅ Checklist Test Manuali - Focolari Voting System + +## Pre-requisiti +- [ ] Backend avviato con `./dev.sh server` +- [ ] Browser aperto su `http://localhost:8000` +- [ ] Lettore RFID collegato (o usa tastiera per simulare) + +--- + +## 🔐 Test Login Validatore + +### T1: Login con password corretta +1. Passa un badge qualsiasi (es. `ò0008988288_`) +2. Inserisci password: `focolari` +3. **Atteso:** Accesso al varco attivo ✅ + +### T2: Login con password errata +1. Passa un badge qualsiasi +2. Inserisci password sbagliata +3. **Atteso:** Messaggio errore rosso, rimane su schermata password ❌ + +### T3: Annulla login +1. Passa un badge +2. Clicca "Annulla" +3. **Atteso:** Torna a "Passa badge validatore" ✅ + +--- + +## 👤 Test Anagrafica Utenti + +### T4: Badge ammesso trovato +1. Login come validatore +2. Passa badge `ò0008988288_` (Marco Bianchi) +3. **Atteso:** Card verde con "Utente ammesso all'ingresso" ✅ + +### T5: Badge NON ammesso trovato +1. Passa badge `ò0000514162_` (Giuseppe Verdi) +2. **Atteso:** Card rossa lampeggiante "ACCESSO NON CONSENTITO" ⚠️ + +### T6: Badge non esistente nel DB +1. Passa badge `ò0006478281_` +2. **Atteso:** Schermata errore con badge in grassetto, countdown 30s ❌ + +### T7: Stesso badge passato due volte +1. Visualizza un utente (es. Marco Bianchi) +2. Passa lo STESSO badge di nuovo +3. **Atteso:** Nessun ricaricamento, utente rimane visualizzato ✅ + +### T8: Badge diverso sostituisce utente corrente +1. Visualizza Marco Bianchi (`0008988288`) +2. Passa Laura Rossi (`ò0007399575_`) +3. **Atteso:** Card cambia mostrando Laura Rossi ✅ + +--- + +## ✓ Test Conferma Ingresso + +### T9: Conferma utente ammesso +1. Visualizza Marco Bianchi (ammesso) +2. Passa badge VALIDATORE (quello usato per login) +3. **Atteso:** Modal verde con carosello "Benvenuto/Welcome/..." per 8 secondi ✅ + +### T10: Badge validatore su utente NON ammesso +1. Visualizza Giuseppe Verdi (non ammesso) +2. Passa badge validatore +3. **Atteso:** Banner arancione "Badge validatore rilevato, se cambiato fare logout" ⚠️ + +### T11: Badge validatore senza utente visualizzato +1. Torna alla schermata "In attesa partecipante" +2. Passa badge validatore +3. **Atteso:** Banner arancione come sopra ⚠️ + +--- + +## 🎠 Test Success Modal + +### T12: Carosello scorre tutte le lingue +1. Conferma ingresso di un utente ammesso +2. Osserva il carosello +3. **Atteso:** Scorre IT→EN→FR→DE→ES→PT→ZH→JA→AR→RU con animazione smooth ✅ + +### T13: Badge durante carosello chiude modal +1. Durante il carosello, passa un NUOVO badge +2. **Atteso:** Modal si chiude immediatamente, carica nuovo utente ✅ + +### T14: Carosello non troppo stretto +1. Osserva le scritte durante il carosello +2. **Atteso:** "Добро пожаловать!" (russo) deve essere visibile per intero ✅ + +--- + +## ⏱️ Test Timeout e Sessione + +### T15: Timeout utente (60s) +1. Visualizza un utente +2. Aspetta 60 secondi +3. **Atteso:** Torna automaticamente a "In attesa partecipante" ✅ + +### T16: Timeout badge non trovato (30s) +1. Passa badge inesistente (`0006478281`) +2. Aspetta 30 secondi +3. **Atteso:** Torna a "In attesa partecipante", barra countdown visiva ✅ + +### T17: Logout manuale +1. Clicca "Esci" nell'header +2. **Atteso:** Torna a "Passa badge validatore" ✅ + +### T18: Sessione invalidata al riavvio server +1. Login come validatore, vai al varco attivo +2. **Ferma il server** (Ctrl+C) +3. **Riavvia il server** (`./dev.sh server`) +4. Aspetta ~30 secondi (polling) +5. **Atteso:** Torna automaticamente a "Passa badge validatore" ✅ + +--- + +## 🔧 Test Debug + +### T19: Pagina debug accessibile +1. Vai a `http://localhost:8000/debug` +2. **Atteso:** Pagina con log tasti, stato scanner, buffer corrente ✅ + +### T20: Log tasti funzionante +1. Nella pagina debug, premi tasti sulla tastiera +2. **Atteso:** Tasti appaiono nella lista eventi ✅ + +--- + +## 📱 Test UI/UX + +### T21: NumLock banner su desktop +1. Su browser desktop, verifica schermata "In attesa partecipante" +2. **Atteso:** Banner giallo con stato NumLock visibile ✅ + +### T22: Occhio toggle password +1. Nella schermata password validatore +2. Clicca l'icona occhio +3. **Atteso:** Password visibile/nascosta ✅ + +--- + +## Badge di Test + +| Formato RFID | Badge | Nome | Risultato Atteso | +|----------------------|------------|----------------|----------------------| +| `ò0008988288_` | 0008988288 | Marco Bianchi | ✅ Ammesso | +| `ò0007399575_` | 0007399575 | Laura Rossi | ✅ Ammessa | +| `ò0000514162_` | 0000514162 | Giuseppe Verdi | ❌ Non ammesso | +| `ò0006478281_` | 0006478281 | - | ⚠️ Non trovato (404) | + +**Password validatore:** `focolari` + +--- + +## Note +- I badge sono stringhe, gli zeri iniziali sono significativi +- Il pattern RFID italiano usa `ò` come start e `_` come end (+ Enter) +- Il pattern US usa `;` come start e `?` come end (+ Enter) diff --git a/ai-prompts/00-welcome-agent.md b/ai-prompts/00-welcome-agent.md index a82101e..f106603 100644 --- a/ai-prompts/00-welcome-agent.md +++ b/ai-prompts/00-welcome-agent.md @@ -39,6 +39,7 @@ VotoFocolari/ ├── backend-mock/ │ ├── main.py # Entry point con argparse │ ├── Pipfile # Dipendenze Python +│ ├── API_SPECIFICATION.md # Specifiche per backend reale │ ├── api/routes.py # Endpoint API │ ├── schemas/models.py # Modelli Pydantic │ └── data/ @@ -65,14 +66,15 @@ VotoFocolari/ 1. **Login Validatore**: Passa badge + inserisci password → Sessione 30 min 2. **Attesa Partecipante**: Schermata grande "Passa il badge" 3. **Visualizzazione Utente**: Card con foto, nome, ruolo, stato ammissione -4. **Conferma Ingresso**: Validatore ripassa il badge → Carosello benvenuto multilingua +4. **Conferma Ingresso**: Validatore ripassa il badge → Carosello benvenuto multilingua (8s) ### Gestione RFID -- **Multi-pattern**: Supporta layout tastiera US (`;?`) e IT (`ò_`) +- **Multi-pattern**: Supporta layout tastiera US (`;?`) e IT (`ò_`), con `\n` dopo fine sequenza - **Timeout 2.5s**: Per scansioni accidentali - **ESC annulla**: Scansione in corso - **Enter handling**: Gestito automaticamente +- **Stesso badge ignorato**: Se passato più volte di seguito ### Sicurezza Sessioni @@ -125,6 +127,7 @@ VotoFocolari/ | Chiamate API | `frontend/src/services/api.ts` | | Endpoint backend | `backend-mock/api/routes.py` | | Dati mock utenti | `backend-mock/data/users_default.json` | +| Specifiche API produzione | `backend-mock/API_SPECIFICATION.md` | | Configurazione Vite | `frontend/vite.config.ts` | --- @@ -142,6 +145,10 @@ VotoFocolari/ 4. **NumLock**: Su desktop, viene mostrato un banner per ricordare di attivare NumLock. +5. **Badge Duplicati**: Se lo stesso badge viene passato più volte di seguito, viene ignorato (no ricaricamento). + +6. **Success Modal Interrompibile**: Se durante il carosello di benvenuto si passa un nuovo badge, la modal si chiude e viene caricato subito il nuovo utente. + --- ## TODO (da concordare con committenti) diff --git a/ai-prompts/01-backend-plan.md b/ai-prompts/01-backend-plan.md index 2562236..0444234 100644 --- a/ai-prompts/01-backend-plan.md +++ b/ai-prompts/01-backend-plan.md @@ -70,7 +70,7 @@ backend-mock/ - [x] `POST /login-validate` - verifica solo password validatore - [x] `GET /anagrafica/{badge_code}` - ricerca utente - [x] Pulizia caratteri sentinel dal badge - - [x] Confronto con e senza zeri iniziali + - [x] **Confronto ESATTO come stringa** (zeri iniziali significativi) - [x] Warning automatico se non ammesso - [x] `POST /entry-request` - registrazione ingresso - [x] Verifica password validatore @@ -173,3 +173,17 @@ curl -X POST http://localhost:8000/entry-request \ ## ✅ BACKEND COMPLETATO Tutti i task sono stati implementati e testati. + +### 📄 Documentazione API +Per le specifiche complete da implementare nel backend reale, vedere: +**`backend-mock/API_SPECIFICATION.md`** + +Questo documento contiene: +- Descrizione completa di tutti gli endpoint +- Schema request/response JSON +- Codici di errore e gestione +- Meccanismo invalidazione sessioni (server_start_time) +- Considerazioni di sicurezza +- Struttura database suggerita +- Casi di test minimi + diff --git a/ai-prompts/02-frontend-plan.md b/ai-prompts/02-frontend-plan.md index 1b441bd..b096419 100644 --- a/ai-prompts/02-frontend-plan.md +++ b/ai-prompts/02-frontend-plan.md @@ -71,7 +71,7 @@ Ottimizzata per tablet in orizzontale. - [x] `RFIDStatus.tsx` - indicatore stato scanner - [x] `UserCard.tsx` - card utente con foto e ruolo - [x] `CountdownTimer.tsx` - timer con progress bar -- [x] `WelcomeCarousel.tsx` - carosello messaggi multilingua +- [x] `WelcomeCarousel.tsx` - carosello messaggi multilingua con **animazione smooth sliding** - [x] `NumLockBanner.tsx` - avviso NumLock per desktop ### 7. Schermate (`screens/`) @@ -80,10 +80,10 @@ Ottimizzata per tablet in orizzontale. - [x] `ValidatorLoginScreen.tsx` - attesa badge + password + NumLockBanner - [x] `ActiveGateScreen.tsx` - varco attivo: - [x] Card utente (layout largo per tablet) - - [x] **Schermata "badge non trovato"** con countdown 30s + - [x] **Schermata "badge non trovato"** con countdown barra visiva (30s) - [x] **Notifica badge validatore ignorato** - [x] NumLockBanner -- [x] `SuccessModal.tsx` - conferma ingresso con carosello +- [x] `SuccessModal.tsx` - conferma ingresso con carosello (**durata aumentata 8s**) - [x] `ErrorModal.tsx` - errore fullscreen - [x] `DebugScreen.tsx` - pagina diagnostica RFID @@ -95,10 +95,13 @@ Ottimizzata per tablet in orizzontale. - [x] **Qualsiasi badge può essere validatore** (verificato con password) - [x] Password salvata in sessione per conferme ingresso - [x] **Invalidazione sessione se server riparte** (serverStartTime) +- [x] **Polling periodico (30s) per verificare server restart** - [x] Timeout sessione 30 minuti - [x] Timeout utente 60 secondi - [x] **Timeout badge non trovato 30 secondi** +- [x] **Ignora stesso badge passato più volte** (no ricaricamento) - [x] Cambio rapido badge partecipante +- [x] **Badge durante success modal chiude modal e carica nuovo utente** (fix dipendenze useCallback) - [x] Conferma con badge validatore (quello della sessione) - [x] **Notifica se badge validatore rippassato senza utente** - [x] Logging transizioni con prefisso `[FLOW]` @@ -107,9 +110,10 @@ Ottimizzata per tablet in orizzontale. - [x] Componente `WelcomeCarousel.tsx` - [x] 10 lingue supportate -- [x] Scorrimento automatico ogni 800ms +- [x] **Animazione smooth sliding** (slide up/down) +- [x] Scorrimento automatico (intervallo calcolato dinamicamente) - [x] Modale fullscreen verde -- [x] Durata totale: 5 secondi +- [x] **Durata totale: 8 secondi** (più rilassato) ### 10. Debug & Diagnostica @@ -135,4 +139,46 @@ Ottimizzata per tablet in orizzontale. --- +## 🧪 TODO: Test Automatici + +### Test da Implementare + +- [ ] **Test RFID Scanner:** + - [ ] Rilevamento pattern US (`;` → `?`) + - [ ] Rilevamento pattern IT (`ò` → `_`) + - [ ] Timeout scansione incompleta + - [ ] ESC annulla scansione + - [ ] Enter post-completamento ignorato + +- [ ] **Test Flow Validatore:** + - [ ] Login con password corretta + - [ ] Login con password errata + - [ ] Sessione persistente in localStorage + - [ ] Invalidazione sessione al riavvio server + - [ ] Logout manuale + +- [ ] **Test Flow Partecipante:** + - [ ] Badge trovato ammesso → mostra card verde + - [ ] Badge trovato non ammesso → mostra card rossa + warning + - [ ] Badge non trovato → mostra schermata errore con countdown + - [ ] Stesso badge passato più volte → ignorato + - [ ] Badge diverso passato → cambia utente visualizzato + - [ ] Timeout 60s → torna in attesa + +- [ ] **Test Conferma Ingresso:** + - [ ] Badge validatore su utente ammesso → success modal + - [ ] Badge validatore su utente NON ammesso → notifica ignorato + - [ ] Badge validatore senza utente → notifica ignorato + - [ ] **⚠️ IMPORTANTE:** Simulare bug che bypassa frontend → backend DEVE bloccare + +- [ ] **Test Success Modal:** + - [ ] Carosello scorre tutte le lingue + - [ ] Durata corretta (8s) + - [ ] Badge durante modal → chiude modal e carica nuovo utente + +--- + ## ✅ FRONTEND COMPLETATO + +Tutte le funzionalità principali sono state implementate. Rimangono da sviluppare i test automatici. + diff --git a/backend-mock/API_SPECIFICATION.md b/backend-mock/API_SPECIFICATION.md new file mode 100644 index 0000000..ef3b1ff --- /dev/null +++ b/backend-mock/API_SPECIFICATION.md @@ -0,0 +1,325 @@ +# 📄 Specifiche API Backend - Focolari Voting System + +Questo documento descrive le specifiche che il backend reale deve implementare per funzionare correttamente con il frontend del sistema di controllo accessi. + +**Versione:** 1.0 +**Data:** Gennaio 2026 + +--- + +## 🌐 Configurazione Server + +### CORS +Il server deve abilitare CORS con le seguenti impostazioni: +- **Origins:** `*` (o lista specifica di domini autorizzati) +- **Methods:** `GET, POST, OPTIONS` +- **Headers:** `Content-Type, Authorization` + +### Serving Frontend +Il backend dovrebbe servire il frontend buildato (file statici) dalla root `/`. +Questo permette di avere un unico endpoint per frontend e API. + +--- + +## 🔑 Meccanismo di Invalidazione Sessioni + +### Server Start Time +All'avvio, il server deve generare un **timestamp univoco** (es. Unix timestamp in secondi). +Questo valore viene restituito nell'endpoint `/info-room` e serve al frontend per invalidare sessioni obsolete. + +**Comportamento:** +1. Il frontend salva `server_start_time` nella sessione locale +2. Al caricamento successivo, confronta il valore salvato con quello attuale +3. Se differiscono, la sessione viene invalidata (il server è stato riavviato) + +Questo garantisce che: +- Un riavvio del server forza il re-login di tutti i validatori +- Sessioni zombie non rimangono attive dopo manutenzione + +--- + +## 📡 Endpoint API + +### 1. `GET /info-room` + +Restituisce informazioni sulla sala/meeting corrente e lo stato del server. + +#### Response `200 OK` +```json +{ + "room_name": "Sala Assemblea", + "meeting_id": "VOT-2024", + "server_start_time": 1737100800 +} +``` + +| Campo | Tipo | Descrizione | +|-------|------|-------------| +| `room_name` | string | Nome della sala visualizzato nell'header | +| `meeting_id` | string | Identificativo del meeting/votazione | +| `server_start_time` | integer | Unix timestamp dell'avvio server (per invalidazione sessioni) | + +--- + +### 2. `POST /login-validate` + +Verifica le credenziali del validatore. **IMPORTANTE:** Qualsiasi badge può diventare un badge validatore se la password è corretta. + +#### Request Body +```json +{ + "badge": "0007399575", + "password": "focolari" +} +``` + +| Campo | Tipo | Descrizione | +|-------|------|-------------| +| `badge` | string | Codice badge scansionato (numerico, senza sentinel) | +| `password` | string | Password inserita dall'utente | + +#### Response `200 OK` (Successo) +```json +{ + "success": true, + "message": "Login effettuato con successo", + "token": "optional-jwt-token" +} +``` + +#### Response `401 Unauthorized` (Errore) +```json +{ + "detail": "Password non valida" +} +``` + +**Note:** +- La password è l'unico fattore di autenticazione +- Il badge viene memorizzato dal frontend come "badge validatore" per conferme successive +- Il token è opzionale (per future implementazioni di autenticazione JWT) + +--- + +### 3. `GET /anagrafica/{badge_code}` + +Recupera i dati anagrafici di un utente dato il suo codice badge. + +#### Path Parameters +- `badge_code`: Codice badge (stringa, es. "0008988288") + +**IMPORTANTE - Confronto Badge:** +Il badge è una **stringa**, non un numero. Va confrontato **esattamente** carattere per carattere. +Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **diversi**. + +#### Response `200 OK` (Utente trovato e ammesso) +```json +{ + "badge_code": "0008988288", + "nome": "Marco", + "cognome": "Bianchi", + "url_foto": "https://example.com/foto.jpg", + "ruolo": "Votante", + "ammesso": true # <-- utente ammesso all'ingresso +} +``` + +#### Response `200 OK` (Utente trovato ma NON ammesso) +```json +{ + "badge_code": "0000514162", + "nome": "Giuseppe", + "cognome": "Verdi", + "url_foto": "https://example.com/foto.jpg", + "ruolo": "Tecnico", + "ammesso": false, #<-- utente NON ammesso all'ingresso + "warning": "Utente non ammesso all'ingresso" +} +``` + +#### Response `404 Not Found` (Utente non trovato) +```json +{ + "detail": "Badge non trovato nel sistema" +} +``` + +| Campo Response | Tipo | Descrizione | +|----------------|------|-------------| +| `badge_code` | string | Codice badge | +| `nome` | string | Nome dell'utente | +| `cognome` | string | Cognome dell'utente | +| `url_foto` | string | URL immagine profilo (può essere placeholder) | +| `ruolo` | string | Ruolo dell'utente (es. "Votante", "Tecnico", "Ospite") | +| `ammesso` | boolean | `true` se autorizzato all'ingresso | +| `warning` | string? | Opzionale, presente se `ammesso: false` | + +--- + +### 4. `POST /entry-request` + +Registra l'ingresso di un utente. Richiede conferma del validatore. + +#### Request Body +```json +{ + "user_badge": "0008988288", + "validator_password": "focolari" +} +``` + +| Campo | Tipo | Descrizione | +|-------|------|-------------| +| `user_badge` | string | Badge dell'utente che sta entrando | +| `validator_password` | string | Password del validatore (ri-verifica) | + +#### Response `200 OK` (Successo) +```json +{ + "success": true, + "message": "Ingresso registrato con successo" +} +``` + +#### Response `401 Unauthorized` (Password errata) +```json +{ + "detail": "Password validatore non valida" +} +``` + +#### Response `403 Forbidden` (Utente non ammesso) +```json +{ + "detail": "Utente non autorizzato all'ingresso" +} +``` + +#### Response `404 Not Found` (Badge non trovato) +```json +{ + "detail": "Badge utente non trovato" +} +``` + +**IMPORTANTE - Sicurezza:** +Anche se il frontend non dovrebbe permettere di inviare entry-request per utenti non ammessi, il backend **DEVE** sempre verificare: +1. Che la password validatore sia corretta +2. Che l'utente esista +3. Che l'utente sia ammesso (`ammesso: true`) + +Non fidarsi mai del frontend per la validazione! + +--- + +## 🔒 Considerazioni di Sicurezza + +### Validazione Lato Backend +Il backend deve **sempre** eseguire tutte le validazioni, indipendentemente da cosa fa il frontend: + +1. **Login:** Verificare che la password sia corretta +2. **Entry:** + - Verificare password validatore + - Verificare che utente esista + - Verificare che utente sia ammesso + - Loggare l'operazione per audit + +### Logging e Audit +Si raccomanda di loggare: +- Ogni tentativo di login (successo/fallimento) +- Ogni registrazione di ingresso +- Badge non trovati (potenziale tentativo di accesso non autorizzato) + +### Protezione Rate Limiting +Implementare rate limiting su: +- `/login-validate`: max 5 tentativi/minuto per IP +- `/entry-request`: max 30 richieste/minuto per IP + +--- + +## 📊 Struttura Dati Suggerita + +### Database Utenti +```sql +CREATE TABLE users ( + id SERIAL PRIMARY KEY, + badge_code VARCHAR(20) UNIQUE NOT NULL, + nome VARCHAR(100) NOT NULL, + cognome VARCHAR(100) NOT NULL, + url_foto VARCHAR(500), + ruolo VARCHAR(50) NOT NULL, + ammesso BOOLEAN DEFAULT true, + created_at TIMESTAMP DEFAULT NOW(), + updated_at TIMESTAMP DEFAULT NOW() +); + +CREATE INDEX idx_badge_code ON users(badge_code); +``` + +### Tabella Accessi (Audit Log) +```sql +CREATE TABLE access_log ( + id SERIAL PRIMARY KEY, + user_badge VARCHAR(20) NOT NULL, + validator_badge VARCHAR(20) NOT NULL, + room_id VARCHAR(50), + action VARCHAR(20) NOT NULL, -- 'entry', 'denied', 'not_found' + timestamp TIMESTAMP DEFAULT NOW(), + ip_address VARCHAR(45) +); +``` + +--- + +## 🧪 Test e Validazione + +### Casi di Test Minimi + +1. **Login con password corretta** → 200 OK +2. **Login con password errata** → 401 Unauthorized +3. **Anagrafica badge esistente ammesso** → 200 OK con `ammesso: true` +4. **Anagrafica badge esistente non ammesso** → 200 OK con `ammesso: false` + warning +5. **Anagrafica badge inesistente** → 404 Not Found +6. **Entry utente ammesso con password corretta** → 200 OK +7. **Entry utente ammesso con password errata** → 401 Unauthorized +8. **Entry utente NON ammesso** → 403 Forbidden (anche se password corretta!) +9. **Entry badge inesistente** → 404 Not Found + +### Badge di Test (Mock) +``` +0008988288 - Marco Bianchi (Votante, ammesso) +0007399575 - Laura Rossi (Votante, ammessa) +0000514162 - Giuseppe Verdi (Tecnico, NON ammesso) +0006478281 - NON nel database (per test 404) +``` + +--- + +## 📝 Note Implementative + +### Content-Type +Tutte le richieste e risposte usano `application/json`. + +### Codici Errore HTTP +- `200`: Successo +- `401`: Non autorizzato (password errata) +- `403`: Vietato (utente non ammesso) +- `404`: Risorsa non trovata +- `500`: Errore interno server + +### Formato Errori +Tutti gli errori devono restituire un JSON con campo `detail`: +```json +{ + "detail": "Messaggio di errore descrittivo" +} +``` + +--- + +## 🔄 Changelog + +### v1.0 (Gennaio 2026) +- Specifica iniziale +- Endpoint: `/info-room`, `/login-validate`, `/anagrafica/{badge}`, `/entry-request` +- Meccanismo invalidazione sessioni con `server_start_time` diff --git a/backend-mock/api/routes.py b/backend-mock/api/routes.py index fc11e41..062fa1d 100644 --- a/backend-mock/api/routes.py +++ b/backend-mock/api/routes.py @@ -46,10 +46,11 @@ def clean_badge(badge: str) -> str: def find_user(badge_code: str) -> dict | None: - """Cerca un utente per badge code""" + """Cerca un utente per badge code (confronto esatto come stringa)""" clean = clean_badge(badge_code) for user in _data["users"]: - if user["badge_code"] == clean or user["badge_code"].lstrip("0") == clean.lstrip("0"): + # Confronto esatto: il badge è una stringa, non un numero + if user["badge_code"] == clean: return user return None diff --git a/backend-mock/data/users_default.json b/backend-mock/data/users_default.json index 4727986..146951e 100644 --- a/backend-mock/data/users_default.json +++ b/backend-mock/data/users_default.json @@ -5,14 +5,6 @@ "meeting_id": "VOT-2024" }, "users": [ - { - "badge_code": "0008988288", - "nome": "Marco", - "cognome": "Bianchi", - "url_foto": "https://randomuser.me/api/portraits/men/1.jpg", - "ruolo": "Votante", - "ammesso": true - }, { "badge_code": "0007399575", "nome": "Laura", diff --git a/frontend/src/App.tsx b/frontend/src/App.tsx index d3d2f3c..1e279a6 100644 --- a/frontend/src/App.tsx +++ b/frontend/src/App.tsx @@ -12,6 +12,7 @@ import type {AppState, RoomInfo, User, ValidatorSession} from './types'; // Costanti const SESSION_DURATION_MS = 30 * 60 * 1000; // 30 minuti const USER_TIMEOUT_SECONDS = 60; +const SUCCESS_MODAL_DURATION_MS = 8000; // Aumentato per carosello più rilassato const STORAGE_KEY = 'focolari_validator_session'; function App() { @@ -38,6 +39,9 @@ function App() { // Badge non trovato (con timeout per tornare all'attesa) const [notFoundBadge, setNotFoundBadge] = useState(null); + // Badge corrente visualizzato (per evitare ricaricamenti multipli dello stesso) + const [currentBadgeCode, setCurrentBadgeCode] = useState(null); + // ============================================ // Session Management // ============================================ @@ -120,10 +124,22 @@ function App() { setTimeout(() => setShowValidatorBadgeNotice(false), 4000); } } else { + // Badge partecipante - verifica se è lo stesso già visualizzato + if (cleanCode === currentBadgeCode) { + console.log('[FLOW] Same badge scanned again - ignoring'); + break; + } + console.log('[FLOW] Loading participant:', cleanCode); // Badge partecipante - carica utente // Se c'era un badge non trovato, cancellalo e carica il nuovo setNotFoundBadge(null); + // Se era aperta la success modal, chiudila subito + if (showSuccessModal) { + console.log('[FLOW] Closing success modal for new badge'); + setShowSuccessModal(false); + setSuccessUserName(undefined); + } await handleLoadUser(cleanCode); } break; @@ -131,7 +147,7 @@ function App() { default: break; } - }, [appState, currentUser, validatorSession]); + }, [appState, currentUser, validatorSession, showSuccessModal, currentBadgeCode]); // ============================================ // Initialize RFID Scanner @@ -153,6 +169,7 @@ function App() { setLoading(true); setError(undefined); setNotFoundBadge(null); + setCurrentBadgeCode(badgeCode); // Traccia il badge corrente setAppState('showing-user'); try { @@ -195,6 +212,7 @@ function App() { setSuccessUserName(userName); setShowSuccessModal(true); setCurrentUser(null); + setCurrentBadgeCode(null); // Reset badge corrente dopo successo setAppState('gate-active'); } } catch (err) { @@ -255,6 +273,7 @@ function App() { const handleCancelUser = useCallback(() => { setCurrentUser(null); setNotFoundBadge(null); + setCurrentBadgeCode(null); // Reset badge corrente setError(undefined); setAppState('gate-active'); }, []); @@ -263,6 +282,7 @@ function App() { console.log('[App] User timeout - tornando in attesa'); setCurrentUser(null); setNotFoundBadge(null); + setCurrentBadgeCode(null); // Reset badge corrente setError(undefined); setAppState('gate-active'); }, []); @@ -323,6 +343,29 @@ function App() { return () => clearInterval(interval); }, [validatorSession, clearSession]); + // Polling per verificare se il server è stato riavviato + useEffect(() => { + if (!validatorSession || !roomInfo) return; + + const checkServerRestart = async () => { + try { + const info = await getRoomInfo(); + if (info.server_start_time !== validatorSession.serverStartTime) { + console.log('[FLOW] Server restarted - invalidating session'); + clearSession(); + } + } catch { + // Ignora errori di connessione durante il polling + console.warn('[FLOW] Server unreachable during polling'); + } + }; + + // Polling ogni 30 secondi + const interval = setInterval(checkServerRestart, 30000); + + return () => clearInterval(interval); + }, [validatorSession, roomInfo, clearSession]); + // ============================================ // Render // ============================================ @@ -387,6 +430,7 @@ function App() { isOpen={showSuccessModal} onClose={handleSuccessModalClose} userName={successUserName} + durationMs={SUCCESS_MODAL_DURATION_MS} /> {/* Error Modal */} diff --git a/frontend/src/components/WelcomeCarousel.tsx b/frontend/src/components/WelcomeCarousel.tsx index 8edb725..ab8f6ea 100644 --- a/frontend/src/components/WelcomeCarousel.tsx +++ b/frontend/src/components/WelcomeCarousel.tsx @@ -2,7 +2,7 @@ * Welcome Carousel Component - Focolari Voting System * * Carosello automatico di messaggi di benvenuto multilingua. - * Scorre automaticamente ogni 800ms durante la visualizzazione. + * Scorrimento fluido con animazione smooth. */ import {useEffect, useState} from 'react'; @@ -25,55 +25,51 @@ const WELCOME_MESSAGES: WelcomeMessage[] = [ {lang: 'ru', text: 'Добро пожаловать!'}, ]; -const CAROUSEL_INTERVAL_MS = 800; - interface WelcomeCarouselProps { /** Se true, il carosello è in pausa */ paused?: boolean; /** Nome dell'utente da visualizzare (opzionale) */ userName?: string; + /** Intervallo tra i messaggi in ms (default 800) */ + intervalMs?: number; } -export function WelcomeCarousel({paused = false, userName}: WelcomeCarouselProps) { +export function WelcomeCarousel({paused = false, userName, intervalMs = 800}: WelcomeCarouselProps) { const [currentIndex, setCurrentIndex] = useState(0); - const [isTransitioning, setIsTransitioning] = useState(false); useEffect(() => { if (paused) return; const interval = setInterval(() => { - setIsTransitioning(true); - - setTimeout(() => { - setCurrentIndex((prev) => (prev + 1) % WELCOME_MESSAGES.length); - setIsTransitioning(false); - }, 150); // Durata transizione fade - }, CAROUSEL_INTERVAL_MS); + setCurrentIndex((prev) => (prev + 1) % WELCOME_MESSAGES.length); + }, intervalMs); return () => clearInterval(interval); - }, [paused]); + }, [paused, intervalMs]); const currentMessage = WELCOME_MESSAGES[currentIndex]; return ( -
- {/* Messaggio di benvenuto */} -
-

- {currentMessage.text} -

-

- {currentMessage.lang.toUpperCase()} -

+
+ {/* Container con overflow hidden per animazione slide - altezza maggiore per testi lunghi */} +
+ {/* Messaggio corrente */} +
+

+ {currentMessage.text} +

+

+ {currentMessage.lang.toUpperCase()} +

+
{/* Nome utente */} {userName && ( -
+

{userName}

@@ -81,18 +77,43 @@ export function WelcomeCarousel({paused = false, userName}: WelcomeCarouselProps )} {/* Indicatori */} -
+
{WELCOME_MESSAGES.map((_, index) => (
))}
+ + {/* Stili per animazione smooth */} +
); } diff --git a/frontend/src/screens/ActiveGateScreen.tsx b/frontend/src/screens/ActiveGateScreen.tsx index e89f426..0afdd86 100644 --- a/frontend/src/screens/ActiveGateScreen.tsx +++ b/frontend/src/screens/ActiveGateScreen.tsx @@ -3,7 +3,6 @@ * Schermata principale del varco attivo */ -import {useEffect, useState} from 'react'; import {Button, CountdownTimer, Logo, NumLockBanner, RFIDStatus, UserCard} from '../components'; import type {RFIDScannerState, RoomInfo, User} from '../types'; @@ -39,27 +38,6 @@ export function ActiveGateScreen({ onUserTimeout, showValidatorBadgeNotice = false, }: ActiveGateScreenProps) { - // Timer per countdown badge non trovato - const [notFoundCountdown, setNotFoundCountdown] = useState(NOT_FOUND_TIMEOUT_SECONDS); - - // Reset countdown quando cambia notFoundBadge - useEffect(() => { - if (notFoundBadge) { - setNotFoundCountdown(NOT_FOUND_TIMEOUT_SECONDS); - const interval = setInterval(() => { - setNotFoundCountdown(prev => { - if (prev <= 1) { - clearInterval(interval); - onUserTimeout(); - return 0; - } - return prev - 1; - }); - }, 1000); - return () => clearInterval(interval); - } - }, [notFoundBadge, onUserTimeout]); - return (
{/* Header */} @@ -123,6 +101,17 @@ export function ActiveGateScreen({ ) : notFoundBadge ? ( // Badge non trovato
+ {/* Timer bar in alto come per l'utente trovato */} +
+ +
+
Utente con badge:

{notFoundBadge}

non trovato nel sistema

- - {/* Footer con countdown */} -
-

- Ritorno all'attesa in {notFoundCountdown} secondi -

-
) : error ? ( // Error state diff --git a/frontend/src/screens/SuccessModal.tsx b/frontend/src/screens/SuccessModal.tsx index bafe05a..b663309 100644 --- a/frontend/src/screens/SuccessModal.tsx +++ b/frontend/src/screens/SuccessModal.tsx @@ -9,19 +9,27 @@ interface SuccessModalProps { isOpen: boolean; onClose: () => void; userName?: string; + durationMs?: number; } +// Numero messaggi nel carosello +const CAROUSEL_MESSAGE_COUNT = 10; + export function SuccessModal({ isOpen, onClose, - userName + userName, + durationMs = 8000 }: SuccessModalProps) { + // Calcola intervallo carosello per mostrare tutti i messaggi durante la durata + const carouselIntervalMs = Math.floor(durationMs / (CAROUSEL_MESSAGE_COUNT * 1.2)); + return (
@@ -46,7 +54,7 @@ export function SuccessModal({
{/* Carosello Messaggi Benvenuto */} - + {/* Sub text */}

@@ -59,7 +67,7 @@ export function SuccessModal({