feat: setup iniziale sistema controllo accessi Focolari
Struttura progetto: - Backend mock Python (FastAPI) con API per gestione varchi - Frontend React + TypeScript + Vite + Tailwind CSS - Documentazione e piani di sviluppo Backend (backend-mock/): - API REST: /info-room, /login-validate, /anagrafica, /entry-request - Dati mock: 7 utenti, validatore (999999/focolari) - CORS abilitato, docs OpenAPI automatiche - Configurazione pipenv per ambiente virtuale Frontend (frontend/): - State machine completa per flusso accesso varco - Hook useRFIDScanner per lettura badge (pattern singolo) - Componenti UI: Logo, Button, Input, Modal, UserCard, Timer - Schermate: Loading, Login, ActiveGate, Success/Error Modal - Design system con colori Focolari - Ottimizzato per tablet touch Documentazione (ai-prompts/): - Welcome guide per futuri agenti - Piano sviluppo backend e frontend con checklist DA COMPLETARE: - Hook RFID multi-pattern (US/IT/altri layout tastiera) - Pagina /debug per diagnostica in loco - Logging console strutturato
This commit is contained in:
30
backend-mock/.gitignore
vendored
Normal file
30
backend-mock/.gitignore
vendored
Normal file
@@ -0,0 +1,30 @@
|
||||
# Byte-compiled / optimized / DLL files
|
||||
__pycache__/
|
||||
*.py[cod]
|
||||
*$py.class
|
||||
|
||||
# Virtual environments
|
||||
.venv/
|
||||
venv/
|
||||
ENV/
|
||||
env/
|
||||
|
||||
# Pipenv
|
||||
Pipfile.lock
|
||||
|
||||
# IDE
|
||||
.idea/
|
||||
.vscode/
|
||||
*.swp
|
||||
*.swo
|
||||
|
||||
# OS
|
||||
.DS_Store
|
||||
Thumbs.db
|
||||
|
||||
# Logs
|
||||
*.log
|
||||
|
||||
# Local environment
|
||||
.env
|
||||
.env.local
|
||||
18
backend-mock/Pipfile
Normal file
18
backend-mock/Pipfile
Normal file
@@ -0,0 +1,18 @@
|
||||
[[source]]
|
||||
url = "https://pypi.org/simple"
|
||||
verify_ssl = true
|
||||
name = "pypi"
|
||||
|
||||
[packages]
|
||||
fastapi = ">=0.109.0"
|
||||
uvicorn = {extras = ["standard"], version = ">=0.27.0"}
|
||||
pydantic = ">=2.5.0"
|
||||
|
||||
[dev-packages]
|
||||
|
||||
[requires]
|
||||
python_version = "3.10"
|
||||
|
||||
[scripts]
|
||||
start = "python main.py"
|
||||
dev = "uvicorn main:app --reload --host 0.0.0.0 --port 8000"
|
||||
277
backend-mock/main.py
Normal file
277
backend-mock/main.py
Normal file
@@ -0,0 +1,277 @@
|
||||
"""
|
||||
Focolari Voting System - Backend Mock
|
||||
Sistema di controllo accessi per votazioni del Movimento dei Focolari
|
||||
"""
|
||||
|
||||
import random
|
||||
from fastapi import FastAPI, HTTPException
|
||||
from fastapi.middleware.cors import CORSMiddleware
|
||||
from pydantic import BaseModel
|
||||
from typing import Optional
|
||||
|
||||
app = FastAPI(
|
||||
title="Focolari Voting System API",
|
||||
description="Backend mock per il sistema di controllo accessi",
|
||||
version="1.0.0"
|
||||
)
|
||||
|
||||
# CORS abilitato per tutti
|
||||
app.add_middleware(
|
||||
CORSMiddleware,
|
||||
allow_origins=["*"],
|
||||
allow_credentials=True,
|
||||
allow_methods=["*"],
|
||||
allow_headers=["*"],
|
||||
)
|
||||
|
||||
# ============================================
|
||||
# MODELLI PYDANTIC
|
||||
# ============================================
|
||||
|
||||
class LoginRequest(BaseModel):
|
||||
badge: str
|
||||
password: str
|
||||
|
||||
class EntryRequest(BaseModel):
|
||||
user_badge: str
|
||||
validator_password: str
|
||||
|
||||
class UserResponse(BaseModel):
|
||||
badge_code: str
|
||||
nome: str
|
||||
cognome: str
|
||||
url_foto: str
|
||||
ruolo: str
|
||||
ammesso: bool
|
||||
warning: Optional[str] = None
|
||||
|
||||
class RoomInfoResponse(BaseModel):
|
||||
room_name: str
|
||||
meeting_id: str
|
||||
|
||||
class LoginResponse(BaseModel):
|
||||
success: bool
|
||||
message: str
|
||||
token: Optional[str] = None
|
||||
|
||||
class EntryResponse(BaseModel):
|
||||
success: bool
|
||||
message: str
|
||||
welcome_message: Optional[str] = None
|
||||
|
||||
# ============================================
|
||||
# DATI MOCK
|
||||
# ============================================
|
||||
|
||||
# Credenziali Validatore
|
||||
VALIDATOR_BADGE = "999999"
|
||||
VALIDATOR_PASSWORD = "focolari"
|
||||
MOCK_TOKEN = "focolare-validator-token-2024"
|
||||
|
||||
# Lista utenti mock
|
||||
USERS_DB = [
|
||||
{
|
||||
"badge_code": "000001",
|
||||
"nome": "Maria",
|
||||
"cognome": "Rossi",
|
||||
"url_foto": "https://randomuser.me/api/portraits/women/1.jpg",
|
||||
"ruolo": "Votante",
|
||||
"ammesso": True
|
||||
},
|
||||
{
|
||||
"badge_code": "000002",
|
||||
"nome": "Giuseppe",
|
||||
"cognome": "Bianchi",
|
||||
"url_foto": "https://randomuser.me/api/portraits/men/2.jpg",
|
||||
"ruolo": "Votante",
|
||||
"ammesso": True
|
||||
},
|
||||
{
|
||||
"badge_code": "000003",
|
||||
"nome": "Anna",
|
||||
"cognome": "Verdi",
|
||||
"url_foto": "https://randomuser.me/api/portraits/women/3.jpg",
|
||||
"ruolo": "Tecnico",
|
||||
"ammesso": True
|
||||
},
|
||||
{
|
||||
"badge_code": "000004",
|
||||
"nome": "Francesco",
|
||||
"cognome": "Neri",
|
||||
"url_foto": "https://randomuser.me/api/portraits/men/4.jpg",
|
||||
"ruolo": "Ospite",
|
||||
"ammesso": False # Non ammesso!
|
||||
},
|
||||
{
|
||||
"badge_code": "000005",
|
||||
"nome": "Lucia",
|
||||
"cognome": "Gialli",
|
||||
"url_foto": "https://randomuser.me/api/portraits/women/5.jpg",
|
||||
"ruolo": "Votante",
|
||||
"ammesso": True
|
||||
},
|
||||
{
|
||||
"badge_code": "000006",
|
||||
"nome": "Paolo",
|
||||
"cognome": "Blu",
|
||||
"url_foto": "https://randomuser.me/api/portraits/men/6.jpg",
|
||||
"ruolo": "Votante",
|
||||
"ammesso": False # Non ammesso!
|
||||
},
|
||||
{
|
||||
"badge_code": "123456",
|
||||
"nome": "Teresa",
|
||||
"cognome": "Martini",
|
||||
"url_foto": "https://randomuser.me/api/portraits/women/7.jpg",
|
||||
"ruolo": "Votante",
|
||||
"ammesso": True
|
||||
},
|
||||
]
|
||||
|
||||
# Messaggi di benvenuto multilingua
|
||||
WELCOME_MESSAGES = [
|
||||
"Benvenuto! / Welcome!",
|
||||
"Bienvenue! / Willkommen!",
|
||||
"Bienvenido! / Bem-vindo!",
|
||||
"欢迎! / 歓迎!",
|
||||
"Добро пожаловать! / مرحبا!"
|
||||
]
|
||||
|
||||
# ============================================
|
||||
# ENDPOINTS
|
||||
# ============================================
|
||||
|
||||
@app.get("/")
|
||||
async def root():
|
||||
"""Endpoint di test per verificare che il server sia attivo"""
|
||||
return {"status": "ok", "message": "Focolari Voting System API is running"}
|
||||
|
||||
|
||||
@app.get("/info-room", response_model=RoomInfoResponse)
|
||||
async def get_room_info():
|
||||
"""
|
||||
Restituisce le informazioni sulla sala e la riunione corrente.
|
||||
"""
|
||||
return RoomInfoResponse(
|
||||
room_name="Sala Assemblea",
|
||||
meeting_id="VOT-2024"
|
||||
)
|
||||
|
||||
|
||||
@app.post("/login-validate", response_model=LoginResponse)
|
||||
async def login_validate(request: LoginRequest):
|
||||
"""
|
||||
Valida le credenziali del validatore.
|
||||
Il badge deve essere quello del validatore (999999) e la password corretta.
|
||||
"""
|
||||
# Pulisci il badge da eventuali caratteri sentinel
|
||||
clean_badge = request.badge.strip().replace(";", "").replace("?", "")
|
||||
|
||||
if clean_badge != VALIDATOR_BADGE:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="Badge validatore non riconosciuto"
|
||||
)
|
||||
|
||||
if request.password != VALIDATOR_PASSWORD:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="Password non corretta"
|
||||
)
|
||||
|
||||
return LoginResponse(
|
||||
success=True,
|
||||
message="Login validatore effettuato con successo",
|
||||
token=MOCK_TOKEN
|
||||
)
|
||||
|
||||
|
||||
@app.get("/anagrafica/{badge_code}", response_model=UserResponse)
|
||||
async def get_user_anagrafica(badge_code: str):
|
||||
"""
|
||||
Cerca un utente tramite il suo badge code.
|
||||
Restituisce i dati anagrafici e un warning se non è ammesso.
|
||||
"""
|
||||
# Pulisci il badge da eventuali caratteri sentinel
|
||||
clean_badge = badge_code.strip().replace(";", "").replace("?", "")
|
||||
|
||||
# Normalizza: rimuovi zeri iniziali per il confronto, ma cerca anche con zeri
|
||||
for user in USERS_DB:
|
||||
if user["badge_code"] == clean_badge or user["badge_code"].lstrip("0") == clean_badge.lstrip("0"):
|
||||
response = UserResponse(
|
||||
badge_code=user["badge_code"],
|
||||
nome=user["nome"],
|
||||
cognome=user["cognome"],
|
||||
url_foto=user["url_foto"],
|
||||
ruolo=user["ruolo"],
|
||||
ammesso=user["ammesso"]
|
||||
)
|
||||
|
||||
# Aggiungi warning se non ammesso
|
||||
if not user["ammesso"]:
|
||||
response.warning = "ATTENZIONE: Questo utente NON è autorizzato all'ingresso!"
|
||||
|
||||
return response
|
||||
|
||||
# Utente non trovato
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Utente con badge {clean_badge} non trovato nel sistema"
|
||||
)
|
||||
|
||||
|
||||
@app.post("/entry-request", response_model=EntryResponse)
|
||||
async def process_entry_request(request: EntryRequest):
|
||||
"""
|
||||
Processa una richiesta di ingresso.
|
||||
Richiede il badge dell'utente e la password del validatore.
|
||||
"""
|
||||
# Pulisci i dati
|
||||
clean_user_badge = request.user_badge.strip().replace(";", "").replace("?", "")
|
||||
|
||||
# Verifica password validatore
|
||||
if request.validator_password != VALIDATOR_PASSWORD:
|
||||
raise HTTPException(
|
||||
status_code=401,
|
||||
detail="Password validatore non corretta"
|
||||
)
|
||||
|
||||
# Cerca l'utente
|
||||
user_found = None
|
||||
for user in USERS_DB:
|
||||
if user["badge_code"] == clean_user_badge or user["badge_code"].lstrip("0") == clean_user_badge.lstrip("0"):
|
||||
user_found = user
|
||||
break
|
||||
|
||||
if not user_found:
|
||||
raise HTTPException(
|
||||
status_code=404,
|
||||
detail=f"Utente con badge {clean_user_badge} non trovato"
|
||||
)
|
||||
|
||||
if not user_found["ammesso"]:
|
||||
raise HTTPException(
|
||||
status_code=403,
|
||||
detail=f"L'utente {user_found['nome']} {user_found['cognome']} NON è autorizzato all'ingresso"
|
||||
)
|
||||
|
||||
# Successo! Genera messaggio di benvenuto casuale
|
||||
welcome = random.choice(WELCOME_MESSAGES)
|
||||
|
||||
return EntryResponse(
|
||||
success=True,
|
||||
message=f"Ingresso registrato per {user_found['nome']} {user_found['cognome']}",
|
||||
welcome_message=welcome
|
||||
)
|
||||
|
||||
|
||||
# ============================================
|
||||
# AVVIO SERVER
|
||||
# ============================================
|
||||
|
||||
if __name__ == "__main__":
|
||||
import uvicorn
|
||||
print("🚀 Avvio Focolari Voting System Backend...")
|
||||
print("📍 Server in ascolto su http://localhost:8000")
|
||||
print("📚 Documentazione API su http://localhost:8000/docs")
|
||||
uvicorn.run(app, host="0.0.0.0", port=8000)
|
||||
Reference in New Issue
Block a user