feat: aggiornamenti alla documentazione e miglioramenti UI/UX
This commit is contained in:
164
NETWORK_ACCESS.md
Normal file
164
NETWORK_ACCESS.md
Normal file
@@ -0,0 +1,164 @@
|
|||||||
|
# Accesso alla Rete Locale
|
||||||
|
|
||||||
|
## Come accedere al server da altri dispositivi (tablet, smartphone, ecc.)
|
||||||
|
|
||||||
|
Il sistema Focolari Voting è progettato per funzionare su tablet Android collegati alla stessa rete locale del server.
|
||||||
|
|
||||||
|
### 1. Avvio del Server
|
||||||
|
|
||||||
|
Il server è già configurato per accettare connessioni da tutta la rete locale:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./dev.sh server
|
||||||
|
```
|
||||||
|
|
||||||
|
Il server si avvia su `0.0.0.0:8000`, il che significa che è in ascolto su **tutte le interfacce di rete**.
|
||||||
|
|
||||||
|
### 2. Trova l'indirizzo IP del server
|
||||||
|
|
||||||
|
Sul computer che esegue il server, trova il suo indirizzo IP locale:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Linux
|
||||||
|
ip addr show | grep "inet " | grep -v 127.0.0.1
|
||||||
|
|
||||||
|
# Oppure
|
||||||
|
hostname -I
|
||||||
|
```
|
||||||
|
|
||||||
|
L'output sarà qualcosa come: `192.168.1.230` (o simile, dipende dalla tua rete).
|
||||||
|
|
||||||
|
### 3. Accedi dal tablet/dispositivo
|
||||||
|
|
||||||
|
Sul tablet o altro dispositivo connesso alla **stessa rete WiFi**:
|
||||||
|
|
||||||
|
1. Apri il browser (Chrome, Firefox, ecc.)
|
||||||
|
2. Vai all'indirizzo: `http://192.168.1.230:8000`
|
||||||
|
- Sostituisci `192.168.1.230` con l'IP che hai trovato nel passo 2
|
||||||
|
- Mantieni la porta `:8000` (o quella che hai specificato con `-p`)
|
||||||
|
|
||||||
|
### 4. Verifica della connettività
|
||||||
|
|
||||||
|
#### Test ping dal tablet
|
||||||
|
|
||||||
|
Se il tablet non si connette, verifica prima che possa "vedere" il server:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Dal computer server
|
||||||
|
ping 192.168.1.xxx # IP del tablet
|
||||||
|
```
|
||||||
|
|
||||||
|
Se il ping funziona in entrambe le direzioni, il problema è probabilmente il firewall.
|
||||||
|
|
||||||
|
#### Firewall Linux
|
||||||
|
|
||||||
|
Se usi un firewall (ufw, firewalld, iptables), devi aprire la porta:
|
||||||
|
|
||||||
|
**ufw:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ufw allow 8000/tcp
|
||||||
|
sudo ufw reload
|
||||||
|
```
|
||||||
|
|
||||||
|
**firewalld:**
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo firewall-cmd --permanent --add-port=8000/tcp
|
||||||
|
sudo firewall-cmd --reload
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Verifica porte in ascolto
|
||||||
|
|
||||||
|
Per verificare che il server sia effettivamente in ascolto:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo ss -tlnp | grep :8000
|
||||||
|
# o
|
||||||
|
sudo netstat -tlnp | grep :8000
|
||||||
|
```
|
||||||
|
|
||||||
|
Dovresti vedere qualcosa come:
|
||||||
|
|
||||||
|
```
|
||||||
|
tcp 0 0 0.0.0.0:8000 0.0.0.0:* LISTEN 12345/python
|
||||||
|
```
|
||||||
|
|
||||||
|
**Nota importante:** Se vedi `127.0.0.1:8000` invece di `0.0.0.0:8000`, significa che il server è in ascolto solo su
|
||||||
|
localhost. In questo caso, riavvia senza il parametro `--host 127.0.0.1`.
|
||||||
|
|
||||||
|
### 5. Porta personalizzata
|
||||||
|
|
||||||
|
Se la porta 8000 è già in uso o vuoi usarne un'altra:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./dev.sh server -p 9000
|
||||||
|
```
|
||||||
|
|
||||||
|
Poi accedi da: `http://192.168.1.230:9000`
|
||||||
|
|
||||||
|
### 6. Ambiente di produzione
|
||||||
|
|
||||||
|
Per un deployment permanente su un tablet/kiosk:
|
||||||
|
|
||||||
|
1. Il tablet deve avere un IP fisso (configurato nel router o nelle impostazioni WiFi)
|
||||||
|
2. Il server deve avviarsi automaticamente (systemd service o simili)
|
||||||
|
3. Il browser del tablet può essere configurato in modalità kiosk per avviare automaticamente l'URL
|
||||||
|
|
||||||
|
## Risoluzione problemi comuni
|
||||||
|
|
||||||
|
### Problema: "Impossibile raggiungere il sito"
|
||||||
|
|
||||||
|
**Cause possibili:**
|
||||||
|
|
||||||
|
1. ❌ Il server non è in esecuzione → verifica con `ps aux | grep python`
|
||||||
|
2. ❌ Firewall attivo → segui le istruzioni sopra per aprire la porta
|
||||||
|
3. ❌ IP sbagliato → ricontrolla l'IP del server con `hostname -I`
|
||||||
|
4. ❌ Tablet non sulla stessa rete → connetti alla stessa WiFi del server
|
||||||
|
5. ❌ Server in ascolto solo su localhost → NON usare `--host 127.0.0.1`
|
||||||
|
|
||||||
|
### Problema: "Server non raggiungibile" (pagina di errore del frontend)
|
||||||
|
|
||||||
|
Il frontend ha una pagina di errore che effettua un ping automatico. Se vedi questa pagina:
|
||||||
|
|
||||||
|
1. Il frontend è caricato correttamente
|
||||||
|
2. Ma l'API backend non risponde
|
||||||
|
3. Verifica che il backend sia effettivamente in esecuzione
|
||||||
|
4. Controlla la console JavaScript per errori CORS
|
||||||
|
|
||||||
|
### Problema: CORS errors nella console
|
||||||
|
|
||||||
|
Non dovrebbero verificarsi, il backend ha CORS abilitato per `*`. Se li vedi:
|
||||||
|
|
||||||
|
1. Verifica che il backend sia avviato correttamente
|
||||||
|
2. Controlla che non ci siano proxy/firewall intermedi che modificano le richieste
|
||||||
|
|
||||||
|
## Note per deployment reale
|
||||||
|
|
||||||
|
- Il sistema è stato testato su **rete locale privata**
|
||||||
|
- Non esporre direttamente il server su Internet senza autenticazione aggiuntiva
|
||||||
|
- In produzione, considera di usare HTTPS con un reverse proxy (nginx/caddy)
|
||||||
|
- Il default `0.0.0.0` è sicuro in una rete locale controllata, ma valuta restrizioni aggiuntive per deployment più
|
||||||
|
grandi
|
||||||
|
|
||||||
|
## Comandi rapidi
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Trova IP del server
|
||||||
|
hostname -I
|
||||||
|
|
||||||
|
# Avvia server (accessibile da rete)
|
||||||
|
./dev.sh server
|
||||||
|
|
||||||
|
# Avvia server su porta custom
|
||||||
|
./dev.sh server -p 9000
|
||||||
|
|
||||||
|
# Avvia server SOLO su localhost (NON accessibile da rete)
|
||||||
|
./dev.sh server --host 127.0.0.1
|
||||||
|
|
||||||
|
# Verifica porte in ascolto
|
||||||
|
sudo ss -tlnp | grep python
|
||||||
|
|
||||||
|
# Apri porta nel firewall (ufw)
|
||||||
|
sudo ufw allow 8000/tcp
|
||||||
|
```
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
# ✅ Checklist Test Manuali - Focolari Voting System
|
# ✅ Checklist Test Manuali - Focolari Voting System
|
||||||
|
|
||||||
## Pre-requisiti
|
## Pre-requisiti
|
||||||
|
|
||||||
- [ ] Backend avviato con `./dev.sh server`
|
- [ ] Backend avviato con `./dev.sh server`
|
||||||
- [ ] Browser aperto su `http://localhost:8000`
|
- [ ] Browser aperto su `http://localhost:8000`
|
||||||
- [ ] Lettore RFID collegato (o usa tastiera per simulare)
|
- [ ] Lettore RFID collegato (o usa tastiera per simulare)
|
||||||
@@ -10,16 +11,19 @@
|
|||||||
## 🔐 Test Login Validatore
|
## 🔐 Test Login Validatore
|
||||||
|
|
||||||
### T1: Login con password corretta
|
### T1: Login con password corretta
|
||||||
|
|
||||||
1. Passa un badge qualsiasi (es. `ò0008988288_`)
|
1. Passa un badge qualsiasi (es. `ò0008988288_`)
|
||||||
2. Inserisci password: `focolari`
|
2. Inserisci password: `focolari`
|
||||||
3. **Atteso:** Accesso al varco attivo ✅
|
3. **Atteso:** Accesso al varco attivo ✅
|
||||||
|
|
||||||
### T2: Login con password errata
|
### T2: Login con password errata
|
||||||
|
|
||||||
1. Passa un badge qualsiasi
|
1. Passa un badge qualsiasi
|
||||||
2. Inserisci password sbagliata
|
2. Inserisci password sbagliata
|
||||||
3. **Atteso:** Messaggio errore rosso, rimane su schermata password ❌
|
3. **Atteso:** Messaggio errore rosso, rimane su schermata password ❌
|
||||||
|
|
||||||
### T3: Annulla login
|
### T3: Annulla login
|
||||||
|
|
||||||
1. Passa un badge
|
1. Passa un badge
|
||||||
2. Clicca "Annulla"
|
2. Clicca "Annulla"
|
||||||
3. **Atteso:** Torna a "Passa badge validatore" ✅
|
3. **Atteso:** Torna a "Passa badge validatore" ✅
|
||||||
@@ -29,24 +33,29 @@
|
|||||||
## 👤 Test Anagrafica Utenti
|
## 👤 Test Anagrafica Utenti
|
||||||
|
|
||||||
### T4: Badge ammesso trovato
|
### T4: Badge ammesso trovato
|
||||||
|
|
||||||
1. Login come validatore
|
1. Login come validatore
|
||||||
2. Passa badge `ò0008988288_` (Marco Bianchi)
|
2. Passa badge `ò0008988288_` (Marco Bianchi)
|
||||||
3. **Atteso:** Card verde con "Utente ammesso all'ingresso" ✅
|
3. **Atteso:** Card verde con "Utente ammesso all'ingresso" ✅
|
||||||
|
|
||||||
### T5: Badge NON ammesso trovato
|
### T5: Badge NON ammesso trovato
|
||||||
|
|
||||||
1. Passa badge `ò0000514162_` (Giuseppe Verdi)
|
1. Passa badge `ò0000514162_` (Giuseppe Verdi)
|
||||||
2. **Atteso:** Card rossa lampeggiante "ACCESSO NON CONSENTITO" ⚠️
|
2. **Atteso:** Card rossa lampeggiante "ACCESSO NON CONSENTITO" ⚠️
|
||||||
|
|
||||||
### T6: Badge non esistente nel DB
|
### T6: Badge non esistente nel DB
|
||||||
|
|
||||||
1. Passa badge `ò0006478281_`
|
1. Passa badge `ò0006478281_`
|
||||||
2. **Atteso:** Schermata errore con badge in grassetto, countdown 30s ❌
|
2. **Atteso:** Schermata errore con badge in grassetto, countdown 30s ❌
|
||||||
|
|
||||||
### T7: Stesso badge passato due volte
|
### T7: Stesso badge passato due volte
|
||||||
|
|
||||||
1. Visualizza un utente (es. Marco Bianchi)
|
1. Visualizza un utente (es. Marco Bianchi)
|
||||||
2. Passa lo STESSO badge di nuovo
|
2. Passa lo STESSO badge di nuovo
|
||||||
3. **Atteso:** Nessun ricaricamento, utente rimane visualizzato ✅
|
3. **Atteso:** Nessun ricaricamento, utente rimane visualizzato ✅
|
||||||
|
|
||||||
### T8: Badge diverso sostituisce utente corrente
|
### T8: Badge diverso sostituisce utente corrente
|
||||||
|
|
||||||
1. Visualizza Marco Bianchi (`0008988288`)
|
1. Visualizza Marco Bianchi (`0008988288`)
|
||||||
2. Passa Laura Rossi (`ò0007399575_`)
|
2. Passa Laura Rossi (`ò0007399575_`)
|
||||||
3. **Atteso:** Card cambia mostrando Laura Rossi ✅
|
3. **Atteso:** Card cambia mostrando Laura Rossi ✅
|
||||||
@@ -56,16 +65,19 @@
|
|||||||
## ✓ Test Conferma Ingresso
|
## ✓ Test Conferma Ingresso
|
||||||
|
|
||||||
### T9: Conferma utente ammesso
|
### T9: Conferma utente ammesso
|
||||||
|
|
||||||
1. Visualizza Marco Bianchi (ammesso)
|
1. Visualizza Marco Bianchi (ammesso)
|
||||||
2. Passa badge VALIDATORE (quello usato per login)
|
2. Passa badge VALIDATORE (quello usato per login)
|
||||||
3. **Atteso:** Modal verde con carosello "Benvenuto/Welcome/..." per 8 secondi ✅
|
3. **Atteso:** Modal verde con carosello "Benvenuto/Welcome/..." per 8 secondi ✅
|
||||||
|
|
||||||
### T10: Badge validatore su utente NON ammesso
|
### T10: Badge validatore su utente NON ammesso
|
||||||
|
|
||||||
1. Visualizza Giuseppe Verdi (non ammesso)
|
1. Visualizza Giuseppe Verdi (non ammesso)
|
||||||
2. Passa badge validatore
|
2. Passa badge validatore
|
||||||
3. **Atteso:** Banner arancione "Badge validatore rilevato, se cambiato fare logout" ⚠️
|
3. **Atteso:** Banner arancione "Badge validatore rilevato, se cambiato fare logout" ⚠️
|
||||||
|
|
||||||
### T11: Badge validatore senza utente visualizzato
|
### T11: Badge validatore senza utente visualizzato
|
||||||
|
|
||||||
1. Torna alla schermata "In attesa partecipante"
|
1. Torna alla schermata "In attesa partecipante"
|
||||||
2. Passa badge validatore
|
2. Passa badge validatore
|
||||||
3. **Atteso:** Banner arancione come sopra ⚠️
|
3. **Atteso:** Banner arancione come sopra ⚠️
|
||||||
@@ -75,15 +87,18 @@
|
|||||||
## 🎠 Test Success Modal
|
## 🎠 Test Success Modal
|
||||||
|
|
||||||
### T12: Carosello scorre tutte le lingue
|
### T12: Carosello scorre tutte le lingue
|
||||||
|
|
||||||
1. Conferma ingresso di un utente ammesso
|
1. Conferma ingresso di un utente ammesso
|
||||||
2. Osserva il carosello
|
2. Osserva il carosello
|
||||||
3. **Atteso:** Scorre IT→EN→FR→DE→ES→PT→ZH→JA→AR→RU con animazione smooth ✅
|
3. **Atteso:** Scorre IT→EN→FR→DE→ES→PT→ZH→JA→AR→RU con animazione smooth ✅
|
||||||
|
|
||||||
### T13: Badge durante carosello chiude modal
|
### T13: Badge durante carosello chiude modal
|
||||||
|
|
||||||
1. Durante il carosello, passa un NUOVO badge
|
1. Durante il carosello, passa un NUOVO badge
|
||||||
2. **Atteso:** Modal si chiude immediatamente, carica nuovo utente ✅
|
2. **Atteso:** Modal si chiude immediatamente, carica nuovo utente ✅
|
||||||
|
|
||||||
### T14: Carosello non troppo stretto
|
### T14: Carosello non troppo stretto
|
||||||
|
|
||||||
1. Osserva le scritte durante il carosello
|
1. Osserva le scritte durante il carosello
|
||||||
2. **Atteso:** "Добро пожаловать!" (russo) deve essere visibile per intero ✅
|
2. **Atteso:** "Добро пожаловать!" (russo) deve essere visibile per intero ✅
|
||||||
|
|
||||||
@@ -92,20 +107,36 @@
|
|||||||
## ⏱️ Test Timeout e Sessione
|
## ⏱️ Test Timeout e Sessione
|
||||||
|
|
||||||
### T15: Timeout utente (60s)
|
### T15: Timeout utente (60s)
|
||||||
|
|
||||||
1. Visualizza un utente
|
1. Visualizza un utente
|
||||||
2. Aspetta 60 secondi
|
2. Aspetta 60 secondi
|
||||||
3. **Atteso:** Torna automaticamente a "In attesa partecipante" ✅
|
3. **Atteso:** Torna automaticamente a "In attesa partecipante" ✅
|
||||||
|
|
||||||
### T16: Timeout badge non trovato (30s)
|
### T16: Timeout badge non trovato (30s)
|
||||||
|
|
||||||
1. Passa badge inesistente (`0006478281`)
|
1. Passa badge inesistente (`0006478281`)
|
||||||
2. Aspetta 30 secondi
|
2. Aspetta 30 secondi
|
||||||
3. **Atteso:** Torna a "In attesa partecipante", barra countdown visiva ✅
|
3. **Atteso:** Torna a "In attesa partecipante", barra countdown visiva ✅
|
||||||
|
|
||||||
|
### T16b: Pulsante Annulla su badge non trovato
|
||||||
|
|
||||||
|
1. Passa badge inesistente (`0006478281`)
|
||||||
|
2. Clicca "Annulla"
|
||||||
|
3. **Atteso:** Torna immediatamente a "In attesa partecipante" ✅
|
||||||
|
|
||||||
|
### T16c: Nuovo badge durante errore "non trovato"
|
||||||
|
|
||||||
|
1. Passa badge inesistente (`0006478281`)
|
||||||
|
2. Mentre è visibile l'errore, passa badge esistente (`ò0008988288_`)
|
||||||
|
3. **Atteso:** Errore sparisce, mostra anagrafica Marco Bianchi ✅
|
||||||
|
|
||||||
### T17: Logout manuale
|
### T17: Logout manuale
|
||||||
|
|
||||||
1. Clicca "Esci" nell'header
|
1. Clicca "Esci" nell'header
|
||||||
2. **Atteso:** Torna a "Passa badge validatore" ✅
|
2. **Atteso:** Torna a "Passa badge validatore" ✅
|
||||||
|
|
||||||
### T18: Sessione invalidata al riavvio server
|
### T18: Sessione invalidata al riavvio server
|
||||||
|
|
||||||
1. Login come validatore, vai al varco attivo
|
1. Login come validatore, vai al varco attivo
|
||||||
2. **Ferma il server** (Ctrl+C)
|
2. **Ferma il server** (Ctrl+C)
|
||||||
3. **Riavvia il server** (`./dev.sh server`)
|
3. **Riavvia il server** (`./dev.sh server`)
|
||||||
@@ -117,10 +148,12 @@
|
|||||||
## 🔧 Test Debug
|
## 🔧 Test Debug
|
||||||
|
|
||||||
### T19: Pagina debug accessibile
|
### T19: Pagina debug accessibile
|
||||||
|
|
||||||
1. Vai a `http://localhost:8000/debug`
|
1. Vai a `http://localhost:8000/debug`
|
||||||
2. **Atteso:** Pagina con log tasti, stato scanner, buffer corrente ✅
|
2. **Atteso:** Pagina con log tasti, stato scanner, buffer corrente ✅
|
||||||
|
|
||||||
### T20: Log tasti funzionante
|
### T20: Log tasti funzionante
|
||||||
|
|
||||||
1. Nella pagina debug, premi tasti sulla tastiera
|
1. Nella pagina debug, premi tasti sulla tastiera
|
||||||
2. **Atteso:** Tasti appaiono nella lista eventi ✅
|
2. **Atteso:** Tasti appaiono nella lista eventi ✅
|
||||||
|
|
||||||
@@ -129,10 +162,12 @@
|
|||||||
## 📱 Test UI/UX
|
## 📱 Test UI/UX
|
||||||
|
|
||||||
### T21: NumLock banner su desktop
|
### T21: NumLock banner su desktop
|
||||||
|
|
||||||
1. Su browser desktop, verifica schermata "In attesa partecipante"
|
1. Su browser desktop, verifica schermata "In attesa partecipante"
|
||||||
2. **Atteso:** Banner giallo con stato NumLock visibile ✅
|
2. **Atteso:** Banner giallo con stato NumLock visibile ✅
|
||||||
|
|
||||||
### T22: Occhio toggle password
|
### T22: Occhio toggle password
|
||||||
|
|
||||||
1. Nella schermata password validatore
|
1. Nella schermata password validatore
|
||||||
2. Clicca l'icona occhio
|
2. Clicca l'icona occhio
|
||||||
3. **Atteso:** Password visibile/nascosta ✅
|
3. **Atteso:** Password visibile/nascosta ✅
|
||||||
@@ -141,18 +176,21 @@
|
|||||||
|
|
||||||
## Badge di Test
|
## Badge di Test
|
||||||
|
|
||||||
| Formato RFID | Badge | Nome | Risultato Atteso |
|
| Formato RFID | Badge | Nome | Ruolo | Risultato Atteso |
|
||||||
|----------------------|------------|----------------|----------------------|
|
|----------------|------------|----------------|-----------|----------------------|
|
||||||
| `ò0008988288_` | 0008988288 | Marco Bianchi | ✅ Ammesso |
|
| `ò0008988288_` | 0008988288 | Marco Bianchi | Convocato | ✅ Ammesso |
|
||||||
| `ò0007399575_` | 0007399575 | Laura Rossi | ✅ Ammessa |
|
| `ò0007399575_` | 0007399575 | Laura Rossi | Invitato | ✅ Ammessa |
|
||||||
| `ò0000514162_` | 0000514162 | Giuseppe Verdi | ❌ Non ammesso |
|
| `ò0000514162_` | 0000514162 | Giuseppe Verdi | Tecnico | ❌ Non ammesso |
|
||||||
| `ò0006478281_` | 0006478281 | - | ⚠️ Non trovato (404) |
|
| `ò0006478281_` | 0006478281 | - | - | ⚠️ Non trovato (404) |
|
||||||
|
|
||||||
|
**Ruoli possibili:** Convocato, Invitato, Tecnico, Staff
|
||||||
|
|
||||||
**Password validatore:** `focolari`
|
**Password validatore:** `focolari`
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Note
|
## Note
|
||||||
|
|
||||||
- I badge sono stringhe, gli zeri iniziali sono significativi
|
- I badge sono stringhe, gli zeri iniziali sono significativi
|
||||||
- Il pattern RFID italiano usa `ò` come start e `_` come end (+ Enter)
|
- Il pattern RFID italiano usa `ò` come start e `_` come end (+ Enter)
|
||||||
- Il pattern US usa `;` come start e `?` come end (+ Enter)
|
- Il pattern US usa `;` come start e `?` come end (+ Enter)
|
||||||
|
|||||||
@@ -147,7 +147,8 @@ VotoFocolari/
|
|||||||
|
|
||||||
5. **Badge Duplicati**: Se lo stesso badge viene passato più volte di seguito, viene ignorato (no ricaricamento).
|
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.
|
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.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|||||||
@@ -50,8 +50,8 @@ backend-mock/
|
|||||||
|
|
||||||
- [x] Password validatore (solo password, badge gestito dal frontend)
|
- [x] Password validatore (solo password, badge gestito dal frontend)
|
||||||
- [x] Lista utenti mock con **badge reali**:
|
- [x] Lista utenti mock con **badge reali**:
|
||||||
- `0008988288` - Marco Bianchi (Votante, ammesso)
|
- `0008988288` - Marco Bianchi (Convocato, ammesso)
|
||||||
- `0007399575` - Laura Rossi (Votante, ammessa)
|
- `0007399575` - Laura Rossi (Invitato, ammessa)
|
||||||
- `0000514162` - Giuseppe Verdi (Tecnico, NON ammesso)
|
- `0000514162` - Giuseppe Verdi (Tecnico, NON ammesso)
|
||||||
- `0006478281` - **NON nel DB** (per test "non trovato")
|
- `0006478281` - **NON nel DB** (per test "non trovato")
|
||||||
- [x] Estrarre dati in file JSON separato
|
- [x] Estrarre dati in file JSON separato
|
||||||
@@ -60,9 +60,20 @@ backend-mock/
|
|||||||
|
|
||||||
**Nota:** I messaggi di benvenuto multilingua sono gestiti dal frontend con carosello.
|
**Nota:** I messaggi di benvenuto multilingua sono gestiti dal frontend con carosello.
|
||||||
|
|
||||||
**TODO (da concordare con committenti):**
|
**Ruoli Supportati:**
|
||||||
|
| Ruolo | Descrizione |
|
||||||
|
|-------|-------------|
|
||||||
|
| `Convocato` | Con diritto di voto |
|
||||||
|
| `Invitato` | Senza diritto di voto |
|
||||||
|
| `Tecnico` | Staff tecnico |
|
||||||
|
| `Staff` | Personale organizzativo |
|
||||||
|
|
||||||
- Valutare se `login-validate` debba ricevere e verificare anche il badge del validatore
|
**Logica Login-Validate:**
|
||||||
|
|
||||||
|
- Il backend **DEVE** verificare la password
|
||||||
|
- Il backend **PUÒ** opzionalmente verificare anche il badge (whitelist validatori)
|
||||||
|
- Risposta 401 = password errata
|
||||||
|
- Risposta 403 = badge non in whitelist (se implementato)
|
||||||
|
|
||||||
### 4. Routes API (`api/routes.py`)
|
### 4. Routes API (`api/routes.py`)
|
||||||
|
|
||||||
@@ -175,10 +186,12 @@ curl -X POST http://localhost:8000/entry-request \
|
|||||||
Tutti i task sono stati implementati e testati.
|
Tutti i task sono stati implementati e testati.
|
||||||
|
|
||||||
### 📄 Documentazione API
|
### 📄 Documentazione API
|
||||||
|
|
||||||
Per le specifiche complete da implementare nel backend reale, vedere:
|
Per le specifiche complete da implementare nel backend reale, vedere:
|
||||||
**`backend-mock/API_SPECIFICATION.md`**
|
**`backend-mock/API_SPECIFICATION.md`**
|
||||||
|
|
||||||
Questo documento contiene:
|
Questo documento contiene:
|
||||||
|
|
||||||
- Descrizione completa di tutti gli endpoint
|
- Descrizione completa di tutti gli endpoint
|
||||||
- Schema request/response JSON
|
- Schema request/response JSON
|
||||||
- Codici di errore e gestione
|
- Codici di errore e gestione
|
||||||
|
|||||||
@@ -81,9 +81,14 @@ Ottimizzata per tablet in orizzontale.
|
|||||||
- [x] `ActiveGateScreen.tsx` - varco attivo:
|
- [x] `ActiveGateScreen.tsx` - varco attivo:
|
||||||
- [x] Card utente (layout largo per tablet)
|
- [x] Card utente (layout largo per tablet)
|
||||||
- [x] **Schermata "badge non trovato"** con countdown barra visiva (30s)
|
- [x] **Schermata "badge non trovato"** con countdown barra visiva (30s)
|
||||||
|
- [x] **Pulsante "Annulla" nella schermata badge non trovato**
|
||||||
|
- [x] **Badge diverso durante errore "non trovato" → ricarica nuovo utente/errore**
|
||||||
- [x] **Notifica badge validatore ignorato**
|
- [x] **Notifica badge validatore ignorato**
|
||||||
- [x] NumLockBanner
|
- [x] NumLockBanner
|
||||||
- [x] `SuccessModal.tsx` - conferma ingresso con carosello (**durata aumentata 8s**)
|
- [x] `SuccessModal.tsx` - conferma ingresso con carosello:
|
||||||
|
- [x] **Carosello fullwidth** (nessun troncamento testo)
|
||||||
|
- [x] **Scorrimento lento** (più rilassato, meno ansioso)
|
||||||
|
- [x] **Durata aumentata 8s**
|
||||||
- [x] `ErrorModal.tsx` - errore fullscreen
|
- [x] `ErrorModal.tsx` - errore fullscreen
|
||||||
- [x] `DebugScreen.tsx` - pagina diagnostica RFID
|
- [x] `DebugScreen.tsx` - pagina diagnostica RFID
|
||||||
|
|
||||||
@@ -111,9 +116,10 @@ Ottimizzata per tablet in orizzontale.
|
|||||||
- [x] Componente `WelcomeCarousel.tsx`
|
- [x] Componente `WelcomeCarousel.tsx`
|
||||||
- [x] 10 lingue supportate
|
- [x] 10 lingue supportate
|
||||||
- [x] **Animazione smooth sliding** (slide up/down)
|
- [x] **Animazione smooth sliding** (slide up/down)
|
||||||
- [x] Scorrimento automatico (intervallo calcolato dinamicamente)
|
- [x] Scorrimento automatico lento (più rilassato)
|
||||||
- [x] Modale fullscreen verde
|
- [x] Modale fullscreen verde
|
||||||
- [x] **Durata totale: 8 secondi** (più rilassato)
|
- [x] **Contenitore fullwidth** (nessun troncamento testo lungo)
|
||||||
|
- [x] **Durata totale: 8 secondi**
|
||||||
|
|
||||||
### 10. Debug & Diagnostica
|
### 10. Debug & Diagnostica
|
||||||
|
|
||||||
@@ -131,9 +137,9 @@ Ottimizzata per tablet in orizzontale.
|
|||||||
## Badge di Test
|
## Badge di Test
|
||||||
|
|
||||||
| Badge | Nome | Ruolo | Ammesso |
|
| Badge | Nome | Ruolo | Ammesso |
|
||||||
|--------------|----------------|---------|---------------|
|
|--------------|----------------|-----------|---------------|
|
||||||
| `0008988288` | Marco Bianchi | Votante | ✅ Sì |
|
| `0008988288` | Marco Bianchi | Convocato | ✅ Sì |
|
||||||
| `0007399575` | Laura Rossi | Votante | ✅ Sì |
|
| `0007399575` | Laura Rossi | Invitato | ✅ Sì |
|
||||||
| `0000514162` | Giuseppe Verdi | Tecnico | ❌ No |
|
| `0000514162` | Giuseppe Verdi | Tecnico | ❌ No |
|
||||||
| `0006478281` | - | - | ⚠️ Non nel DB |
|
| `0006478281` | - | - | ⚠️ Non nel DB |
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
# 📄 Specifiche API Backend - Focolari Voting System
|
# 📄 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.
|
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
|
**Versione:** 1.0
|
||||||
**Data:** Gennaio 2026
|
**Data:** Gennaio 2026
|
||||||
@@ -10,12 +11,15 @@ Questo documento descrive le specifiche che il backend reale deve implementare p
|
|||||||
## 🌐 Configurazione Server
|
## 🌐 Configurazione Server
|
||||||
|
|
||||||
### CORS
|
### CORS
|
||||||
|
|
||||||
Il server deve abilitare CORS con le seguenti impostazioni:
|
Il server deve abilitare CORS con le seguenti impostazioni:
|
||||||
|
|
||||||
- **Origins:** `*` (o lista specifica di domini autorizzati)
|
- **Origins:** `*` (o lista specifica di domini autorizzati)
|
||||||
- **Methods:** `GET, POST, OPTIONS`
|
- **Methods:** `GET, POST, OPTIONS`
|
||||||
- **Headers:** `Content-Type, Authorization`
|
- **Headers:** `Content-Type, Authorization`
|
||||||
|
|
||||||
### Serving Frontend
|
### Serving Frontend
|
||||||
|
|
||||||
Il backend dovrebbe servire il frontend buildato (file statici) dalla root `/`.
|
Il backend dovrebbe servire il frontend buildato (file statici) dalla root `/`.
|
||||||
Questo permette di avere un unico endpoint per frontend e API.
|
Questo permette di avere un unico endpoint per frontend e API.
|
||||||
|
|
||||||
@@ -24,15 +28,18 @@ Questo permette di avere un unico endpoint per frontend e API.
|
|||||||
## 🔑 Meccanismo di Invalidazione Sessioni
|
## 🔑 Meccanismo di Invalidazione Sessioni
|
||||||
|
|
||||||
### Server Start Time
|
### Server Start Time
|
||||||
|
|
||||||
All'avvio, il server deve generare un **timestamp univoco** (es. Unix timestamp in secondi).
|
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.
|
Questo valore viene restituito nell'endpoint `/info-room` e serve al frontend per invalidare sessioni obsolete.
|
||||||
|
|
||||||
**Comportamento:**
|
**Comportamento:**
|
||||||
|
|
||||||
1. Il frontend salva `server_start_time` nella sessione locale
|
1. Il frontend salva `server_start_time` nella sessione locale
|
||||||
2. Al caricamento successivo, confronta il valore salvato con quello attuale
|
2. Al caricamento successivo, confronta il valore salvato con quello attuale
|
||||||
3. Se differiscono, la sessione viene invalidata (il server è stato riavviato)
|
3. Se differiscono, la sessione viene invalidata (il server è stato riavviato)
|
||||||
|
|
||||||
Questo garantisce che:
|
Questo garantisce che:
|
||||||
|
|
||||||
- Un riavvio del server forza il re-login di tutti i validatori
|
- Un riavvio del server forza il re-login di tutti i validatori
|
||||||
- Sessioni zombie non rimangono attive dopo manutenzione
|
- Sessioni zombie non rimangono attive dopo manutenzione
|
||||||
|
|
||||||
@@ -45,6 +52,7 @@ Questo garantisce che:
|
|||||||
Restituisce informazioni sulla sala/meeting corrente e lo stato del server.
|
Restituisce informazioni sulla sala/meeting corrente e lo stato del server.
|
||||||
|
|
||||||
#### Response `200 OK`
|
#### Response `200 OK`
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"room_name": "Sala Assemblea",
|
"room_name": "Sala Assemblea",
|
||||||
@@ -54,7 +62,7 @@ Restituisce informazioni sulla sala/meeting corrente e lo stato del server.
|
|||||||
```
|
```
|
||||||
|
|
||||||
| Campo | Tipo | Descrizione |
|
| Campo | Tipo | Descrizione |
|
||||||
|-------|------|-------------|
|
|---------------------|---------|---------------------------------------------------------------|
|
||||||
| `room_name` | string | Nome della sala visualizzato nell'header |
|
| `room_name` | string | Nome della sala visualizzato nell'header |
|
||||||
| `meeting_id` | string | Identificativo del meeting/votazione |
|
| `meeting_id` | string | Identificativo del meeting/votazione |
|
||||||
| `server_start_time` | integer | Unix timestamp dell'avvio server (per invalidazione sessioni) |
|
| `server_start_time` | integer | Unix timestamp dell'avvio server (per invalidazione sessioni) |
|
||||||
@@ -63,9 +71,11 @@ Restituisce informazioni sulla sala/meeting corrente e lo stato del server.
|
|||||||
|
|
||||||
### 2. `POST /login-validate`
|
### 2. `POST /login-validate`
|
||||||
|
|
||||||
Verifica le credenziali del validatore. **IMPORTANTE:** Qualsiasi badge può diventare un badge validatore se la password è corretta.
|
Verifica le credenziali del validatore. **IMPORTANTE:** Qualsiasi badge può diventare un badge validatore se la password
|
||||||
|
è corretta.
|
||||||
|
|
||||||
#### Request Body
|
#### Request Body
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"badge": "0007399575",
|
"badge": "0007399575",
|
||||||
@@ -74,11 +84,19 @@ Verifica le credenziali del validatore. **IMPORTANTE:** Qualsiasi badge può div
|
|||||||
```
|
```
|
||||||
|
|
||||||
| Campo | Tipo | Descrizione |
|
| Campo | Tipo | Descrizione |
|
||||||
|-------|------|-------------|
|
|------------|--------|-------------------------------------------------------------------------------------|
|
||||||
| `badge` | string | Codice badge scansionato (numerico, senza sentinel) |
|
| `badge` | string | Codice badge scansionato (numerico, senza sentinel) - **opzionale per validazione** |
|
||||||
| `password` | string | Password inserita dall'utente |
|
| `password` | string | Password inserita dall'utente |
|
||||||
|
|
||||||
|
**Logica di Validazione:**
|
||||||
|
|
||||||
|
- Il backend **DEVE** verificare la password
|
||||||
|
- Il backend **PUÒ** opzionalmente verificare anche il badge (lista whitelist di badge abilitati come validatori)
|
||||||
|
- Se verifica solo password: qualsiasi badge può diventare validatore con la password corretta
|
||||||
|
- Se verifica anche badge: solo badge nella whitelist possono diventare validatori
|
||||||
|
|
||||||
#### Response `200 OK` (Successo)
|
#### Response `200 OK` (Successo)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"success": true,
|
"success": true,
|
||||||
@@ -87,14 +105,26 @@ Verifica le credenziali del validatore. **IMPORTANTE:** Qualsiasi badge può div
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Response `401 Unauthorized` (Errore)
|
#### Response `401 Unauthorized` (Password errata)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detail": "Password non valida"
|
"detail": "Password non valida"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### Response `403 Forbidden` (Badge non autorizzato - se implementato controllo badge)
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"detail": "Badge non autorizzato come validatore"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Note:** Il frontend gestisce entrambi i codici di errore (401 e 403) mostrando il messaggio ricevuto nel campo
|
||||||
|
`detail`.
|
||||||
**Note:**
|
**Note:**
|
||||||
|
|
||||||
- La password è l'unico fattore di autenticazione
|
- La password è l'unico fattore di autenticazione
|
||||||
- Il badge viene memorizzato dal frontend come "badge validatore" per conferme successive
|
- Il badge viene memorizzato dal frontend come "badge validatore" per conferme successive
|
||||||
- Il token è opzionale (per future implementazioni di autenticazione JWT)
|
- Il token è opzionale (per future implementazioni di autenticazione JWT)
|
||||||
@@ -106,13 +136,23 @@ Verifica le credenziali del validatore. **IMPORTANTE:** Qualsiasi badge può div
|
|||||||
Recupera i dati anagrafici di un utente dato il suo codice badge.
|
Recupera i dati anagrafici di un utente dato il suo codice badge.
|
||||||
|
|
||||||
#### Path Parameters
|
#### Path Parameters
|
||||||
|
|
||||||
- `badge_code`: Codice badge (stringa, es. "0008988288")
|
- `badge_code`: Codice badge (stringa, es. "0008988288")
|
||||||
|
|
||||||
**IMPORTANTE - Confronto Badge:**
|
**IMPORTANTE - Confronto Badge:**
|
||||||
Il badge è una **stringa**, non un numero. Va confrontato **esattamente** carattere per carattere.
|
Il badge è una **stringa**, non un numero. Va confrontato **esattamente** carattere per carattere.
|
||||||
Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **diversi**.
|
Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **diversi**.
|
||||||
|
|
||||||
|
**Ruoli Ammessi:**
|
||||||
|
| Ruolo | Descrizione |
|
||||||
|
|-------|-------------|
|
||||||
|
| `Convocato` | Con diritto di voto |
|
||||||
|
| `Invitato` | Senza diritto di voto |
|
||||||
|
| `Tecnico` | Staff tecnico |
|
||||||
|
| `Staff` | Personale organizzativo |
|
||||||
|
|
||||||
#### Response `200 OK` (Utente trovato e ammesso)
|
#### Response `200 OK` (Utente trovato e ammesso)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"badge_code": "0008988288",
|
"badge_code": "0008988288",
|
||||||
@@ -120,11 +160,18 @@ Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **
|
|||||||
"cognome": "Bianchi",
|
"cognome": "Bianchi",
|
||||||
"url_foto": "https://example.com/foto.jpg",
|
"url_foto": "https://example.com/foto.jpg",
|
||||||
"ruolo": "Votante",
|
"ruolo": "Votante",
|
||||||
"ammesso": true # <-- utente ammesso all'ingresso
|
"ammesso": true
|
||||||
|
#
|
||||||
|
<--
|
||||||
|
utente
|
||||||
|
ammesso
|
||||||
|
all
|
||||||
|
'ingresso
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Response `200 OK` (Utente trovato ma NON ammesso)
|
#### Response `200 OK` (Utente trovato ma NON ammesso)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"badge_code": "0000514162",
|
"badge_code": "0000514162",
|
||||||
@@ -132,12 +179,20 @@ Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **
|
|||||||
"cognome": "Verdi",
|
"cognome": "Verdi",
|
||||||
"url_foto": "https://example.com/foto.jpg",
|
"url_foto": "https://example.com/foto.jpg",
|
||||||
"ruolo": "Tecnico",
|
"ruolo": "Tecnico",
|
||||||
"ammesso": false, #<-- utente NON ammesso all'ingresso
|
"ammesso": false,
|
||||||
|
#
|
||||||
|
<--
|
||||||
|
utente
|
||||||
|
NON
|
||||||
|
ammesso
|
||||||
|
all
|
||||||
|
'ingresso
|
||||||
"warning": "Utente non ammesso all'ingresso"
|
"warning": "Utente non ammesso all'ingresso"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
#### Response `404 Not Found` (Utente non trovato)
|
#### Response `404 Not Found` (Utente non trovato)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detail": "Badge non trovato nel sistema"
|
"detail": "Badge non trovato nel sistema"
|
||||||
@@ -145,7 +200,7 @@ Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **
|
|||||||
```
|
```
|
||||||
|
|
||||||
| Campo Response | Tipo | Descrizione |
|
| Campo Response | Tipo | Descrizione |
|
||||||
|----------------|------|-------------|
|
|----------------|---------|--------------------------------------------------------|
|
||||||
| `badge_code` | string | Codice badge |
|
| `badge_code` | string | Codice badge |
|
||||||
| `nome` | string | Nome dell'utente |
|
| `nome` | string | Nome dell'utente |
|
||||||
| `cognome` | string | Cognome dell'utente |
|
| `cognome` | string | Cognome dell'utente |
|
||||||
@@ -161,6 +216,7 @@ Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **
|
|||||||
Registra l'ingresso di un utente. Richiede conferma del validatore.
|
Registra l'ingresso di un utente. Richiede conferma del validatore.
|
||||||
|
|
||||||
#### Request Body
|
#### Request Body
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"user_badge": "0008988288",
|
"user_badge": "0008988288",
|
||||||
@@ -169,11 +225,12 @@ Registra l'ingresso di un utente. Richiede conferma del validatore.
|
|||||||
```
|
```
|
||||||
|
|
||||||
| Campo | Tipo | Descrizione |
|
| Campo | Tipo | Descrizione |
|
||||||
|-------|------|-------------|
|
|----------------------|--------|---------------------------------------|
|
||||||
| `user_badge` | string | Badge dell'utente che sta entrando |
|
| `user_badge` | string | Badge dell'utente che sta entrando |
|
||||||
| `validator_password` | string | Password del validatore (ri-verifica) |
|
| `validator_password` | string | Password del validatore (ri-verifica) |
|
||||||
|
|
||||||
#### Response `200 OK` (Successo)
|
#### Response `200 OK` (Successo)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"success": true,
|
"success": true,
|
||||||
@@ -182,6 +239,7 @@ Registra l'ingresso di un utente. Richiede conferma del validatore.
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### Response `401 Unauthorized` (Password errata)
|
#### Response `401 Unauthorized` (Password errata)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detail": "Password validatore non valida"
|
"detail": "Password validatore non valida"
|
||||||
@@ -189,6 +247,7 @@ Registra l'ingresso di un utente. Richiede conferma del validatore.
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### Response `403 Forbidden` (Utente non ammesso)
|
#### Response `403 Forbidden` (Utente non ammesso)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detail": "Utente non autorizzato all'ingresso"
|
"detail": "Utente non autorizzato all'ingresso"
|
||||||
@@ -196,6 +255,7 @@ Registra l'ingresso di un utente. Richiede conferma del validatore.
|
|||||||
```
|
```
|
||||||
|
|
||||||
#### Response `404 Not Found` (Badge non trovato)
|
#### Response `404 Not Found` (Badge non trovato)
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detail": "Badge utente non trovato"
|
"detail": "Badge utente non trovato"
|
||||||
@@ -203,7 +263,9 @@ Registra l'ingresso di un utente. Richiede conferma del validatore.
|
|||||||
```
|
```
|
||||||
|
|
||||||
**IMPORTANTE - Sicurezza:**
|
**IMPORTANTE - Sicurezza:**
|
||||||
Anche se il frontend non dovrebbe permettere di inviare entry-request per utenti non ammessi, il backend **DEVE** sempre verificare:
|
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
|
1. Che la password validatore sia corretta
|
||||||
2. Che l'utente esista
|
2. Che l'utente esista
|
||||||
3. Che l'utente sia ammesso (`ammesso: true`)
|
3. Che l'utente sia ammesso (`ammesso: true`)
|
||||||
@@ -215,6 +277,7 @@ Non fidarsi mai del frontend per la validazione!
|
|||||||
## 🔒 Considerazioni di Sicurezza
|
## 🔒 Considerazioni di Sicurezza
|
||||||
|
|
||||||
### Validazione Lato Backend
|
### Validazione Lato Backend
|
||||||
|
|
||||||
Il backend deve **sempre** eseguire tutte le validazioni, indipendentemente da cosa fa il frontend:
|
Il backend deve **sempre** eseguire tutte le validazioni, indipendentemente da cosa fa il frontend:
|
||||||
|
|
||||||
1. **Login:** Verificare che la password sia corretta
|
1. **Login:** Verificare che la password sia corretta
|
||||||
@@ -225,13 +288,17 @@ Il backend deve **sempre** eseguire tutte le validazioni, indipendentemente da c
|
|||||||
- Loggare l'operazione per audit
|
- Loggare l'operazione per audit
|
||||||
|
|
||||||
### Logging e Audit
|
### Logging e Audit
|
||||||
|
|
||||||
Si raccomanda di loggare:
|
Si raccomanda di loggare:
|
||||||
|
|
||||||
- Ogni tentativo di login (successo/fallimento)
|
- Ogni tentativo di login (successo/fallimento)
|
||||||
- Ogni registrazione di ingresso
|
- Ogni registrazione di ingresso
|
||||||
- Badge non trovati (potenziale tentativo di accesso non autorizzato)
|
- Badge non trovati (potenziale tentativo di accesso non autorizzato)
|
||||||
|
|
||||||
### Protezione Rate Limiting
|
### Protezione Rate Limiting
|
||||||
|
|
||||||
Implementare rate limiting su:
|
Implementare rate limiting su:
|
||||||
|
|
||||||
- `/login-validate`: max 5 tentativi/minuto per IP
|
- `/login-validate`: max 5 tentativi/minuto per IP
|
||||||
- `/entry-request`: max 30 richieste/minuto per IP
|
- `/entry-request`: max 30 richieste/minuto per IP
|
||||||
|
|
||||||
@@ -240,8 +307,10 @@ Implementare rate limiting su:
|
|||||||
## 📊 Struttura Dati Suggerita
|
## 📊 Struttura Dati Suggerita
|
||||||
|
|
||||||
### Database Utenti
|
### Database Utenti
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE TABLE users (
|
CREATE TABLE users
|
||||||
|
(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
badge_code VARCHAR(20) UNIQUE NOT NULL,
|
badge_code VARCHAR(20) UNIQUE NOT NULL,
|
||||||
nome VARCHAR(100) NOT NULL,
|
nome VARCHAR(100) NOT NULL,
|
||||||
@@ -253,12 +322,14 @@ CREATE TABLE users (
|
|||||||
updated_at TIMESTAMP DEFAULT NOW()
|
updated_at TIMESTAMP DEFAULT NOW()
|
||||||
);
|
);
|
||||||
|
|
||||||
CREATE INDEX idx_badge_code ON users(badge_code);
|
CREATE INDEX idx_badge_code ON users (badge_code);
|
||||||
```
|
```
|
||||||
|
|
||||||
### Tabella Accessi (Audit Log)
|
### Tabella Accessi (Audit Log)
|
||||||
|
|
||||||
```sql
|
```sql
|
||||||
CREATE TABLE access_log (
|
CREATE TABLE access_log
|
||||||
|
(
|
||||||
id SERIAL PRIMARY KEY,
|
id SERIAL PRIMARY KEY,
|
||||||
user_badge VARCHAR(20) NOT NULL,
|
user_badge VARCHAR(20) NOT NULL,
|
||||||
validator_badge VARCHAR(20) NOT NULL,
|
validator_badge VARCHAR(20) NOT NULL,
|
||||||
@@ -286,9 +357,10 @@ CREATE TABLE access_log (
|
|||||||
9. **Entry badge inesistente** → 404 Not Found
|
9. **Entry badge inesistente** → 404 Not Found
|
||||||
|
|
||||||
### Badge di Test (Mock)
|
### Badge di Test (Mock)
|
||||||
|
|
||||||
```
|
```
|
||||||
0008988288 - Marco Bianchi (Votante, ammesso)
|
0008988288 - Marco Bianchi (Convocato, ammesso)
|
||||||
0007399575 - Laura Rossi (Votante, ammessa)
|
0007399575 - Laura Rossi (Invitato, ammessa)
|
||||||
0000514162 - Giuseppe Verdi (Tecnico, NON ammesso)
|
0000514162 - Giuseppe Verdi (Tecnico, NON ammesso)
|
||||||
0006478281 - NON nel database (per test 404)
|
0006478281 - NON nel database (per test 404)
|
||||||
```
|
```
|
||||||
@@ -298,9 +370,11 @@ CREATE TABLE access_log (
|
|||||||
## 📝 Note Implementative
|
## 📝 Note Implementative
|
||||||
|
|
||||||
### Content-Type
|
### Content-Type
|
||||||
|
|
||||||
Tutte le richieste e risposte usano `application/json`.
|
Tutte le richieste e risposte usano `application/json`.
|
||||||
|
|
||||||
### Codici Errore HTTP
|
### Codici Errore HTTP
|
||||||
|
|
||||||
- `200`: Successo
|
- `200`: Successo
|
||||||
- `401`: Non autorizzato (password errata)
|
- `401`: Non autorizzato (password errata)
|
||||||
- `403`: Vietato (utente non ammesso)
|
- `403`: Vietato (utente non ammesso)
|
||||||
@@ -308,7 +382,9 @@ Tutte le richieste e risposte usano `application/json`.
|
|||||||
- `500`: Errore interno server
|
- `500`: Errore interno server
|
||||||
|
|
||||||
### Formato Errori
|
### Formato Errori
|
||||||
|
|
||||||
Tutti gli errori devono restituire un JSON con campo `detail`:
|
Tutti gli errori devono restituire un JSON con campo `detail`:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"detail": "Messaggio di errore descrittivo"
|
"detail": "Messaggio di errore descrittivo"
|
||||||
@@ -320,6 +396,7 @@ Tutti gli errori devono restituire un JSON con campo `detail`:
|
|||||||
## 🔄 Changelog
|
## 🔄 Changelog
|
||||||
|
|
||||||
### v1.0 (Gennaio 2026)
|
### v1.0 (Gennaio 2026)
|
||||||
|
|
||||||
- Specifica iniziale
|
- Specifica iniziale
|
||||||
- Endpoint: `/info-room`, `/login-validate`, `/anagrafica/{badge}`, `/entry-request`
|
- Endpoint: `/info-room`, `/login-validate`, `/anagrafica/{badge}`, `/entry-request`
|
||||||
- Meccanismo invalidazione sessioni con `server_start_time`
|
- Meccanismo invalidazione sessioni con `server_start_time`
|
||||||
|
|||||||
@@ -5,12 +5,20 @@
|
|||||||
"meeting_id": "VOT-2024"
|
"meeting_id": "VOT-2024"
|
||||||
},
|
},
|
||||||
"users": [
|
"users": [
|
||||||
|
{
|
||||||
|
"badge_code": "0008988288",
|
||||||
|
"nome": "Marco",
|
||||||
|
"cognome": "Bianchi",
|
||||||
|
"url_foto": "https://randomuser.me/api/portraits/men/1.jpg",
|
||||||
|
"ruolo": "Convocato",
|
||||||
|
"ammesso": true
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"badge_code": "0007399575",
|
"badge_code": "0007399575",
|
||||||
"nome": "Laura",
|
"nome": "Laura",
|
||||||
"cognome": "Rossi",
|
"cognome": "Rossi",
|
||||||
"url_foto": "https://randomuser.me/api/portraits/women/2.jpg",
|
"url_foto": "https://randomuser.me/api/portraits/women/2.jpg",
|
||||||
"ruolo": "Votante",
|
"ruolo": "Invitato",
|
||||||
"ammesso": true
|
"ammesso": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -10,7 +10,7 @@
|
|||||||
"nome": "Test",
|
"nome": "Test",
|
||||||
"cognome": "Ammesso",
|
"cognome": "Ammesso",
|
||||||
"url_foto": "https://randomuser.me/api/portraits/lego/1.jpg",
|
"url_foto": "https://randomuser.me/api/portraits/lego/1.jpg",
|
||||||
"ruolo": "Votante",
|
"ruolo": "Convocato",
|
||||||
"ammesso": true
|
"ammesso": true
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -18,7 +18,7 @@
|
|||||||
"nome": "Test",
|
"nome": "Test",
|
||||||
"cognome": "NonAmmesso",
|
"cognome": "NonAmmesso",
|
||||||
"url_foto": "https://randomuser.me/api/portraits/lego/2.jpg",
|
"url_foto": "https://randomuser.me/api/portraits/lego/2.jpg",
|
||||||
"ruolo": "Ospite",
|
"ruolo": "Invitato",
|
||||||
"ammesso": false
|
"ammesso": false
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
8
dev.sh
8
dev.sh
@@ -215,10 +215,16 @@ cmd_help() {
|
|||||||
echo "Esempi:"
|
echo "Esempi:"
|
||||||
echo " ./dev.sh install # Setup iniziale"
|
echo " ./dev.sh install # Setup iniziale"
|
||||||
echo " ./dev.sh dev # Sviluppo (hot reload)"
|
echo " ./dev.sh dev # Sviluppo (hot reload)"
|
||||||
echo " ./dev.sh server # Produzione locale"
|
echo " ./dev.sh server # Produzione locale (accessibile da rete)"
|
||||||
echo " ./dev.sh server -p 9000 # Server su porta 9000"
|
echo " ./dev.sh server -p 9000 # Server su porta 9000"
|
||||||
|
echo " ./dev.sh server --host 127.0.0.1 # Solo localhost (non accessibile da rete)"
|
||||||
echo " ./dev.sh server -d data/users_test.json # Con dataset test"
|
echo " ./dev.sh server -d data/users_test.json # Con dataset test"
|
||||||
echo ""
|
echo ""
|
||||||
|
echo "Note:"
|
||||||
|
echo " - Il server usa --host 0.0.0.0 di default (accessibile da tutta la rete locale)"
|
||||||
|
echo " - Per accedere da altri dispositivi: usa l'IP della macchina (es. 192.168.1.230:8000)"
|
||||||
|
echo " - Verifica che il firewall non blocchi la porta"
|
||||||
|
echo ""
|
||||||
}
|
}
|
||||||
|
|
||||||
# Main
|
# Main
|
||||||
|
|||||||
@@ -30,11 +30,11 @@ interface WelcomeCarouselProps {
|
|||||||
paused?: boolean;
|
paused?: boolean;
|
||||||
/** Nome dell'utente da visualizzare (opzionale) */
|
/** Nome dell'utente da visualizzare (opzionale) */
|
||||||
userName?: string;
|
userName?: string;
|
||||||
/** Intervallo tra i messaggi in ms (default 800) */
|
/** Intervallo tra i messaggi in ms (default 1200 - più lento per effetto rilassante) */
|
||||||
intervalMs?: number;
|
intervalMs?: number;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function WelcomeCarousel({paused = false, userName, intervalMs = 800}: WelcomeCarouselProps) {
|
export function WelcomeCarousel({paused = false, userName, intervalMs = 1200}: WelcomeCarouselProps) {
|
||||||
const [currentIndex, setCurrentIndex] = useState(0);
|
const [currentIndex, setCurrentIndex] = useState(0);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -50,15 +50,15 @@ export function WelcomeCarousel({paused = false, userName, intervalMs = 800}: We
|
|||||||
const currentMessage = WELCOME_MESSAGES[currentIndex];
|
const currentMessage = WELCOME_MESSAGES[currentIndex];
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="flex flex-col items-center justify-center text-center w-full max-w-4xl mx-auto px-4">
|
<div className="flex flex-col items-center justify-center text-center w-full px-4">
|
||||||
{/* Container con overflow hidden per animazione slide - altezza maggiore per testi lunghi */}
|
{/* Container fullwidth con overflow hidden per animazione slide */}
|
||||||
<div className="relative h-40 md:h-52 w-full overflow-hidden">
|
<div className="relative h-44 md:h-56 w-full overflow-hidden">
|
||||||
{/* Messaggio corrente */}
|
{/* Messaggio corrente */}
|
||||||
<div
|
<div
|
||||||
key={currentIndex}
|
key={currentIndex}
|
||||||
className="absolute inset-0 flex flex-col items-center justify-center animate-slide-in-up"
|
className="absolute inset-0 flex flex-col items-center justify-center animate-slide-in-up"
|
||||||
>
|
>
|
||||||
<h1 className="text-5xl md:text-7xl lg:text-8xl font-bold text-white mb-2 drop-shadow-lg whitespace-nowrap">
|
<h1 className="text-5xl md:text-7xl lg:text-8xl font-bold text-white mb-2 drop-shadow-lg">
|
||||||
{currentMessage.text}
|
{currentMessage.text}
|
||||||
</h1>
|
</h1>
|
||||||
<p className="text-lg text-white/60 uppercase tracking-wider">
|
<p className="text-lg text-white/60 uppercase tracking-wider">
|
||||||
|
|||||||
@@ -130,7 +130,16 @@ export function ActiveGateScreen({
|
|||||||
</div>
|
</div>
|
||||||
<p className="text-2xl text-gray-700 mb-2">Utente con badge:</p>
|
<p className="text-2xl text-gray-700 mb-2">Utente con badge:</p>
|
||||||
<p className="text-3xl font-bold text-error font-mono mb-4">{notFoundBadge}</p>
|
<p className="text-3xl font-bold text-error font-mono mb-4">{notFoundBadge}</p>
|
||||||
<p className="text-2xl text-gray-700">non trovato nel sistema</p>
|
<p className="text-2xl text-gray-700 mb-8">non trovato nel sistema</p>
|
||||||
|
|
||||||
|
{/* Cancel Button */}
|
||||||
|
<Button
|
||||||
|
variant="secondary"
|
||||||
|
size="lg"
|
||||||
|
onClick={onCancelUser}
|
||||||
|
>
|
||||||
|
✕ Annulla
|
||||||
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
) : error ? (
|
) : error ? (
|
||||||
// Error state
|
// Error state
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ export function SuccessModal({
|
|||||||
</div>
|
</div>
|
||||||
|
|
||||||
{/* Carosello Messaggi Benvenuto */}
|
{/* Carosello Messaggi Benvenuto */}
|
||||||
<WelcomeCarousel userName={userName} intervalMs={carouselIntervalMs} />
|
<WelcomeCarousel userName={userName} intervalMs={carouselIntervalMs}/>
|
||||||
|
|
||||||
{/* Sub text */}
|
{/* Sub text */}
|
||||||
<p className="text-2xl md:text-3xl text-white/80 mt-8 animate-fade-in">
|
<p className="text-2xl md:text-3xl text-white/80 mt-8 animate-fade-in">
|
||||||
|
|||||||
Reference in New Issue
Block a user