final 1.2

This commit is contained in:
2026-06-06 23:19:55 +02:00
parent a8c841bdbe
commit bc57d0dcd0
17 changed files with 142 additions and 537 deletions
+48 -78
View File
@@ -1,91 +1,61 @@
# 🧠 MEMORY.md — Stato Progetto
## Goal
- Implementare 3 feature in Glastree Laravel 13:
1. **Page-length selector** su pagine lista (Individui, Eventi, Email)
2. **CSV import per gruppi** (stesso pattern import individui)
3. **ICS import per eventi** (via sabre/vobject)
- Risolvere login non funzionante dopo fresh install (causa: tinker senza escaping + `2>/dev/null`)
- Sostituire tinker per creazione admin con PDO prepared statement + SQL file per schema
## Constraints & Preferences
- Laravel 13, PHP 8.4, AdminLTE 4, MySQL/MariaDB
- Laravel 13, PHP 8.4, AdminLTE 4, MySQL/MariaDB (install.sql solo MySQL)
- Zero placeholder, zero TODO, codice funzionante
- Seguire pattern esistenti (individui import, controller conventions, view layouts)
- sabre/vobject già installato in composer.json (verificato funzionante in vendor/)
- UI in Italiano come da applicazione esistente
- Compatibilità cross-DB MySQL ↔ SQLite (fallback migration per SQLite)
## Verifica Finale — Tutto OK ✅
## Progress
### Done
- **Creazione `database/install.sql`** — schema MySQL completo + seed data (~1059 righe):
- Tutte le 45 tabelle nello stato finale (consolidate da 58 migration)
- Seed: 119 diocesi, 54 comuni, 8 tipologie documenti, 8 tipologie eventi, 8 ruoli, 23 permessi Spatie, 2 ruoli Spatie, 3 role preset, 5 email folders, 1 riga app_settings
- **NON contiene l'admin** (creato via PDO da install.php)
- `SET FOREIGN_KEY_CHECKS=0` per ordine tabelle, riattivato alla fine
- **Riscritto `install.php`** — eliminata dipendenza da tinker per la creazione admin:
- Aggiunta funzione `createAdminUser()`: PDO prepared statement + `password_hash()` + Spatie role assignment
- Fresh Install Apache: importa `install.sql` via `mysql` CLI, poi crea admin via PDO
- Fresh Install Docker: copia `install.sql` nel container, importa via mysql CLI, crea admin via script PHP temporaneo con `var_export()` (safe)
- Restore: crea admin via PDO se non esiste
- Fallback SQLite: mantiene `php artisan migrate --seed` + tinker ma con `var_export()` per escaping
- Step 5 rinominato da "MIGRATION E SEED" a "SCHEMA DATABASE E DATI BASE"
- Step 6 usa PDO invece di tinker (MySQL)
- **Unified MySQL setup**: singolo tentativo PDO con `dbname` nel DSN — se fallisce (1044/1045/1049), passa automaticamente in root-mode: chiede password root, crea database + utente + GRANT ALL su `localhost` e `%`
- **Sostituito loop 30-retry** con connessione singola PDO (timeout 5s)
- **Fix parse error**: `\$adminName``$adminName` in `var_export()` nel ramo SQLite
- **Fix composer**: rimosso `sudo -u www-data` da composer (causa git dubious ownership + permission denied), aggiunto `COMPOSER_ROOT_VERSION=dev-main`; `vendor` aggiunto al chown finale
| Controllo | Risultato |
|-----------|-----------|
| Syntax PHP tutti i file modificati | ✅ Nessun errore |
| Bilanciamento `{}` JS in tutte le views | ✅ Tutte bilanciate |
| Rotta `gruppi.import` GET | ✅ Registrata |
| Rotta `gruppi.import.store` POST | ✅ Registrata |
| Rotta `gruppi.template` GET | ✅ Registrata |
| Rotta `eventi.import` GET | ✅ Registrata |
| Rotta `eventi.import.store` POST | ✅ Registrata |
| View `gruppi/import.blade.php` | ✅ Creata |
| View `eventi/import.blade.php` | ✅ Creata |
| sabre/vobject loadabile | ✅ `Sabre\VObject\Reader` disponibile |
### In Progress
- *(nessuno)*
## Modifiche Effettuate
### 1. Page-length selector (3 pagine)
**File modificati:**
- `app/Http/Controllers/IndividuoController.php:20``paginate(20)``paginate($perPage)` con clamp `[10,20,25,50,100]`, default 20
- `app/Http/Controllers/EventoController.php:48-49` — stessa logica, default 20
- `app/Http/Controllers/EmailController.php:61-62` — stessa logica, default 20
- `resources/views/individui/index.blade.php` — dropdown `<select name="perPage">` in card-footer con hidden fields per preservare altri query params
- `resources/views/eventi/index.blade.php` — stesso dropdown in card-footer
- `resources/views/email/index.blade.php` — stesso dropdown in card-footer
### 2. CSV Import Gruppi (nuovo)
**File modificati:**
- `app/Http/Controllers/GruppoController.php` — aggiunti 3 metodi:
- `import()`: GET, mostra form upload (authorizeWrite)
- `importStore()`: POST, validate file CSV, parsing fault-tolerant (skip righe vuote, log errori), crea Gruppo::create() con `nome,descrizione,parent_id,diocesi_id,indirizzo_incontro,cap_incontro,città_incontro,sigla_provincia_incontro`
- `downloadTemplate()`: GET, restituisce CSV template con header + esempio
- `resources/views/gruppi/import.blade.php` — form upload + template download + colonna info campi
- `resources/views/gruppi/index.blade.php` — bottone "Importa" in card-tools
- `routes/web.php``gruppi/import` (GET/POST), `gruppi/template` (GET), tutte prima di `Route::resource('gruppi')`
### 3. ICS Import Eventi (nuovo)
**File modificati:**
- `app/Models/Evento.php:34` — aggiunto `'uid_esterno'` a `$fillable` (colonna esisteva in DB ma mancava dal model — potenziale bug risolto)
- `app/Http/Controllers/EventoController.php` — aggiunti 2 metodi:
- `importIcs()`: GET, mostra form upload (authorizeWrite)
- `importIcsStore()`: POST, validate file ICS, usa `Sabre\VObject\Reader::read()` per parsare, estrae VEVENT, mapping:
- `SUMMARY``nome_evento`
- `DESCRIPTION``descrizione`
- `DTSTART``data_specifica` + `ora_inizio`
- `DTEND``durata_minuti` (differenza in minuti)
- `LOCATION``luogo_indirizzo`
- `UID``uid_esterno` (per dedup)
- RRULE: import come `tipo_recorrenza: 'singolo'` con prima occorrenza (no espansione)
- Dedup: skip se `uid_esterno` già presente
- `resources/views/eventi/import.blade.php` — form upload + colonna info campi
- `resources/views/eventi/index.blade.php` — bottone "Importa ICS" in card-tools
- `routes/web.php``eventi/import` (GET/POST), prima di `Route::resource('eventi')`
## Bug Fix Trovato
- **`Evento::$fillable` mancava `uid_esterno`** — la colonna esisteva in DB (aggiunta da migration `2026_06_02_000002_add_uid_esterno_to_eventi_table.php`) ma non era in `$fillable`, quindi mass-assignment la ignorava silenziosamente. Risolto.
### Blocked
- *(nessuno)*
## Key Decisions
- **perPage clamping**: soli valori `[10,20,25,50,100]`, fallback a default 20 su valore non valido
- **CSV gruppi segue pattern individui**: stesso approccio fault-tolerant (skip righe incomplete, log errori, riepilogo finale)
- **ICS senza espansione RRULE**: eventi ricorrenti importati come evento singolo con data della prima occorrenza
- **Dedup via uid_esterno**: lookup prima della creazione, skip se già presente
- **PDO > tinker**: `createAdminUser()` usa PDO prepared statement con `password_hash()`, niente interpolazione PHP in stringhe shell → zero escaping issues
- **`install.sql` > migration sequenziali**: unico file SQL per fresh install, più veloce e debuggabile (si può lanciare `mysql < install.sql` a mano)
- **`var_export()` per tinker fallback**: nei rari casi SQLite, si usa `var_export()` che produce stringhe PHP valide con escaping automatico
- **`2>/dev/null` rimosso** dal MySQL main path (non serve più, non c'è tinker)
- **Docker MySQL credenziali**: hardcoded `mysql:glastree:secret:glastree` (dal docker-compose.mysql.yml)
- **Composer senza sudo**: `COMPOSER_ROOT_VERSION=dev-main` per evitare git dubious ownership; composer eseguito come utente corrente (root), non più come www-data — dopo, `chown -R www-data vendor` per permessi lettura webserver
- **Unified MySQL setup**: PDO connect con `dbname` nel DSN → se fallisce 1044/1045/1049, root-mode: crea DB + utente + GRANT ALL su `localhost` e `%` + FLUSH
## Next Steps
- *(nessuno)*
## Critical Context
- `install.sql` è solo per MySQL. SQLite usa `php artisan migrate --seed` + `DatabaseSeeder`
- `DatabaseSeeder` crea ancora admin `admin@glastree.local / password` ma per MySQL viene cancellato dal PDO script
- `createAdminUser()` setta `is_admin=1`, `status='active'`, `permissions` completo, `role_preset_id=1`, e Spatie `model_has_roles` con role_id=1
- `password_hash($pass, PASSWORD_BCRYPT)` produce hash `$2y$...` compatibile con `Hash::check()` di Laravel
- Composer ora eseguito come utente corrente (root) con `COMPOSER_ROOT_VERSION=dev-main` per evitare git dubious ownership e permission denied su `vendor/`
- `vendor/` aggiunto al `chown` finale in entrambi i modi (Apache e Restore) per garantire leggibilità da www-data
- `ensureMysqlUserAndDb()` sanitizza input (rimuove `' " \0 \`) prima di passarli a SQL exec
## Relevant Files
- `app/Http/Controllers/IndividuoController.php` — perPage in index()
- `app/Http/Controllers/EventoController.php` — perPage in index() + importIcs()/importIcsStore()
- `app/Http/Controllers/GruppoController.php` — import()/importStore()/downloadTemplate()
- `app/Http/Controllers/EmailController.php` — perPage in index()
- `app/Models/Evento.php` — uid_esterno in $fillable
- `resources/views/individui/index.blade.php` — perPage dropdown
- `resources/views/eventi/index.blade.php` — perPage dropdown + ICS import button
- `resources/views/eventi/import.blade.php` — ICS import form (NEW)
- `resources/views/gruppi/index.blade.php` — CSV import button
- `resources/views/gruppi/import.blade.php` — CSV import form (NEW)
- `resources/views/email/index.blade.php` — perPage dropdown
- `routes/web.php` — nuove route gruppi/eventi import
- `database/install.sql`: NEW — schema MySQL completo + seed data (1059 righe)
- `install.php`: MODIFIED — `createAdminUser()` + `ensureMysqlUserAndDb()` + composer fix + auto-create MySQL user