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
|
||||
|
||||
## Pre-requisiti
|
||||
|
||||
- [ ] Backend avviato con `./dev.sh server`
|
||||
- [ ] Browser aperto su `http://localhost:8000`
|
||||
- [ ] Lettore RFID collegato (o usa tastiera per simulare)
|
||||
@@ -10,16 +11,19 @@
|
||||
## 🔐 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" ✅
|
||||
@@ -29,24 +33,29 @@
|
||||
## 👤 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 ✅
|
||||
@@ -56,16 +65,19 @@
|
||||
## ✓ 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 ⚠️
|
||||
@@ -75,15 +87,18 @@
|
||||
## 🎠 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 ✅
|
||||
|
||||
@@ -92,20 +107,36 @@
|
||||
## ⏱️ 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 ✅
|
||||
|
||||
### 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
|
||||
|
||||
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`)
|
||||
@@ -117,10 +148,12 @@
|
||||
## 🔧 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 ✅
|
||||
|
||||
@@ -129,10 +162,12 @@
|
||||
## 📱 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 ✅
|
||||
@@ -141,18 +176,21 @@
|
||||
|
||||
## 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) |
|
||||
| Formato RFID | Badge | Nome | Ruolo | Risultato Atteso |
|
||||
|----------------|------------|----------------|-----------|----------------------|
|
||||
| `ò0008988288_` | 0008988288 | Marco Bianchi | Convocato | ✅ Ammesso |
|
||||
| `ò0007399575_` | 0007399575 | Laura Rossi | Invitato | ✅ Ammessa |
|
||||
| `ò0000514162_` | 0000514162 | Giuseppe Verdi | Tecnico | ❌ Non ammesso |
|
||||
| `ò0006478281_` | 0006478281 | - | - | ⚠️ Non trovato (404) |
|
||||
|
||||
**Ruoli possibili:** Convocato, Invitato, Tecnico, Staff
|
||||
|
||||
**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)
|
||||
|
||||
@@ -147,7 +147,8 @@ VotoFocolari/
|
||||
|
||||
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] Lista utenti mock con **badge reali**:
|
||||
- `0008988288` - Marco Bianchi (Votante, ammesso)
|
||||
- `0007399575` - Laura Rossi (Votante, ammessa)
|
||||
- `0008988288` - Marco Bianchi (Convocato, ammesso)
|
||||
- `0007399575` - Laura Rossi (Invitato, ammessa)
|
||||
- `0000514162` - Giuseppe Verdi (Tecnico, NON ammesso)
|
||||
- `0006478281` - **NON nel DB** (per test "non trovato")
|
||||
- [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.
|
||||
|
||||
**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`)
|
||||
|
||||
@@ -175,10 +186,12 @@ curl -X POST http://localhost:8000/entry-request \
|
||||
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
|
||||
|
||||
@@ -81,9 +81,14 @@ Ottimizzata per tablet in orizzontale.
|
||||
- [x] `ActiveGateScreen.tsx` - varco attivo:
|
||||
- [x] Card utente (layout largo per tablet)
|
||||
- [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] 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] `DebugScreen.tsx` - pagina diagnostica RFID
|
||||
|
||||
@@ -111,9 +116,10 @@ Ottimizzata per tablet in orizzontale.
|
||||
- [x] Componente `WelcomeCarousel.tsx`
|
||||
- [x] 10 lingue supportate
|
||||
- [x] **Animazione smooth sliding** (slide up/down)
|
||||
- [x] Scorrimento automatico (intervallo calcolato dinamicamente)
|
||||
- [x] Scorrimento automatico lento (più rilassato)
|
||||
- [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
|
||||
|
||||
@@ -130,12 +136,12 @@ Ottimizzata per tablet in orizzontale.
|
||||
|
||||
## Badge di Test
|
||||
|
||||
| Badge | Nome | Ruolo | Ammesso |
|
||||
|--------------|----------------|---------|---------------|
|
||||
| `0008988288` | Marco Bianchi | Votante | ✅ Sì |
|
||||
| `0007399575` | Laura Rossi | Votante | ✅ Sì |
|
||||
| `0000514162` | Giuseppe Verdi | Tecnico | ❌ No |
|
||||
| `0006478281` | - | - | ⚠️ Non nel DB |
|
||||
| Badge | Nome | Ruolo | Ammesso |
|
||||
|--------------|----------------|-----------|---------------|
|
||||
| `0008988288` | Marco Bianchi | Convocato | ✅ Sì |
|
||||
| `0007399575` | Laura Rossi | Invitato | ✅ Sì |
|
||||
| `0000514162` | Giuseppe Verdi | Tecnico | ❌ No |
|
||||
| `0006478281` | - | - | ⚠️ Non nel DB |
|
||||
|
||||
---
|
||||
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
# 📄 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
|
||||
**Data:** Gennaio 2026
|
||||
@@ -10,12 +11,15 @@ Questo documento descrive le specifiche che il backend reale deve implementare p
|
||||
## 🌐 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.
|
||||
|
||||
@@ -24,15 +28,18 @@ 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
|
||||
|
||||
@@ -45,6 +52,7 @@ Questo garantisce che:
|
||||
Restituisce informazioni sulla sala/meeting corrente e lo stato del server.
|
||||
|
||||
#### Response `200 OK`
|
||||
|
||||
```json
|
||||
{
|
||||
"room_name": "Sala Assemblea",
|
||||
@@ -53,19 +61,21 @@ Restituisce informazioni sulla sala/meeting corrente e lo stato del server.
|
||||
}
|
||||
```
|
||||
|
||||
| Campo | Tipo | Descrizione |
|
||||
|-------|------|-------------|
|
||||
| `room_name` | string | Nome della sala visualizzato nell'header |
|
||||
| `meeting_id` | string | Identificativo del meeting/votazione |
|
||||
| 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.
|
||||
Verifica le credenziali del validatore. **IMPORTANTE:** Qualsiasi badge può diventare un badge validatore se la password
|
||||
è corretta.
|
||||
|
||||
#### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"badge": "0007399575",
|
||||
@@ -73,12 +83,20 @@ Verifica le credenziali del validatore. **IMPORTANTE:** Qualsiasi badge può div
|
||||
}
|
||||
```
|
||||
|
||||
| Campo | Tipo | Descrizione |
|
||||
|-------|------|-------------|
|
||||
| `badge` | string | Codice badge scansionato (numerico, senza sentinel) |
|
||||
| `password` | string | Password inserita dall'utente |
|
||||
| Campo | Tipo | Descrizione |
|
||||
|------------|--------|-------------------------------------------------------------------------------------|
|
||||
| `badge` | string | Codice badge scansionato (numerico, senza sentinel) - **opzionale per validazione** |
|
||||
| `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)
|
||||
|
||||
```json
|
||||
{
|
||||
"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
|
||||
{
|
||||
"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:**
|
||||
|
||||
- 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)
|
||||
@@ -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.
|
||||
|
||||
#### 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**.
|
||||
|
||||
**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)
|
||||
|
||||
```json
|
||||
{
|
||||
"badge_code": "0008988288",
|
||||
@@ -120,11 +160,18 @@ Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **
|
||||
"cognome": "Bianchi",
|
||||
"url_foto": "https://example.com/foto.jpg",
|
||||
"ruolo": "Votante",
|
||||
"ammesso": true # <-- utente ammesso all'ingresso
|
||||
"ammesso": true
|
||||
#
|
||||
<--
|
||||
utente
|
||||
ammesso
|
||||
all
|
||||
'ingresso
|
||||
}
|
||||
```
|
||||
|
||||
#### Response `200 OK` (Utente trovato ma NON ammesso)
|
||||
|
||||
```json
|
||||
{
|
||||
"badge_code": "0000514162",
|
||||
@@ -132,27 +179,35 @@ Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **
|
||||
"cognome": "Verdi",
|
||||
"url_foto": "https://example.com/foto.jpg",
|
||||
"ruolo": "Tecnico",
|
||||
"ammesso": false, #<-- utente NON ammesso all'ingresso
|
||||
"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` |
|
||||
| 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` |
|
||||
|
||||
---
|
||||
|
||||
@@ -161,6 +216,7 @@ Gli zeri iniziali sono significativi: `"0008988288"` e `"8988288"` sono badge **
|
||||
Registra l'ingresso di un utente. Richiede conferma del validatore.
|
||||
|
||||
#### Request Body
|
||||
|
||||
```json
|
||||
{
|
||||
"user_badge": "0008988288",
|
||||
@@ -168,12 +224,13 @@ Registra l'ingresso di un utente. Richiede conferma del validatore.
|
||||
}
|
||||
```
|
||||
|
||||
| Campo | Tipo | Descrizione |
|
||||
|-------|------|-------------|
|
||||
| `user_badge` | string | Badge dell'utente che sta entrando |
|
||||
| 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,
|
||||
@@ -182,6 +239,7 @@ Registra l'ingresso di un utente. Richiede conferma del validatore.
|
||||
```
|
||||
|
||||
#### Response `401 Unauthorized` (Password errata)
|
||||
|
||||
```json
|
||||
{
|
||||
"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)
|
||||
|
||||
```json
|
||||
{
|
||||
"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)
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "Badge utente non trovato"
|
||||
@@ -203,7 +263,9 @@ Registra l'ingresso di un utente. Richiede conferma del validatore.
|
||||
```
|
||||
|
||||
**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
|
||||
2. Che l'utente esista
|
||||
3. Che l'utente sia ammesso (`ammesso: true`)
|
||||
@@ -215,23 +277,28 @@ 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
|
||||
- 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
|
||||
|
||||
@@ -240,32 +307,36 @@ Implementare rate limiting su:
|
||||
## 📊 Struttura Dati Suggerita
|
||||
|
||||
### Database Utenti
|
||||
|
||||
```sql
|
||||
CREATE TABLE users (
|
||||
id SERIAL PRIMARY KEY,
|
||||
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,
|
||||
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);
|
||||
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,
|
||||
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)
|
||||
room_id VARCHAR(50),
|
||||
action VARCHAR(20) NOT NULL, -- 'entry', 'denied', 'not_found'
|
||||
timestamp TIMESTAMP DEFAULT NOW(),
|
||||
ip_address VARCHAR(45)
|
||||
);
|
||||
```
|
||||
|
||||
@@ -286,9 +357,10 @@ CREATE TABLE access_log (
|
||||
9. **Entry badge inesistente** → 404 Not Found
|
||||
|
||||
### Badge di Test (Mock)
|
||||
|
||||
```
|
||||
0008988288 - Marco Bianchi (Votante, ammesso)
|
||||
0007399575 - Laura Rossi (Votante, ammessa)
|
||||
0008988288 - Marco Bianchi (Convocato, ammesso)
|
||||
0007399575 - Laura Rossi (Invitato, ammessa)
|
||||
0000514162 - Giuseppe Verdi (Tecnico, NON ammesso)
|
||||
0006478281 - NON nel database (per test 404)
|
||||
```
|
||||
@@ -298,9 +370,11 @@ CREATE TABLE access_log (
|
||||
## 📝 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)
|
||||
@@ -308,7 +382,9 @@ Tutte le richieste e risposte usano `application/json`.
|
||||
- `500`: Errore interno server
|
||||
|
||||
### Formato Errori
|
||||
|
||||
Tutti gli errori devono restituire un JSON con campo `detail`:
|
||||
|
||||
```json
|
||||
{
|
||||
"detail": "Messaggio di errore descrittivo"
|
||||
@@ -320,6 +396,7 @@ Tutti gli errori devono restituire un JSON con campo `detail`:
|
||||
## 🔄 Changelog
|
||||
|
||||
### v1.0 (Gennaio 2026)
|
||||
|
||||
- Specifica iniziale
|
||||
- Endpoint: `/info-room`, `/login-validate`, `/anagrafica/{badge}`, `/entry-request`
|
||||
- Meccanismo invalidazione sessioni con `server_start_time`
|
||||
|
||||
@@ -5,12 +5,20 @@
|
||||
"meeting_id": "VOT-2024"
|
||||
},
|
||||
"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",
|
||||
"nome": "Laura",
|
||||
"cognome": "Rossi",
|
||||
"url_foto": "https://randomuser.me/api/portraits/women/2.jpg",
|
||||
"ruolo": "Votante",
|
||||
"ruolo": "Invitato",
|
||||
"ammesso": true
|
||||
},
|
||||
{
|
||||
|
||||
@@ -10,7 +10,7 @@
|
||||
"nome": "Test",
|
||||
"cognome": "Ammesso",
|
||||
"url_foto": "https://randomuser.me/api/portraits/lego/1.jpg",
|
||||
"ruolo": "Votante",
|
||||
"ruolo": "Convocato",
|
||||
"ammesso": true
|
||||
},
|
||||
{
|
||||
@@ -18,7 +18,7 @@
|
||||
"nome": "Test",
|
||||
"cognome": "NonAmmesso",
|
||||
"url_foto": "https://randomuser.me/api/portraits/lego/2.jpg",
|
||||
"ruolo": "Ospite",
|
||||
"ruolo": "Invitato",
|
||||
"ammesso": false
|
||||
},
|
||||
{
|
||||
|
||||
8
dev.sh
8
dev.sh
@@ -215,10 +215,16 @@ cmd_help() {
|
||||
echo "Esempi:"
|
||||
echo " ./dev.sh install # Setup iniziale"
|
||||
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 --host 127.0.0.1 # Solo localhost (non accessibile da rete)"
|
||||
echo " ./dev.sh server -d data/users_test.json # Con dataset test"
|
||||
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
|
||||
|
||||
@@ -30,11 +30,11 @@ interface WelcomeCarouselProps {
|
||||
paused?: boolean;
|
||||
/** Nome dell'utente da visualizzare (opzionale) */
|
||||
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;
|
||||
}
|
||||
|
||||
export function WelcomeCarousel({paused = false, userName, intervalMs = 800}: WelcomeCarouselProps) {
|
||||
export function WelcomeCarousel({paused = false, userName, intervalMs = 1200}: WelcomeCarouselProps) {
|
||||
const [currentIndex, setCurrentIndex] = useState(0);
|
||||
|
||||
useEffect(() => {
|
||||
@@ -50,15 +50,15 @@ export function WelcomeCarousel({paused = false, userName, intervalMs = 800}: We
|
||||
const currentMessage = WELCOME_MESSAGES[currentIndex];
|
||||
|
||||
return (
|
||||
<div className="flex flex-col items-center justify-center text-center w-full max-w-4xl mx-auto px-4">
|
||||
{/* Container con overflow hidden per animazione slide - altezza maggiore per testi lunghi */}
|
||||
<div className="relative h-40 md:h-52 w-full overflow-hidden">
|
||||
<div className="flex flex-col items-center justify-center text-center w-full px-4">
|
||||
{/* Container fullwidth con overflow hidden per animazione slide */}
|
||||
<div className="relative h-44 md:h-56 w-full overflow-hidden">
|
||||
{/* Messaggio corrente */}
|
||||
<div
|
||||
key={currentIndex}
|
||||
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}
|
||||
</h1>
|
||||
<p className="text-lg text-white/60 uppercase tracking-wider">
|
||||
|
||||
@@ -130,7 +130,16 @@ export function ActiveGateScreen({
|
||||
</div>
|
||||
<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-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>
|
||||
) : error ? (
|
||||
// Error state
|
||||
|
||||
@@ -54,7 +54,7 @@ export function SuccessModal({
|
||||
</div>
|
||||
|
||||
{/* Carosello Messaggi Benvenuto */}
|
||||
<WelcomeCarousel userName={userName} intervalMs={carouselIntervalMs} />
|
||||
<WelcomeCarousel userName={userName} intervalMs={carouselIntervalMs}/>
|
||||
|
||||
{/* Sub text */}
|
||||
<p className="text-2xl md:text-3xl text-white/80 mt-8 animate-fade-in">
|
||||
|
||||
Reference in New Issue
Block a user