Francesco Picone 34415862d2 ++ fix area profilo
2026-07-25 23:35:03 +02:00
2026-07-25 23:35:03 +02:00
2026-07-25 22:39:38 +02:00
2026-04-16 10:05:15 +00:00
2026-07-25 23:35:03 +02:00
2026-07-25 22:39:38 +02:00
2026-04-05 19:26:04 +02:00
2026-07-25 23:35:03 +02:00
2026-04-05 19:26:04 +02:00
2026-04-05 19:26:04 +02:00
2026-04-05 19:26:04 +02:00

TerManager2

Applicazione web per la gestione dell'assegnazione e del rientro dei territori (cartoline) di congregazione, con statistiche di percorrenza, campagne speciali e registro storico completo.

I dati personali dei proclamatori sono cifrati nel database, e ogni operazione è tracciata in un audit log consultabile.


Indice

Per iniziare

Gestione quotidiana

Riferimento


Deploy su Dokploy

Come funziona

L'immagine Docker è autosufficiente: dipendenze PHP e asset frontend sono compilati durante il build, non all'avvio. Nginx gira nello stesso container di php-fpm. Un deploy quindi è semplicemente: build → migrazioni → online.

Non serve il codice sorgente sul server, non ci sono bind mount, e non viene eseguito né composer install né npm build all'avvio.

Servizio Ruolo Esposto
app nginx + php-fpm — l'applicazione via Traefik
queue-worker Job in coda (import PDF massivi) no
scheduler Task pianificati (pulizia audit log) no
mariadb Database no
redis Cache, sessioni, code no

Solo app è raggiungibile dall'esterno. Database e Redis restano su una rete interna senza rotta verso Internet.


Passo 1 — Crea l'applicazione

Su Dokploy: Create Application → tipo Compose.

Campo Valore
Repository il tuo repo Git
Branch main
Compose Path docker-compose.yml

Passo 2 — Genera le credenziali

Sul tuo computer, esegui questi comandi e tieni da parte l'output:

echo "APP_KEY=base64:$(openssl rand -base64 32)"
echo "DB_PASSWORD=$(openssl rand -base64 24)"
echo "DB_ROOT_PASSWORD=$(openssl rand -base64 24)"
echo "REDIS_PASSWORD=$(openssl rand -base64 24)"

Conserva APP_KEY in un password manager. I dati personali dei proclamatori sono cifrati con questa chiave: se la perdi, non sono più leggibili nemmeno avendo il backup del database.

Passo 3 — Imposta le variabili d'ambiente

Nella sezione Environment di Dokploy, incolla questo blocco e sostituisci i valori:

# --- Applicazione ---
APP_NAME=TerManager2
APP_ENV=production
APP_DEBUG=false
APP_TIMEZONE=Europe/Rome

# Deve combaciare con l'indirizzo da cui accedi, porta inclusa.
#   per IP:porta ->  http://IP-DEL-SERVER:8080
#   con dominio  ->  https://territori.tuodominio.it
APP_URL=http://IP-DEL-SERVER:8080

# --- Chiavi e password (dal Passo 2) ---
APP_KEY=base64:...
DB_PASSWORD=...
DB_ROOT_PASSWORD=...
REDIS_PASSWORD=...

# --- Database (mariadb e' il nome del servizio, non cambiarlo) ---
DB_CONNECTION=mysql
DB_HOST=mariadb
DB_PORT=3306
DB_DATABASE=termanager2
DB_USERNAME=termanager2

# --- Redis (idem: redis e' il nome del servizio) ---
REDIS_HOST=redis
REDIS_PORT=6379
CACHE_STORE=redis
SESSION_DRIVER=redis
QUEUE_CONNECTION=redis

# --- Primo amministratore (creato al primo avvio) ---
INITIAL_ADMIN_NAME=Nome Cognome
INITIAL_ADMIN_EMAIL=admin@tuodominio.it
INITIAL_ADMIN_PASSWORD=UnaPasswordSolida

# --- Porte esposte sull'host ---
APP_BIND=0.0.0.0        # 127.0.0.1 se ci metti davanti un reverse proxy
APP_PORT=8080
DB_BIND=127.0.0.1       # database raggiungibile solo via tunnel SSH
DB_PORT_HOST=3306

# --- Sessioni e log ---
# false con accesso HTTP, true solo in HTTPS: vedi .env.example
SESSION_SECURE_COOKIE=false
LOG_CHANNEL=stderr
LOG_LEVEL=warning

# --- Posta (SMTP esterno) ---
MAIL_MAILER=smtp
MAIL_HOST=smtp.tuoprovider.it
MAIL_PORT=587
MAIL_USERNAME=utente
MAIL_PASSWORD=password
MAIL_SCHEME=tls
MAIL_FROM_ADDRESS=noreply@tuodominio.it

APP_URL deve corrispondere esattamente al dominio che servirai, schema https:// incluso. L'applicazione rifiuta richieste con un Host diverso: è la protezione contro l'host header injection, ed è la causa più comune di "pagina che non si apre" al primo deploy.

Passo 4 — Assegna il dominio (facoltativo)

Con la configurazione qui sopra l'applicazione risponde gia' su http://IP-DEL-SERVER:8080. Questo passo serve solo se vuoi un dominio con HTTPS — consigliato, perche' altrimenti password e sessioni viaggiano in chiaro.

Quando lo fai, cambia anche: APP_URL=https://tuodominio.it, SESSION_SECURE_COOKIE=true e APP_BIND=127.0.0.1.

Domains → Add Domain:

Campo Valore
Service Name app
Container Port 80
Host il tuo dominio
HTTPS attivo, Let's Encrypt

Dokploy genera da sé le label Traefik: non vanno scritte nel compose.

Passo 5 — Deploy

Premi Deploy. Il primo avvio esegue migrazioni, seed di ruoli e permessi, e crea l'amministratore. Richiede in genere uno o due minuti.

Nei log dovresti vedere:

[✓] Storage directories ready.
[✓] APP_KEY provided via environment.
[✓] Database reachable.
[✓] Migrations applied.
[✓] Seed completed.
Initial admin created: admin@tuodominio.it
[✓] Caches warmed.
  TerManager2 — ready

Accedi con le credenziali INITIAL_ADMIN_* e cambia subito la password.

Se qualcosa non parte

Lo stack si rifiuta di avviarsi quando manca una variabile essenziale, con un messaggio esplicito:

Messaggio nei log Cosa fare
required variable DB_PASSWORD is missing a value Imposta DB_PASSWORD
required variable REDIS_PASSWORD is missing a value Imposta REDIS_PASSWORD
[✗] APP_URL is not set Imposta APP_URL
[✗] APP_URL must include the scheme Aggiungi https:// davanti

È voluto: meglio un errore immediato che un database in produzione con la password di default.


Deploy su altri sistemi

Lo stack è Docker Compose standard e funziona su Coolify, Portainer, CapRover o un semplice server con Docker.

Le uniche due particolarità sono:

  1. Il file .env. Dokploy lo genera dalle variabili della sua interfaccia. Altrove crealo a mano: cp .env.example .env e compila i valori.

  2. La rete dokploy-network. In fondo a docker-compose.yml c'è una rete esterna che serve al Traefik di Dokploy per raggiungere il container. Hai due possibilità:

    • Tenerla, creandola una volta sola: docker network create dokploy-network
    • Rimuoverla: elimina - dokploy-network dalle reti del servizio app e il blocco dokploy-network: in fondo al file.

Poi imposta nel .env la porta su cui esporre l'applicazione e avvia:

# ascolta su tutte le interfacce, per metterci davanti un reverse proxy
echo "APP_PORT=0.0.0.0:8080" >> .env

docker compose up -d --build
docker compose logs -f app

Infine metti il tuo reverse proxy (Nginx, Caddy, Traefik) davanti alla porta 8080, con certificato TLS.


Sviluppo locale

Si usa lo stesso docker-compose.yml della produzione, con il profilo dev che aggiunge Mailpit:

cp .env.example .env
docker network create dokploy-network   # una volta sola

# nel .env, per l'uso locale:
#   APP_URL=http://localhost:8080
#   APP_ENV=local
#   MAIL_MAILER=smtp  MAIL_HOST=mailpit  MAIL_PORT=1025
#   e le tre password a piacere
# (SESSION_SECURE_COOKIE=false e APP_BIND/APP_PORT vanno gia' bene)

docker compose --profile dev up -d --build
Cosa Dove
Applicazione http://localhost:8080
Mailpit (posta test) http://localhost:8025
Login quello che hai messo in INITIAL_ADMIN_*

Se la porta 8080 è occupata, cambia APP_PORT nel .env (es. APP_PORT=127.0.0.1:8081).

Modifiche al codice senza ricostruire l'immagine

L'immagine è autosufficiente: vendor/ e public/build stanno dentro, quindi di norma una modifica al codice richiede docker compose up -d --build.

Per avere invece le modifiche PHP immediate, crea un docker-compose.override.yml — Docker Compose lo applica da solo, senza bisogno di alcun flag:

services:
  app:
    environment:
      PHP_OPCACHE_VALIDATE_TIMESTAMPS: 1
    volumes:
      - ./:/var/www/html
      # vendor e build restano quelli dell'immagine, non quelli dell'host
      - /var/www/html/vendor
      - /var/www/html/public/build

Il file è già in .gitignore, quindi resta solo tuo.

Per lavorare sugli asset frontend con hot reload:

npm install
npm run dev

Variabili d'ambiente

Il container legge la configurazione dall'ambiente di processo (su Dokploy: le variabili della UI, salvate in .env). Il file docker-compose.yml non ridichiara le variabili applicative: le prende dal .env così com'è.

Obbligatorie

Variabile Esempio Note
APP_URL https://territori.esempio.it Deve combaciare col dominio servito
APP_KEY base64:... Cifra i dati personali — da custodire
DB_PASSWORD — Nessun default
DB_ROOT_PASSWORD — Nessun default
REDIS_PASSWORD — Nessun default

Principali facoltative

Variabile Default Descrizione
APP_NAME TerManager2 Nome mostrato nell'interfaccia
APP_ENV production local in sviluppo
APP_DEBUG false Mai true in produzione
APP_TIMEZONE Europe/Rome Fuso orario
LOG_LEVEL warning debug per diagnosticare
SESSION_SECURE_COOKIE false true solo servendo in HTTPS
APP_BIND 0.0.0.0 127.0.0.1 dietro un reverse proxy
APP_PORT 8080 Porta dell'applicazione sull'host
DB_BIND 127.0.0.1 Interfaccia della porta MariaDB
DB_PORT_HOST 3306 Cambiala se la 3306 e' occupata
INITIAL_ADMIN_EMAIL — Admin creato al primo avvio
INITIAL_ADMIN_PASSWORD — Idem
INITIAL_ADMIN_NAME Amministratore Idem
MAIL_MAILER log smtp per inviare davvero
RUN_MIGRATIONS true Migrazioni automatiche all'avvio
RUN_DB_SEED_ON_FIRST_START true Seed al primo avvio

Da non cambiare

Questi valori sono nomi di servizi definiti in docker-compose.yml, non indirizzi di rete. Se li modifichi il container non trova più il database o Redis:

DB_HOST=mariadb
REDIS_HOST=redis

Nemmeno CACHE_STORE, SESSION_DRIVER e QUEUE_CONNECTION andrebbero spostati altrove: lo stack fornisce Redis e il queue worker consuma la coda Redis, quindi puntarli su un driver diverso interrompe l'elaborazione dei job.


Impostazioni applicative

Configurabili dall'interfaccia (Impostazioni, permesso settings.manage), non da variabili d'ambiente:

Impostazione Descrizione
Nome congregazione Mostrato nell'intestazione e nei PDF
Logo Immagine nell'intestazione
URL pubblico base Base dei link PDF condivisibili
Giorni giacenza "da assegnare" Soglia oltre cui un territorio è da assegnare
Mesi giacenza "prioritari" Soglia oltre cui un territorio diventa prioritario
Giorni per "smarrito" Soglia oltre cui un'assegnazione è da rientrare
Elementi nelle liste della home Quanti record mostrare nelle tre liste rapide
Validità link PDF (mesi) Scadenza dei link pubblici alle cartoline
Download PDF visibile Mostra o nasconde il pulsante di download
Retention audit log (giorni) Dopo quanti giorni i log vengono cancellati

Aggiornamenti

Push su main → Deploy su Dokploy. Ad ogni avvio il container:

  1. ricrea le directory di storage e risolve APP_KEY
  2. attende il database
  3. applica le migrazioni pendenti
  4. salta il seed (già eseguito — il marker è nel volume storage)
  5. rigenera le cache di configurazione, rotte e viste

Se le migrazioni falliscono il container non parte: preferibile a servire traffico su uno schema applicato a metà.

Un aggiornamento ordinario non richiede alcun intervento manuale.


Backup e ripristino

Cosa salvare

Volume Contenuto Nel backup
db_data Database MariaDB ✅
storage_data PDF territori, miniature, log, APP_KEY di fallback ✅
redis_data Cache e sessioni — rigenerabili —

Più il valore di APP_KEY. Il database da solo non basta: senza la chiave i dati personali cifrati non sono leggibili.

Backup

# Database
docker exec <container-db> mariadb-dump -u root -p"$DB_ROOT_PASSWORD" \
  termanager2 | gzip > termanager2_$(date +%Y%m%d).sql.gz

# File caricati (PDF e miniature)
docker run --rm -v <progetto>_storage_data:/data -v "$PWD":/backup \
  alpine tar czf /backup/storage_$(date +%Y%m%d).tar.gz -C /data .

Automatizzabile con cron:

0 2 * * * docker exec <container-db> mariadb-dump -u root -p"$DB_ROOT_PASSWORD" termanager2 | gzip > /backups/termanager2_$(date +\%Y\%m\%d).sql.gz

Ripristino

gunzip -c termanager2_20260718.sql.gz | \
  docker exec -i <container-db> mariadb -u root -p"$DB_ROOT_PASSWORD" termanager2

docker exec <container-app> php artisan migrate --force
docker exec <container-app> php artisan cache:clear

Assicurati che APP_KEY sia la stessa di quando il backup è stato creato.


Comandi utili

Sostituisci <app> con il nome del container (lo trovi con docker ps).

# Log in tempo reale
docker logs -f <app>

# Stato delle migrazioni
docker exec -it <app> php artisan migrate:status

# Shell interattiva Laravel
docker exec -it <app> php artisan tinker

# Reimpostare la password di un utente
docker exec -it <app> php artisan tinker --execute="
  \$u = App\Models\User::where('email','admin@tuodominio.it')->first();
  \$u->password = Hash::make('NuovaPassword');
  \$u->save();
"

# Creare un altro amministratore
docker exec -it <app> php artisan app:create-initial-admin \
  --name="Nome" --email="mail@dominio.it" --password="Password"

# Pulizia manuale dell'audit log
docker exec -it <app> php artisan audit:cleanup

# Svuotare le cache
docker exec -it <app> php artisan optimize:clear

Troubleshooting

Il container app resta unhealthy

L'health check interroga /up. Guarda i log (docker logs <app>): quasi sempre è il database irraggiungibile o una migrazione fallita.

Errore 400 o pagina bianca sul dominio

APP_URL non corrisponde al dominio servito. Deve combaciare esattamente, schema https:// incluso. È la protezione contro l'host header injection.

La pagina appare senza stile (CSS mancante)

Gli asset sono compilati dentro l'immagine. Se mancano, è fallito lo stage di build frontend: cerca npm run build nel log di build del deploy.

I job restano in coda

Verifica che queue-worker sia healthy e che REDIS_PASSWORD sia identica per tutti i servizi.

L'import PDF non termina

L'import ha un timeout di 15 minuti; oltre, il job viene marcato failed e i file temporanei rimossi. Controlla i log del worker per individuare il PDF responsabile.

I dati cifrati non sono più leggibili

APP_KEY è cambiata. Ripristina il valore originale: non esiste altro modo di recuperare i dati. Se la chiave è persa definitivamente, i proclamatori vanno reinseriti (territori, assegnazioni e storico restano intatti — non sono cifrati).

Reset completo (solo sviluppo)

docker compose --profile dev down -v
docker compose --profile dev up -d --build

-v cancella i volumi: database, Redis e file caricati vengono persi.


Funzionalità

Dashboard

  • Anno teocratico corrente (creato automaticamente, settembre → agosto)
  • Media mensile di percorrenza e durata media delle assegnazioni
  • Campagna attiva con barra di progresso
  • Tre liste rapide con azione diretta: da assegnare, prioritari, da rientrare

Territori

  • CRUD completo con upload PDF e miniatura generata automaticamente
  • Filtri per zona, tipologia e stato
  • Badge di stato colorati e indicatore di priorità (manuale o automatica)
  • Dettaglio con visualizzatore PDF e storico assegnazioni per anno teocratico
  • Link PDF pubblico condivisibile, a scadenza configurabile
  • Soft delete con cestino (ripristino o eliminazione definitiva)
  • Import massivo di PDF da cartella o archivio ZIP, elaborato in background

Proclamatori

  • CRUD con nome e cognome cifrati nel database
  • Conteggio dei territori attualmente assegnati
  • Anonimizzazione GDPR irreversibile
  • Soft delete con cestino

Assegnazione e rientro

  • Selezione territorio disponibile e proclamatore attivo
  • Anno teocratico rilevato automaticamente dalla data
  • Validazione: territorio non già assegnato, entrambi attivi
  • Rientro con calcolo automatico dei giorni
  • Prompt campagna: se il rientro ricade nel periodo di una campagna, viene chiesto se conteggiarlo

Campagne

  • CRUD con date di inizio e fine
  • Stato automatico: attiva, futura o conclusa
  • Percentuale di percorrenza in tempo reale
  • Dettaglio territorio per territorio

Registro assegnazioni

  • Filtri per anno teocratico, zona, tipologia, stato e ricerca testuale
  • Colonne: territorio, proclamatore, date, giorni, campagna

Audit log

  • Tracciamento automatico su tutti i modelli
  • Eventi di flusso espliciti: assegnazione, rientro, login, logout
  • Filtri per utente e tipo evento, con diff prima/dopo
  • Pulizia automatica schedulata secondo la retention configurata

XML Exchange

  • Conversione di dump SQL legacy in XML compatibile
  • Import ed export XML dell'intero set di dati gestionali

Zone e tipologie

  • Gestione inline: aggiungi, rinomina, attiva/disattiva, elimina
  • Non eliminabili se hanno territori associati

Ruoli e permessi

Tre ruoli con permessi granulari (spatie/laravel-permission):

Permesso Amministratore Assistente Operatore
settings.manage ✅
territori.manage ✅
proclamatori.manage ✅ ✅
campagne.manage ✅ ✅
registro.view ✅ ✅
registro.export ✅
territori.assign ✅ ✅ ✅
territori.return ✅ ✅ ✅
audit.view ✅
audit.export ✅

L'amministratore ha un bypass completo (Gate::before in AppServiceProvider). Il menu laterale mostra solo le voci per cui l'utente ha i permessi.

I permessi sono verificati sia sulle rotte sia sulle richieste Livewire, quindi una revoca ha effetto immediato senza bisogno di ricaricare la pagina.


Sicurezza e GDPR

Sicurezza

  • Cifratura a riposo: nome e cognome dei proclamatori cifrati con AES-256-CBC tramite APP_KEY
  • Password: hash bcrypt
  • RBAC: middleware permission: sulle rotte, riapplicato anche alle richieste Livewire
  • CSRF: token su tutti i form
  • Rate limiting: sul login
  • Host header: accettate solo richieste il cui Host corrisponde a APP_URL
  • Scheme forwarding: lo schema è deciso dal server, non dall'header del client, per non falsare la validazione dei link firmati
  • Security headers: X-Frame-Options, X-Content-Type-Options, Referrer-Policy
  • Sessioni: in Redis, cookie httpOnly e Secure in produzione
  • Esecuzione PHP: solo index.php è un entrypoint valido; ogni altro .php restituisce 404

GDPR

  • Minimizzazione: dei proclamatori si conservano solo nome, cognome e stato
  • Cifratura a riposo: i dati sono illeggibili anche con accesso diretto al DB
  • Audit trail: registro completo delle operazioni
  • Diritto all'oblio: funzione di anonimizzazione irreversibile
  • Retention: cancellazione automatica dei log oltre il periodo configurato
  • No dati sensibili nei log: l'audit registra gli identificativi, non i nomi in chiaro

Modello dati

Tabella Descrizione
users Utenti dell'applicazione
settings Configurazione singleton
zone Zone territoriali
tipologie Tipologie di territorio
proclamatori Assegnatari, con nome/cognome cifrati e soft delete
territori Cartoline con zona, tipologia, PDF, miniatura, soft delete
anni_teocratici Periodi settembre → agosto
campagne Periodi speciali con date e descrizione
assegnazioni Territorio ↔ proclamatore, con date, anno e campagna
activity_log Audit trail

Vincoli e indici principali

  • territori.numero e anni_teocratici.label — univoci
  • assegnazioni(territorio_id, returned_at) — indice composito, è la ricerca più frequente dell'applicazione
  • assegnazioni(proclamatore_id, returned_at), assegnazioni(assigned_at)
  • territori(attivo, deleted_at), proclamatori(deleted_at)
  • Foreign key con RESTRICT su territorio e proclamatore, a protezione dell'integrità dello storico

Regole di business

Anno teocratico

Va da settembre ad agosto: da settembre in poi l'anno è corrente/corrente+1, prima è corrente-1/corrente. Viene creato automaticamente quando serve. L'assegnazione conta per l'anno determinato dalla data di assegnazione.

Stato del territorio

Calcolato dinamicamente, non memorizzato:

Stato Condizione
in_reparto Attivo, nessuna assegnazione aperta
assegnato Ha un'assegnazione con returned_at nullo
da_rientrare Assegnato da più giorni della soglia configurata
inattivo Contrassegnato come non attivo

Priorità

Un territorio è prioritario se lo è per flag manuale o perché non viene assegnato da più mesi della soglia configurata. La soglia automatica vince sempre sul flag: l'amministratore può aggiungere priorità, non toglierla quando la soglia è scattata.

Percentuale di percorrenza delle campagne

  • Denominatore: tutti i territori assegnati nel periodo della campagna
  • Numeratore: le assegnazioni contrassegnate come conteggiate

Architettura

Stack

Componente Tecnologia
Framework Laravel 11
PHP 8.3 (FPM)
Interfaccia Livewire 3.5 + TailwindCSS 4 + Alpine.js
Database MariaDB 11
Cache/Sessioni/Code Redis 7
Web server Nginx, nello stesso container di php-fpm
RBAC spatie/laravel-permission
Audit trail spatie/laravel-activitylog
PDF barryvdh/laravel-dompdf + poppler-utils
Build asset Vite 6 (in fase di build immagine)

Struttura del progetto

TerManager2/
├── app/
│   ├── Console/Commands/       # audit:cleanup, app:create-initial-admin
│   ├── Http/Controllers/       # PDF pubblici, import, login
│   ├── Jobs/                   # Import PDF massivo (in coda)
│   ├── Livewire/               # Tutti i componenti di interfaccia
│   ├── Models/                 # 9 modelli Eloquent
│   ├── Providers/              # AppServiceProvider
│   └── Services/               # Miniature PDF, stato import
├── bootstrap/app.php           # Middleware, proxy e host fidati
├── config/                     # Configurazione Laravel
├── database/
│   ├── migrations/
│   └── seeders/                # Ruoli, permessi, dati di sviluppo
├── docker/
│   ├── nginx/default.conf      # Vhost
│   ├── php/
│   │   ├── Dockerfile          # Build multi-stage
│   │   ├── entrypoint.sh       # Avvio: chiave, migrazioni, cache
│   │   ├── queue-entrypoint.sh # Avvio worker e scheduler
│   │   ├── php.ini
│   │   └── www.conf
│   └── supervisor/             # nginx + php-fpm nello stesso container
├── resources/                  # CSS, JS, viste Blade
├── routes/web.php              # Rotte con i relativi permessi
├── docker-compose.yml          # Unico, produzione e sviluppo
├── .env.example                # Tutta la configurazione
├── HANDOFF.md                  # Stato del progetto per chi subentra
└── README.md

Perché nginx e php-fpm nello stesso container

Con container separati, nginx avrebbe bisogno di una propria copia di public/ e del volume storage solo per servire i file statici. Per un'applicazione di questa scala è complessità senza beneficio: un solo container significa un solo health check, un solo target per il proxy e nessun volume condiviso da tenere allineato.


Licenza

Proprietaria. Tutti i diritti riservati.

Description
No description provided
Readme 4.2 MiB
Languages
PHP 64%
Blade 26.4%
CSS 4.2%
JavaScript 3.7%
Shell 0.9%
Other 0.8%