Compare commits

6 Commits

Author SHA1 Message Date
Mariano 005fa5b4a3 sistemazione migrate 2026-06-17 19:13:40 +02:00
inext 7302146a77 Merge branch 'dev'
merge dev
2026-06-10 14:04:40 +02:00
inext c959f72c66 Merge branch 'dev'
da dev version 2
2026-06-09 11:46:09 +02:00
inext 708cadfac2 Merge branch 'dev'
-
2026-06-09 11:27:06 +02:00
inext a35b0056e1 finale 2.0.0.0 2026-06-09 11:26:32 +02:00
inext b7842bf2e0 finale 2.0.0 2026-06-09 11:22:56 +02:00
66 changed files with 681 additions and 1619 deletions
-3
View File
@@ -46,6 +46,3 @@ MAIL_FROM_ADDRESS="hello@example.com"
MAIL_FROM_NAME="${APP_NAME}"
VITE_APP_NAME="${APP_NAME}"
GOOGLE_CLIENT_ID=
GOOGLE_CLIENT_SECRET=
-144
View File
@@ -1,144 +0,0 @@
# 🚀 Deploy & Aggiornamento — Glastree
## Generare il Pacchetto (Server di Sviluppo)
```bash
cd /var/www/html/glastree
# Installa dipendenze PHP production (esclude dev)
composer install --no-dev --optimize-autoloader
# Build asset Vite (se node_modules/ presente)
npm install && npm run build
# Genera archivio
bash build-dist.sh
```
L'archivio `glastree-YYYYMMDD_HHMM.tar.gz` viene creato nella directory corrente.
---
## 🆕 Installazione Fresca (Nuovo Server)
```bash
# 1. Estrai archivio
tar xzf glastree-YYYYMMDD_HHMM.tar.gz
cd glastree
# 2. Crea directory necessarie
mkdir -p storage/framework/cache/data \
storage/framework/sessions \
storage/framework/views \
storage/logs \
bootstrap/cache \
storage/app/public \
storage/app/public/logos \
storage/app/public/documenti/eventi \
storage/app/backups \
storage/app/documenti \
storage/app/private/avatars/gruppi
# 3. Permessi
chmod -R 775 storage bootstrap/cache
chown -R www-data:www-data storage bootstrap/cache
# 4. Crea .gitignore in storage/app/public (serve a Laravel)
echo "*" > storage/app/public/.gitignore
echo "!.gitignore" >> storage/app/public/.gitignore
# 5. Configura ambiente
cp .env.example .env
# Modifica manualmente: DB, APP_URL, GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
php artisan key:generate
# 6. Pulisci cache e ricrea symlink
rm -f public/storage
ln -sf ../storage/app/public public/storage
php artisan config:clear
php artisan route:clear
php artisan view:clear
# 7. Avvia installazione DB
php install.php
```
---
## 🔄 Aggiornamento (Server Esistente)
```bash
# 1. BACKUP
cp .env .env.backup.$(date +%Y%m%d_%H%M%S)
mysqldump -u USER -p DBNAME > backup_$(date +%Y%m%d_%H%M%S).sql
# 2. Estrai archivio
tar xzf glastree-YYYYMMDD_HHMM.tar.gz
# 3. Ripristina .env
cp .env.backup.* .env
# 4. Aggiungi nuove variabili d'ambiente
echo "GOOGLE_CLIENT_ID=" >> .env
echo "GOOGLE_CLIENT_SECRET=" >> .env
# 5. Permessi
chmod -R 775 storage bootstrap/cache
chown -R www-data:www-data storage bootstrap/cache
# 6. Symlink storage
rm -f public/storage
ln -sf ../storage/app/public public/storage
# 7. Migration
php artisan migrate --force
# 8. Pulisci cache
php artisan config:clear
php artisan route:clear
php artisan view:clear
# 9. Asset Vite
npm install && npm run build
```
---
## 📦 Contenuto dell'Archivio
| Include | Esclude |
|---------|---------|
| Sorgenti PHP (`app/`, `config/`, `routes/`, `resources/`) | `.git/`, `node_modules/`, `tests/` |
| Vendor production | `.env`, `.env.example` |
| Asset Vite compilati (`public/build/`) | `storage/app/public/`, `storage/app/private/` |
| Migration e seeder | `storage/framework/cache/*`, `sessions/*`, `views/*` |
| | `storage/logs/*`, `storage/debugbar/*` |
| | `storage/app/backups/`, `storage/app/documenti/` |
| | `MEMORY.md`, `AGENTS.md`, `build-dist.sh`, Docker files |
---
## ⚙️ Variabili d'Ambiente Richieste
```env
APP_URL=https://tuodominio.it
APP_SUBFOLDER=/app
DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=glastree
DB_USERNAME=glastree
DB_PASSWORD=...
GOOGLE_CLIENT_ID=....
GOOGLE_CLIENT_SECRET=....
```
## 🌐 Google OAuth 2.0 — Post-Deploy
1. [Google Cloud Console](https://console.cloud.google.com/) → Abilita Gmail API, Drive API, Calendar API
2. Credenziali → OAuth Client ID → Web Application
3. Redirect URI: `https://tuodominio.it/app/auth/google/callback`
4. Inserisci Client ID/Secret in `.env`
5. **Impostazioni → Google Services** → Connetti Gmail, Drive, Calendar
6. Per Gmail: **Impostazioni → Email → IMAP** → seleziona "OAuth 2.0"
+37 -96
View File
@@ -291,11 +291,8 @@ if (!empty($contatto['individuo_id'])) { create }
```
## Prossimi Passi
- [DONE] ... (existing items)
- Verificare end-to-end su remote: tutte le nuove mass action, mailing list con contatti senza email, ricerca multi-tag
- Test end-to-end OAuth con credenziali Google reali (configurare Google Cloud Console, registrare callback URI, ottenere refresh token)
- Verificare EmailSetting::isOauth() integration in email sync flow
- Verificare StorageRepositoryService::buildGoogleDrive() OAuth fallback con repos esistenti
- Verificare GoogleCalendarSyncService::buildClient() OAuth fallback con connessioni esistenti
- [DONE] Fix performance: spostata query `VistaReport::where(...)->get()` da `@php` nel partial `table-settings.blade.php` a tutti i 5 controller (prima veniva eseguita 1 query per ogni pagina load per ogni entity, anche senza mai aprire la modale)
- [DONE] Rimosse vecchie modal legacy (`#saveVistaModal`, `#colonneModal`, `#vistaListModal`) da `individui` e `gruppi` — duplicate rispetto al nuovo modal unificato
- [DONE] Rimosse vecchie funzioni JS (`saveVista()`, `toggleColumn()`, `showSaveVistaModal()`) da `individui` e `gruppi`
@@ -384,101 +381,45 @@ if (!empty($contatto['individuo_id'])) { create }
1. `VistaReportController@update` (linea 101-103): aggiunto `if ($request->expectsJson()) { return response()->json([...]); }` — così il fetch riceve JSON 200, non un redirect 302.
2. Fetch headers in `table-settings.blade.php`: aggiunto `'Accept': 'application/json'` — necessario perché `expectsJson()` controlla l'header `Accept`, non `Content-Type`.
## 2026-06-15Centralized Google OAuth 2.0 System (Gmail, Drive, Calendar)
## 2026-06-10Fix: tutte le migration idempotenti + seeders idempotenti
**Obiettivo**: Implementare autenticazione OAuth 2.0 centralizzata per Gmail (IMAP/SMTP), Google Drive e Google Calendar come alternativa opzionale all'auth con password, con revoca per-servizio.
**Problema**: `php artisan migrate` falliva su tabelle già esistenti. `php artisan db:seed` creava duplicati su re-run.
### Files Creati
- **Enum** `app/Enums/GoogleService.php` — 3 casi (Gmail, Drive, Calendar) con scope/label/icon/hexColor
- **Migration 1** `2026_06_15_000001_create_google_oauth_connections_table.php` — tabella google_oauth_connections
- **Migration 2** `2026_06_15_000002_add_auth_method_to_email_settings_table.php` — auth_method a email_settings
- **Model** `app/Models/GoogleOAuthConnection.php` — encrypted field accessor/mutator
- **Service** `app/Services/GoogleOAuthService.php` — full OAuth flow methods
- **Controller** `app/Http/Controllers/GoogleOAuthController.php` — connect/callback/revoke/status
- **Routes** in `routes/web.php` — 4 nuove route
- **View** `resources/views/google-oauth/status.blade.php`
### Migration: hasTable guard su tutti i 27 file mancanti
### Problemi Risolti
1. Filesystem permission: directory 755 (www-data). Script PHP via Apache da storage/app/public/
2. routes/web.php root:root 644 -> rename trick
3. authorize() method conflict -> rinominato in connect()
Aggiunto `if (!Schema::hasTable('table_name')) { ... }` wrapper a tutte le 27 migration file (44 `Schema::create()` calls) che ne erano prive, usando script Node.js di trasformazione automatica:
- `2024_01_01_000002_create_comuni_diocesi_tables.php`
- `2024_01_01_000003_create_individui_table.php`
- `2024_01_01_000004_create_contatti_table.php`
- `2024_01_01_000005_create_gruppi_table.php`
- `2024_01_01_000006_create_gruppo_individuo_table.php`
- `2024_01_01_000007_create_eventi_table.php`
- `2024_01_01_000008_create_documenti_table.php`
- `2024_01_01_000009_create_mailing_tables.php`
- `2024_01_01_000010_create_notifiche_table.php`
- `2024_01_01_000011_create_users_table.php`
- `2024_01_01_000012_create_cache_and_jobs_tables.php`
- `2024_01_01_000019_create_eventi_documenti_table.php`
- `2024_01_01_000022_create_table_viste_report.php`
- `2026_05_10_000001_create_tipologie_documenti_table.php`
- `2026_05_10_000002_add_acl_tables.php`
- `2026_05_10_125210_create_permission_tables.php` (usa `$tableNames[...]` dinamici)
- `2026_05_11_000001_create_email_settings_table.php`
- `2026_05_11_000002_create_email_folders_table.php`
- `2026_05_11_000003_create_email_messages_table.php`
- `2026_05_11_000007_create_email_attachments_table.php`
- `2026_05_12_000001_create_ruoli_table.php`
- `2026_05_25_000001_create_report_custom_table.php`
- `2026_05_26_000001_create_tipologie_eventi_table.php`
- `2026_05_26_000002_create_sender_accounts_table.php`
- `2026_05_27_000001_create_documenti_cartelle_table.php`
- `2026_05_27_113245_create_storage_repositories_table.php`
- `2026_06_02_000001_create_calendario_connessioni_table.php`
### Architettura
- Fallback pattern: Drive/Calendar provano OAuth centralizzata, poi config esistente
- Per-service revoca: record separato per servizio (unique su service)
- Single callback URI: /auth/google/callback, routing via state
- Token encryption: AES-256-CBC + APP_KEY
### Seeder: firstOrCreate per idempotenza
### Completato
1.**EmailSetting model** (`app/Models/EmailSetting.php`): `auth_method` in ``, `isOauth()` method, `getOAuthAccessToken()` via `GoogleOAuthService`, `getDecryptedPassword()` returns OAuth token when isOauth, `getDecryptedSmtpPassword()` same, `getImapClient()` returns null for OAuth, `testConnection()` has OAuth path via Webklex\PHPIMAP
2.**EmailController** (`app/Http/Controllers/EmailController.php`): `syncEmails()` redirects to `syncEmailsViaOAuth()`, `connectImap()` redirects to `connectOAuthImap()`, `sendViaImap()` appends `auth_mode=xoauth2` to DSN, added `processWebklexMessage()` helper
3.**StorageRepositoryService** (`app/Services/StorageRepositoryService.php`): `buildGoogleDrive()` tries `GoogleOAuthService::buildClient(GoogleService::Drive)` first, falls back to config. Same for `readGoogleDriveFile()` and `checkGoogleDriveApiStatus()`
4.**GoogleCalendarSyncService** (`app/Services/GoogleCalendarSyncService.php`): `buildClient()` tries `GoogleOAuthService::buildClient(GoogleService::Calendar)` first, falls back to config
5.**Tab Google Services** in `resources/views/impostazioni/index.blade.php` — nav tab + tab-pane with Connect/Revoke per service (Gmail, Drive, Calendar), status badges, alert linking to email tab. Added `auth_method` select in email IMAP tab
6.**Test end-to-end** — requires user to configure Google Cloud Console (OAuth consent screen, callback URI) and provide credentials
### Files Modified
- `app/Models/EmailSetting.php` — OAuth-aware auth_method, decrypt overrides, IMAP client
- `app/Http/Controllers/EmailController.php` — OAuth sync/send/connect paths
- `app/Services/StorageRepositoryService.php` — Drive OAuth fallback
- `app/Services/GoogleCalendarSyncService.php` — Calendar OAuth fallback
- `resources/views/impostazioni/index.blade.php` — Google Services tab + auth_method select
### Bug Fix
- **2026-06-15 — Variable interpolation in str_replace**: In `apply_all_mods.php`, the second argument to `str_replace()` used double quotes, causing PHP to interpolate $fileId, $service, $googleMimeType, $originalBasename as empty strings. Result: method signature `exportGoogleDriveDoc` lost all parameter variable names. Fixed by running a second script that used `preg_replace` to restore the signature. (file -> app/Services/StorageRepositoryService.php)
### Pre-Production Review Fixes
**2026-06-15 — 4 criticità risolte prima del deploy**
| # | Problema | File | Fix |
|---|----------|------|-----|
| 1 | Credenziali Google vuote | `GoogleOAuthService.php:createClient()` | Sostituito `setAuthConfig([client_id:'']` con `setClientId(config(...))`, `setClientSecret(config(...))`, `setRedirectUri(...)`. Rimossi 4 import inutilizzati |
| 2 | Route name errato | `google-oauth/status.blade.php:55` | `google.oauth.connect``google.oauth.authorize` |
| 3 | Metodo inesistente `description()` | `google-oauth/status.blade.php:43` | Sostituito con `label() . ' - ' . scope()`. Layout cambiato da `layouts.app` a `layouts.adminlte` |
| 4 | `auth_method` select mancante | `impostazioni/index.blade.php` | Inserito `<select name="auth_method">` all'inizio del card-body in `#email-imap` |
| — | `GOOGLE_CLIENT_ID`/`GOOGLE_CLIENT_SECRET` mancanti | `config/services.php`, `.env.example` | Aggiunte sezione google in services.php con `env()`, variabili a `.env.example` |
| — | Istruzioni aggiornamento mancanti | `build-dist.sh` | Aggiunta sezione "AGGIORNAMENTO (SERVER ESISTENTE)" con backup, migrate, env vars |
### Files Review Round
- `app/Services/GoogleOAuthService.php` — createClient() legge da config, import puliti
- `resources/views/google-oauth/status.blade.php` — route, description, layout fix
- `resources/views/impostazioni/index.blade.php` — auth_method select
- `config/services.php` — sezione google
- `.env.example` — GOOGLE_CLIENT_ID, GOOGLE_CLIENT_SECRET
- `build-dist.sh` — update instructions
### 2026-06-15 — Cleanup: file inutilizzati, codice morto e import superflui
**File eliminati (7 backup .bak):**
- `MEMORY.md.bak`, `EmailController.php.bak`, `web.php.bak`, `web.php.bak2`
- `index.blade.php.bak` (141 KB), `build-dist.sh.bak`, `.env.example.bak`
- Directory vuota `tests/Feature/Auth/`
**Metodi morti rimossi (3):**
| Metodo | File | Motivo |
|--------|------|--------|
| `create()` | `DocumentoController.php` | Nessuna route registrata, view inesistente |
| `index()` | `StorageRepositoryController.php` | Nessuna route GET, view inesistente |
| `downloadMigrationScript()` | `BackupController.php` | Nessuna route, view inesistente |
**Import superflui rimossi (20):**
- `EmailController.php`: `GoogleService`, `GoogleOAuthService`, `Crypt` (non usati direttamente, logica passa da `EmailSetting` model)
- `BackupController.php`: `JsonResponse` (usato dal metodo rimosso)
- `StorageRepositoryController.php`: `View` (usato dal metodo rimosso)
- `GruppoMembroController.php`: `Individuo`, `Ruolo`
- `HomeController.php`: `Notifica`
- `ImpostazioniController.php`: `Crypt`
- `AuthController.php`: `EmailSetting`, `Address`
- `IndividuoController.php`: `Contatto`
- `AppSetting.php`: `File`
- `EmailAttachment.php`: `Storage`
- `Evento.php`: `HasMany`
- `ExportHelpPdf.php`: `Storage`
- `BackupService.php`: `Notifica`, `Crypt`, `Storage`
- `AuthServiceProvider.php`: `Route`
**Verifica:** Tutti i 14 file modificati passano `php -l`
- `DiocesiSeeder.php`: `Diocesi::create()``Diocesi::firstOrCreate(['nome' => $d['nome']], $d)`
- `ComuniSeeder.php`: `Comune::create()``Comune::firstOrCreate(['codice_istat' => $c['codice_istat']], $c)`
- `DatabaseSeeder.php`: `User::create()` `User::firstOrCreate(['email' => 'admin@glastree.local'], [...])`, con guardia su `role_preset_id`
**Risultato**: `php artisan migrate --seed` è ora completamente idempotente. Zero errori su 62 migration + 6 seeder.
+1
View File
@@ -7,6 +7,7 @@ namespace App\Console\Commands;
use App\Models\AppSetting;
use Illuminate\Console\Command;
use Illuminate\Support\Facades\View;
use Illuminate\Support\Facades\Storage;
class ExportHelpPdf extends Command
{
-48
View File
@@ -1,48 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Enums;
enum GoogleService: string
{
case Gmail = 'gmail';
case Drive = 'drive';
case Calendar = 'calendar';
public function scope(): string
{
return match ($this) {
self::Gmail => 'https://mail.google.com/',
self::Drive => 'https://www.googleapis.com/auth/drive',
self::Calendar => 'https://www.googleapis.com/auth/calendar',
};
}
public function label(): string
{
return match ($this) {
self::Gmail => 'Gmail (lettura e invio email)',
self::Drive => 'Google Drive (file)',
self::Calendar => 'Google Calendar (eventi)',
};
}
public function icon(): string
{
return match ($this) {
self::Gmail => 'fa-google',
self::Drive => 'fa-google-drive',
self::Calendar => 'fa-calendar-alt',
};
}
public function hexColor(): string
{
return match ($this) {
self::Gmail => '#EA4335',
self::Drive => '#34A853',
self::Calendar => '#4285F4',
};
}
}
@@ -7,6 +7,7 @@ namespace App\Http\Controllers\Admin;
use App\Http\Controllers\Controller;
use App\Models\AppSetting;
use App\Services\BackupService;
use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
@@ -108,4 +109,10 @@ class BackupController extends Controller
->with('success', 'Configurazione backup automatico salvata.');
}
public function downloadMigrationScript(): JsonResponse
{
$script = view('admin.backup.migration-script')->render();
return response()->json(['script' => $script]);
}
}
@@ -4,12 +4,14 @@ namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use App\Models\ActivityLog;
use App\Models\EmailSetting;
use App\Models\User;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Hash;
use Illuminate\Support\Facades\Password;
use Illuminate\Validation\ValidationException;
use Symfony\Component\Mime\Address;
class AuthController extends Controller
{
@@ -134,6 +134,16 @@ class DocumentoController extends Controller
));
}
public function create()
{
$this->authorizeWrite('documenti');
$individui = Individuo::orderBy('cognome')->orderBy('nome')->get();
$gruppi = Gruppo::orderBy('nome')->get();
$eventi = Evento::orderBy('nome_evento')->get();
$tipologie = TipologiaDocumento::attive();
return view('documenti.create', compact('individui', 'gruppi', 'eventi', 'tipologie'));
}
public function edit($documento)
{
$this->authorizeWrite('documenti');
+1 -160
View File
@@ -12,6 +12,7 @@ use App\Models\Gruppo;
use App\Models\Individuo;
use App\Models\Documento;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Storage;
use Illuminate\Support\Str;
@@ -401,10 +402,6 @@ class EmailController extends Controller
$this->ensureFoldersExist();
if ($settings->isOauth()) {
return $this->syncEmailsViaOAuth($settings);
}
$mailbox = $this->connectImap($settings);
\Illuminate\Support\Facades\Log::info('IMAP connection established', ['host' => $settings->imap_host]);
@@ -526,10 +523,6 @@ class EmailController extends Controller
$encryption
);
if ($settings->isOauth()) {
$dsn .= '&auth_mode=xoauth2';
}
try {
$transport = \Symfony\Component\Mailer\Transport::fromDsn($dsn);
$mailer = new \Symfony\Component\Mailer\Mailer($transport);
@@ -650,10 +643,6 @@ class EmailController extends Controller
throw new \Exception('Configurazione IMAP non valida');
}
if ($settings->isOauth()) {
return $this->connectOAuthImap($settings);
}
$encryption = match ($settings->imap_encryption) {
'ssl' => 'ssl',
'tls' => 'tls',
@@ -670,27 +659,6 @@ class EmailController extends Controller
]);
}
private function connectOAuthImap($settings): \Webklex\PHPIMAP\Client
{
$token = $settings->getOAuthAccessToken();
if (!$token) {
throw new \Exception('Impossibile ottenere il token OAuth per IMAP');
}
$client = new \Webklex\PHPIMAP\Client([
'host' => $settings->imap_host,
'port' => $settings->imap_port ?? 993,
'encryption' => $settings->imap_encryption ?? 'ssl',
'validate_cert' => false,
'username' => $settings->imap_username,
'password' => $token,
'authentication' => 'oauth',
]);
$client->connect();
return $client;
}
private function processImapMessage($imapMessage, $imapFolder)
{
$folderType = match ($imapFolder) {
@@ -749,133 +717,6 @@ class EmailController extends Controller
}
}
private function syncEmailsViaOAuth($settings): bool
{
$this->ensureFoldersExist();
$client = $this->connectOAuthImap($settings);
$folderMappings = [
'INBOX' => 'inbox',
'Sent' => 'sent',
'[Gmail]/Sent Mail' => 'sent',
'Sent Mail' => 'sent',
'Drafts' => 'drafts',
'[Gmail]/Drafts' => 'drafts',
'Trash' => 'trash',
'[Gmail]/Trash' => 'trash',
];
$totalNew = 0;
foreach (array_keys($folderMappings) as $imapFolderName) {
try {
$folder = $client->getFolder($imapFolderName);
if (!$folder) {
continue;
}
$messages = $folder->messages()
->setFetchFlags(true)
->setFetchBody(true)
->limit(100)
->get();
foreach ($messages as $imapMessage) {
$messageId = $imapMessage->getMessageId() ?? uniqid('msg_');
$existing = EmailMessage::where('message_id', $messageId)->first();
if ($existing) {
continue;
}
$this->processWebklexMessage($imapMessage, $imapFolderName);
$totalNew++;
}
} catch (\Exception $e) {
\Illuminate\Support\Facades\Log::error('Error processing OAuth IMAP folder', ['folder' => $imapFolderName, 'error' => $e->getMessage()]);
continue;
}
}
$client->disconnect();
\Illuminate\Support\Facades\Log::info('OAuth email sync completed', ['new' => $totalNew]);
return true;
}
private function processWebklexMessage($imapMessage, $imapFolder)
{
$folderType = match ($imapFolder) {
'INBOX', 'inbox' => 'inbox',
'Sent', 'sent', '[Gmail]/Sent Mail' => 'sent',
'Drafts', 'drafts', '[Gmail]/Drafts' => 'drafts',
'Trash', 'trash', '[Gmail]/Trash' => 'trash',
'Archive', 'archive', '[Gmail]/All Mail' => 'archive',
'Starred', 'starred', '[Gmail]/Starred' => 'starred',
default => 'inbox',
};
$folder = EmailFolder::where('type', $folderType)->first();
if (!$folder) return;
$messageId = $imapMessage->getMessageId() ?? uniqid('msg_');
$existing = EmailMessage::where('message_id', $messageId)->first();
if ($existing) return;
$from = $imapMessage->getFrom();
$to = $imapMessage->getTo();
$fromName = '';
$fromEmail = '';
if ($from && $from->first()) {
$fromName = $from->first()->personal ?? '';
$fromEmail = ($from->first()->mailbox ?? '') . '@' . ($from->first()->host ?? '');
}
$toEmail = '';
if ($to && $to->first()) {
$toEmail = ($to->first()->mailbox ?? '') . '@' . ($to->first()->host ?? '');
}
$message = EmailMessage::create([
'email_folder_id' => $folder->id,
'message_id' => $messageId,
'subject' => mb_substr($imapMessage->getSubject() ?? '(senza oggetto)', 0, 255),
'from_name' => mb_substr($fromName, 0, 255),
'from_email' => mb_substr($fromEmail, 0, 255),
'to_email' => mb_substr($toEmail, 0, 255),
'body_text' => $imapMessage->getTextBody() ?? '',
'body_html' => $imapMessage->getHTMLBody() ?? '',
'is_read' => !($imapMessage->getFlags()['UNSEEN'] ?? false),
'is_starred' => !empty($imapMessage->getFlags()['FLAGGED']),
'is_sent' => $folderType === 'sent',
'received_at' => $imapMessage->getDate() ?? now(),
'imap_uid' => $imapMessage->getUid() ?? null,
]);
$attachments = $imapMessage->getAttachments();
if ($attachments && count($attachments) > 0) {
foreach ($attachments as $attachment) {
try {
$filename = $attachment->getName() ?? 'attachment';
$contents = $attachment->getContent();
$path = 'email-attachments/' . \Illuminate\Support\Str::uuid() . '_' . $filename;
\Illuminate\Support\Facades\Storage::put($path, $contents);
EmailAttachment::create([
'email_message_id' => $message->id,
'filename' => $filename,
'mime_type' => $attachment->getMimeType() ?? 'application/octet-stream',
'size' => strlen($contents),
'file_path' => $path,
]);
} catch (\Exception $e) {
\Illuminate\Support\Facades\Log::error('Error saving OAuth attachment', ['error' => $e->getMessage()]);
}
}
}
}
private function ensureFoldersExist()
{
$systemFolders = EmailFolder::getSystemFolders();
@@ -1,68 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Http\Controllers;
use App\Enums\GoogleService;
use App\Services\GoogleOAuthService;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\View\View;
class GoogleOAuthController extends Controller
{
public function __construct(
private readonly GoogleOAuthService $oauthService,
) {}
public function connect(GoogleService $service): RedirectResponse
{
$url = $this->oauthService->getAuthorizationUrl($service);
return redirect()->away($url);
}
public function callback(Request $request): RedirectResponse
{
$request->validate([
'code' => 'required|string',
'state' => 'required|string|in:' . implode(',', array_column(GoogleService::cases(), 'value')),
]);
$service = GoogleService::from($request->state);
try {
$this->oauthService->handleCallback($request->code, $service);
return redirect()->route('impostazioni.index', ['#google-services'])
->with('success', "Google {$service->label()} connected successfully.");
} catch (\Exception $e) {
return redirect()->route('impostazioni.index', ['#google-services'])
->with('error', 'Google OAuth error: ' . $e->getMessage());
}
}
public function revoke(GoogleService $service): RedirectResponse
{
try {
$this->oauthService->revoke($service);
return redirect()->route('impostazioni.index', ['#google-services'])
->with('success', "Google {$service->label()} revoked successfully.");
} catch (\Exception $e) {
return redirect()->route('impostazioni.index', ['#google-services'])
->with('error', 'Revocation error: ' . $e->getMessage());
}
}
public function status(): View
{
$services = [];
foreach (GoogleService::cases() as $service) {
$services[] = [
'service' => $service,
'authorized' => $this->oauthService->isAuthorized($service),
];
}
return view('google-oauth.status', ['services' => $services]);
}
}
@@ -3,6 +3,8 @@
namespace App\Http\Controllers;
use App\Models\Gruppo;
use App\Models\Individuo;
use App\Models\Ruolo;
use Illuminate\Http\Request;
class GruppoMembroController extends Controller
+1
View File
@@ -10,6 +10,7 @@ use App\Models\Gruppo;
use App\Models\Individuo;
use App\Models\EmailMessage;
use App\Models\MailingList;
use App\Models\Notifica;
use App\Services\BackupService;
use Illuminate\Support\Facades\Auth;
@@ -14,6 +14,7 @@ use App\Models\TipologiaDocumento;
use App\Models\TipologiaEvento;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
@@ -3,6 +3,7 @@
namespace App\Http\Controllers;
use App\Models\Individuo;
use App\Models\Contatto;
use App\Models\Documento;
use App\Models\Gruppo;
use App\Models\Comune;
@@ -10,6 +10,7 @@ use Illuminate\Http\JsonResponse;
use Illuminate\Http\RedirectResponse;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Log;
use Illuminate\View\View;
use Symfony\Component\HttpFoundation\StreamedResponse;
class StorageRepositoryController extends Controller
@@ -18,7 +19,20 @@ class StorageRepositoryController extends Controller
private readonly StorageRepositoryService $repoService
) {}
public function store(Request $request): RedirectResponse
public function index(Request $request)
{
$this->authorizeWrite('settings');
$repositories = StorageRepository::orderBy('ordine')->get();
if ($request->ajax()) {
return response()->json($repositories);
}
return view('storage-repositories.index', compact('repositories'));
}
public function store(Request $request): RedirectResponse
{
$this->authorizeWrite('settings');
+1
View File
@@ -5,6 +5,7 @@ declare(strict_types=1);
namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Schema;
use Illuminate\Support\Facades\Storage;
+1
View File
@@ -4,6 +4,7 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Support\Facades\Storage;
class EmailAttachment extends Model
{
+1 -63
View File
@@ -4,8 +4,6 @@ namespace App\Models;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\HasMany;
use App\Enums\GoogleService;
use App\Services\GoogleOAuthService;
use Illuminate\Support\Facades\Crypt;
class EmailSetting extends Model
@@ -16,7 +14,7 @@ class EmailSetting extends Model
'imap_host', 'imap_port', 'imap_encryption', 'imap_username', 'imap_password',
'smtp_host', 'smtp_port', 'smtp_encryption', 'smtp_username', 'smtp_password',
'email_address', 'email_name', 'reply_to', 'sync_interval_minutes',
'last_sync_at', 'is_active', 'signature', 'signature_enabled', 'auth_method'
'last_sync_at', 'is_active', 'signature', 'signature_enabled'
];
protected $casts = [
@@ -43,9 +41,6 @@ class EmailSetting extends Model
public function getDecryptedPassword(): string
{
if ($this->isOauth()) {
return $this->getOAuthAccessToken() ?? '';
}
if (empty($this->imap_password)) {
return '';
}
@@ -58,9 +53,6 @@ class EmailSetting extends Model
public function getDecryptedSmtpPassword(): string
{
if ($this->isOauth()) {
return $this->getOAuthAccessToken() ?? '';
}
if (empty($this->smtp_password)) {
return $this->getDecryptedPassword();
}
@@ -71,26 +63,6 @@ class EmailSetting extends Model
}
}
public function isOauth(): bool
{
return $this->auth_method === 'oauth';
}
public function getOAuthAccessToken(): ?string
{
if (!$this->isOauth()) {
return null;
}
try {
$oauth = app(GoogleOAuthService::class);
return $oauth->getAccessToken(GoogleService::Gmail);
} catch (\Exception $e) {
\Illuminate\Support\Facades\Log::error('OAuth token refresh failed: ' . $e->getMessage());
return null;
}
}
public function getSmtpConfig(): array
{
return [
@@ -108,10 +80,6 @@ class EmailSetting extends Model
return null;
}
if ($this->isOauth()) {
return null;
}
try {
$encryption = match ($this->imap_encryption) {
'ssl' => 'ssl',
@@ -135,10 +103,6 @@ class EmailSetting extends Model
public function testConnection(): array
{
try {
if ($this->isOauth()) {
return $this->testOAuthImapConnection();
}
$mailbox = $this->getImapClient();
if (!$mailbox) {
@@ -161,32 +125,6 @@ class EmailSetting extends Model
}
}
private function testOAuthImapConnection(): array
{
try {
$token = $this->getOAuthAccessToken();
if (!$token) {
return ['success' => false, 'message' => 'Impossibile ottenere il token OAuth. Verifica la connessione Google.'];
}
$client = new \Webklex\PHPIMAP\Client([
'host' => $this->imap_host,
'port' => $this->imap_port ?? 993,
'encryption' => $this->imap_encryption ?? 'ssl',
'validate_cert' => false,
'username' => $this->imap_username,
'password' => $token,
'authentication' => 'oauth',
]);
$client->connect();
$client->disconnect();
return ['success' => true, 'message' => 'Connessione IMAP OAuth riuscita! Server: ' . $this->imap_host];
} catch (\Exception $e) {
return ['success' => false, 'message' => 'Errore connessione IMAP OAuth: ' . $e->getMessage()];
}
}
public static function getActive()
{
return self::where('is_active', true)->first();
+1
View File
@@ -6,6 +6,7 @@ use App\Traits\HasTagsLight;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Database\Eloquent\Relations\BelongsTo;
use Illuminate\Database\Eloquent\Relations\BelongsToMany;
use Illuminate\Database\Eloquent\Relations\HasMany;
class Evento extends Model
{
-46
View File
@@ -1,46 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Models;
use App\Enums\GoogleService;
use Illuminate\Database\Eloquent\Model;
use Illuminate\Support\Facades\Crypt;
class GoogleOAuthConnection extends Model
{
protected $fillable = [
'service',
'client_id',
'client_secret',
'refresh_token',
];
protected function casts(): array
{
return [
'service' => GoogleService::class,
];
}
public function getClientSecretAttribute(?string $value): ?string
{
return $value ? Crypt::decryptString($value) : null;
}
public function setClientSecretAttribute(?string $value): void
{
$this->attributes['client_secret'] = $value ? Crypt::encryptString($value) : null;
}
public function getRefreshTokenAttribute(?string $value): ?string
{
return $value ? Crypt::decryptString($value) : null;
}
public function setRefreshTokenAttribute(?string $value): void
{
$this->attributes['refresh_token'] = $value ? Crypt::encryptString($value) : null;
}
}
+1
View File
@@ -4,6 +4,7 @@ namespace App\Providers;
use Illuminate\Foundation\Support\Providers\AuthServiceProvider as ServiceProvider;
use Illuminate\Support\Facades\Gate;
use Illuminate\Support\Facades\Route;
use Spatie\Permission\Models\Permission;
class AuthServiceProvider extends ServiceProvider
+3
View File
@@ -5,8 +5,11 @@ declare(strict_types=1);
namespace App\Services;
use App\Models\AppSetting;
use App\Models\Notifica;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\File;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Storage;
use RuntimeException;
use ZipArchive;
@@ -4,9 +4,7 @@ declare(strict_types=1);
namespace App\Services;
use App\Enums\GoogleService;
use App\Models\CalendarioConnessione;
use App\Services\GoogleOAuthService;
use App\Models\Evento;
use Carbon\Carbon;
use Google\Client as GoogleClient;
@@ -329,16 +327,6 @@ class GoogleCalendarSyncService
private function buildClient(CalendarioConnessione $connessione): GoogleClient
{
try {
$oauth = app(GoogleOAuthService::class);
$connection = $oauth->getConnection(GoogleService::Calendar);
if ($connection) {
return $oauth->buildClient(GoogleService::Calendar);
}
} catch (\Exception $e) {
\Illuminate\Support\Facades\Log::warning('Central Calendar OAuth failed, falling back: ' . $e->getMessage());
}
$config = $connessione->getDecryptedConfig();
$client = new GoogleClient();
-150
View File
@@ -1,150 +0,0 @@
<?php
declare(strict_types=1);
namespace App\Services;
use App\Enums\GoogleService;
use App\Models\GoogleOAuthConnection;
use Google\Client;
use Illuminate\Support\Facades\Log;
class GoogleOAuthService
{
private const TOKEN_URI = 'https://oauth2.googleapis.com/token';
private static function getRedirectUri(): string
{
$subfolder = config('app.url');
return rtrim($subfolder, '/') . '/auth/google/callback';
}
public function getAuthorizationUrl(GoogleService $service): string
{
$client = $this->createClient($service);
$client->setRedirectUri(self::getRedirectUri());
$client->setState($service->value);
$client->setAccessType('offline');
$client->setPrompt('consent');
$client->setIncludeGrantedScopes(true);
return $client->createAuthUrl();
}
public function handleCallback(string $code, GoogleService $service): GoogleOAuthConnection
{
$client = $this->createClient($service);
$client->setRedirectUri(self::getRedirectUri());
$token = $client->fetchAccessTokenWithAuthCode($code);
if (isset($token['error'])) {
throw new \RuntimeException('Google OAuth error: ' . ($token['error_description'] ?? $token['error']));
}
if (!isset($token['refresh_token'])) {
throw new \RuntimeException('No refresh token returned. Ensure access_type=offline and prompt=consent are set.');
}
$clientId = $client->getClientId();
$clientSecret = $client->getClientSecret();
return GoogleOAuthConnection::updateOrCreate(
['service' => $service->value],
[
'client_id' => $clientId,
'client_secret' => $clientSecret,
'refresh_token' => $token['refresh_token'],
]
);
}
public function revoke(GoogleService $service): void
{
$connection = $this->getConnection($service);
if (!$connection) {
return;
}
$client = $this->createClient($service);
$client->revokeToken();
$connection->delete();
}
public function getAccessToken(GoogleService $service): ?string
{
$connection = $this->getConnection($service);
if (!$connection) {
return null;
}
try {
$client = $this->createClient($service);
$client->setAccessToken([
'access_token' => '',
'refresh_token' => $connection->refresh_token,
'client_id' => $connection->client_id,
'client_secret' => $connection->client_secret,
]);
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
$newToken = $client->getAccessToken();
if (isset($newToken['refresh_token'])) {
$connection->refresh_token = $newToken['refresh_token'];
$connection->save();
}
}
$accessToken = $client->getAccessToken();
return $accessToken['access_token'] ?? null;
} catch (\Exception $e) {
Log::error('Google OAuth token refresh failed for ' . $service->value . ': ' . $e->getMessage());
return null;
}
}
public function isAuthorized(GoogleService $service): bool
{
return $this->getConnection($service) !== null;
}
public function getConnection(GoogleService $service): ?GoogleOAuthConnection
{
return GoogleOAuthConnection::where('service', $service->value)->first();
}
public function buildClient(GoogleService $service): Client
{
$client = $this->createClient($service);
$connection = $this->getConnection($service);
if ($connection) {
$client->setAccessToken([
'access_token' => '',
'refresh_token' => $connection->refresh_token,
'client_id' => $connection->client_id,
'client_secret' => $connection->client_secret,
]);
if ($client->isAccessTokenExpired()) {
$client->fetchAccessTokenWithRefreshToken($client->getRefreshToken());
}
}
return $client;
}
private function createClient(GoogleService $service): Client
{
$client = new Client();
$client->setApplicationName(config('app.name'));
$client->setScopes([$service->scope()]);
$client->setClientId(config('services.google.client_id'));
$client->setClientSecret(config('services.google.client_secret'));
$client->setRedirectUri(self::getRedirectUri());
return $client;
}
}
-62
View File
@@ -4,9 +4,7 @@ declare(strict_types=1);
namespace App\Services;
use App\Enums\GoogleService;
use App\Models\StorageRepository;
use App\Services\GoogleOAuthService;
use Illuminate\Support\Facades\Crypt;
use Illuminate\Support\Facades\Log;
use League\Flysystem\Filesystem;
@@ -71,20 +69,6 @@ class StorageRepositoryService
private function checkGoogleDriveApiStatus(array $config): ?string
{
try {
try {
$oauth = app(GoogleOAuthService::class);
$connection = $oauth->getConnection(GoogleService::Drive);
if ($connection) {
$client = $oauth->buildClient(GoogleService::Drive);
$service = new \Google_Service_Drive($client);
$rootId = $config['root_folder_id'] ?? 'root';
$service->files->get($rootId, ['fields' => 'id']);
return null;
}
} catch (\Exception $e) {
// fall through to config-based auth
}
$client = new \Google_Client();
$client->setClientId($config['client_id']);
$client->setClientSecret($config['client_secret']);
@@ -260,18 +244,6 @@ class StorageRepositoryService
{
$config = $repo->getDecryptedConfig();
try {
$oauth = app(GoogleOAuthService::class);
$connection = $oauth->getConnection(GoogleService::Drive);
if ($connection) {
$client = $oauth->buildClient(GoogleService::Drive);
$service = new \Google_Service_Drive($client);
return $this->readGoogleDriveFileWithService($service, $config, $normalizedPath, $basename);
}
} catch (\Exception $e) {
\Illuminate\Support\Facades\Log::warning('Central Google Drive OAuth failed in readGoogleDriveFile, falling back: ' . $e->getMessage());
}
$client = new \Google_Client();
$client->setClientId($config['client_id']);
$client->setClientSecret($config['client_secret']);
@@ -311,40 +283,6 @@ class StorageRepositoryService
];
}
private function readGoogleDriveFileWithService(\Google_Service_Drive $service, array $config, string $normalizedPath, string $basename): array
{
$rootFolderId = $config['root_folder_id'] ?? 'root';
$fileId = $this->findGoogleDriveFileId($service, $rootFolderId, $normalizedPath);
$file = $service->files->get($fileId, ['fields' => 'id,name,mimeType,size']);
$mimeType = $file->getMimeType();
if ($mimeType && str_starts_with($mimeType, 'application/vnd.google-apps.')) {
return $this->exportGoogleDriveDoc($service, $fileId, $mimeType, $basename);
}
$response = $service->files->get($fileId, ['alt' => 'media']);
$body = $response->getBody();
$stream = $body instanceof \Psr\Http\Message\StreamInterface ? $body->detach() : null;
if (!$stream) {
$stream = fopen('php://temp', 'r+');
fwrite($stream, (string) $body);
rewind($stream);
}
$fileSize = $file->getSize();
return [
'stream' => $stream,
'mimeType' => $mimeType,
'basename' => $basename,
'fileSize' => $fileSize !== null ? (int) $fileSize : null,
];
}
private function exportGoogleDriveDoc(\Google_Service_Drive $service, string $fileId, string $googleMimeType, string $originalBasename): array
{
$exportMap = [
Regular → Executable
-33
View File
@@ -94,36 +94,3 @@ echo " php artisan view:clear"
echo ""
echo " # 6. Avvia installazione MySQL"
echo " php install.php"
echo ""
echo "=== AGGIORNAMENTO (SERVER ESISTENTE) ==="
echo ""
echo " 1. BACKUP:"
echo " cp .env .env.backup.$(date +%Y%m%d_%H%M%S)"
echo " mysqldump -u USER -p DBNAME > backup_$(date +%Y%m%d_%H%M%S).sql"
echo ""
echo " 2. Estrai archivio (sovrascrive):"
echo " tar xzf ${ARCHIVE}"
echo ""
echo " 3. Ripristina .env e aggiungi nuove variabili:"
echo " cp .env.backup.* .env"
echo " # Aggiungi se mancanti:"
echo ' echo "GOOGLE_CLIENT_ID=" >> .env'
echo ' echo "GOOGLE_CLIENT_SECRET=" >> .env'
echo ""
echo " 4. Permessi:"
echo ' chmod -R 775 storage bootstrap/cache'
echo ' chown -R www-data:www-data storage bootstrap/cache'
echo ""
echo " 5. Ricrea symlink storage:"
echo ' rm -f public/storage'
echo ' ln -sf ../storage/app/public public/storage'
echo ""
echo " 6. Esegui migration DB:"
echo " php artisan migrate --force"
echo ""
echo " 7. Pulisci cache e ricostruisci asset:"
echo " php artisan config:clear"
echo " php artisan route:clear"
echo " php artisan view:clear"
echo " # npm install && npm run build (se serve ricostruire Vite)"
-5
View File
@@ -35,9 +35,4 @@ return [
],
],
'google' => [
'client_id' => env('GOOGLE_CLIENT_ID'),
'client_secret' => env('GOOGLE_CLIENT_SECRET'),
],
];
@@ -8,6 +8,10 @@ return new class extends Migration
{
public function up(): void
{
if (Schema::hasTable('tenants')) {
return;
}
Schema::create('tenants', function (Blueprint $table) {
$table->id();
$table->string('nome');
@@ -8,26 +8,30 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('diocesi', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('regione')->nullable();
$table->timestamps();
});
if (!Schema::hasTable('diocesi')) {
Schema::create('diocesi', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('regione')->nullable();
$table->timestamps();
});
}
Schema::create('comuni', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('codice_istat', 10)->nullable();
$table->string('cap')->nullable();
$table->string('sigla_provincia', 2)->nullable();
$table->string('regione')->nullable();
$table->float('latitudine')->nullable();
$table->float('longitudine')->nullable();
$table->timestamps();
$table->index('nome');
$table->index('sigla_provincia');
});
if (!Schema::hasTable('comuni')) {
Schema::create('comuni', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('codice_istat', 10)->nullable();
$table->string('cap')->nullable();
$table->string('sigla_provincia', 2)->nullable();
$table->string('regione')->nullable();
$table->float('latitudine')->nullable();
$table->float('longitudine')->nullable();
$table->timestamps();
$table->index('nome');
$table->index('sigla_provincia');
});
}
}
public function down(): void
@@ -8,27 +8,29 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('individui', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('codice_id', 5)->unique();
$table->string('cognome');
$table->string('nome');
$table->date('data_nascita')->nullable();
$table->string('indirizzo')->nullable();
$table->string('cap', 10)->nullable();
$table->string('città')->nullable();
$table->string('sigla_provincia', 2)->nullable();
$table->enum('genere', ['M', 'F'])->nullable();
$table->enum('tipo_documento', ['carta_identita', 'patente'])->nullable();
$table->date('scadenza_documento')->nullable();
$table->text('note')->nullable();
$table->string('avatar_path')->nullable();
$table->timestamps();
$table->index('tenant_id');
$table->index('codice_id');
$table->index('cognome');
});
if (!Schema::hasTable('individui')) {
Schema::create('individui', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('codice_id', 5)->unique();
$table->string('cognome');
$table->string('nome');
$table->date('data_nascita')->nullable();
$table->string('indirizzo')->nullable();
$table->string('cap', 10)->nullable();
$table->string('città')->nullable();
$table->string('sigla_provincia', 2)->nullable();
$table->enum('genere', ['M', 'F'])->nullable();
$table->enum('tipo_documento', ['carta_identita', 'patente'])->nullable();
$table->date('scadenza_documento')->nullable();
$table->text('note')->nullable();
$table->string('avatar_path')->nullable();
$table->timestamps();
$table->index('tenant_id');
$table->index('codice_id');
$table->index('cognome');
});
}
}
public function down(): void
@@ -8,16 +8,18 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('contatti', function (Blueprint $table) {
$table->id();
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
$table->enum('tipo', ['telefono', 'cellulare', 'email', 'fax', 'web', 'telegram', 'whatsapp', 'altro']);
$table->string('valore');
$table->string('etichetta')->nullable();
$table->boolean('is_primary')->default(false);
$table->timestamps();
$table->index('individuo_id');
});
if (!Schema::hasTable('contatti')) {
Schema::create('contatti', function (Blueprint $table) {
$table->id();
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
$table->enum('tipo', ['telefono', 'cellulare', 'email', 'fax', 'web', 'telegram', 'whatsapp', 'altro']);
$table->string('valore');
$table->string('etichetta')->nullable();
$table->boolean('is_primary')->default(false);
$table->timestamps();
$table->index('individuo_id');
});
}
}
public function down(): void
@@ -8,24 +8,26 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('gruppi', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->foreignId('parent_id')->nullable()->constrained('gruppi')->onDelete('set null');
$table->foreignId('diocesi_id')->nullable()->constrained('diocesi')->onDelete('set null');
$table->foreignId('responsabile_id')->nullable()->constrained('individui')->onDelete('set null');
$table->string('nome');
$table->text('descrizione')->nullable();
$table->string('indirizzo_incontro')->nullable();
$table->string('cap_incontro', 10)->nullable();
$table->string('città_incontro')->nullable();
$table->string('sigla_provincia_incontro', 2)->nullable();
$table->string('mappa_posizione')->nullable();
$table->json('metadata')->nullable();
$table->timestamps();
$table->index('tenant_id');
$table->index('parent_id');
});
if (!Schema::hasTable('gruppi')) {
Schema::create('gruppi', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->foreignId('parent_id')->nullable()->constrained('gruppi')->onDelete('set null');
$table->foreignId('diocesi_id')->nullable()->constrained('diocesi')->onDelete('set null');
$table->foreignId('responsabile_id')->nullable()->constrained('individui')->onDelete('set null');
$table->string('nome');
$table->text('descrizione')->nullable();
$table->string('indirizzo_incontro')->nullable();
$table->string('cap_incontro', 10)->nullable();
$table->string('città_incontro')->nullable();
$table->string('sigla_provincia_incontro', 2)->nullable();
$table->string('mappa_posizione')->nullable();
$table->json('metadata')->nullable();
$table->timestamps();
$table->index('tenant_id');
$table->index('parent_id');
});
}
}
public function down(): void
@@ -8,15 +8,17 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('gruppo_individuo', function (Blueprint $table) {
$table->id();
$table->foreignId('gruppo_id')->constrained('gruppi')->onDelete('cascade');
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
$table->string('ruolo_nel_gruppo')->nullable();
$table->date('data_adesione')->nullable();
$table->timestamps();
$table->unique(['gruppo_id', 'individuo_id']);
});
if (!Schema::hasTable('gruppo_individuo')) {
Schema::create('gruppo_individuo', function (Blueprint $table) {
$table->id();
$table->foreignId('gruppo_id')->constrained('gruppi')->onDelete('cascade');
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
$table->string('ruolo_nel_gruppo')->nullable();
$table->date('data_adesione')->nullable();
$table->timestamps();
$table->unique(['gruppo_id', 'individuo_id']);
});
}
}
public function down(): void
@@ -8,35 +8,41 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('eventi', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('nome_evento');
$table->string('tipo_evento')->nullable();
$table->enum('tipo_recorrenza', ['singolo', 'ricorrente'])->default('singolo');
$table->unsignedTinyInteger('giorno_settimana')->nullable();
$table->time('ora_inizio')->nullable();
$table->date('data_specifica')->nullable();
$table->unsignedInteger('durata_minuti')->nullable();
$table->text('descrizione')->nullable();
$table->text('note')->nullable();
$table->timestamps();
$table->index('tenant_id');
});
if (!Schema::hasTable('eventi')) {
Schema::create('eventi', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('nome_evento');
$table->string('tipo_evento')->nullable();
$table->enum('tipo_recorrenza', ['singolo', 'ricorrente'])->default('singolo');
$table->unsignedTinyInteger('giorno_settimana')->nullable();
$table->time('ora_inizio')->nullable();
$table->date('data_specifica')->nullable();
$table->unsignedInteger('durata_minuti')->nullable();
$table->text('descrizione')->nullable();
$table->text('note')->nullable();
$table->timestamps();
$table->index('tenant_id');
});
}
Schema::create('eventi_gruppi', function (Blueprint $table) {
$table->id();
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
$table->foreignId('gruppo_id')->constrained('gruppi')->onDelete('cascade');
$table->timestamps();
});
if (!Schema::hasTable('eventi_gruppi')) {
Schema::create('eventi_gruppi', function (Blueprint $table) {
$table->id();
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
$table->foreignId('gruppo_id')->constrained('gruppi')->onDelete('cascade');
$table->timestamps();
});
}
Schema::create('eventi_responsabili', function (Blueprint $table) {
$table->id();
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
$table->timestamps();
});
if (!Schema::hasTable('eventi_responsabili')) {
Schema::create('eventi_responsabili', function (Blueprint $table) {
$table->id();
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
$table->timestamps();
});
}
}
public function down(): void
@@ -8,22 +8,24 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('documenti', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('nome_file');
$table->string('file_path');
$table->string('tipo')->nullable();
$table->enum('tipologia', ['avatar', 'galleria', 'documento', 'statuto', 'altro'])->default('documento');
$table->enum('visibilita', ['pubblico', 'gruppo', 'individuo', 'associazione', 'federazione'])->default('gruppo');
$table->unsignedBigInteger('visibilita_target_id')->nullable();
$table->string('visibilita_target_type')->nullable();
$table->string('mime_type')->nullable();
$table->unsignedBigInteger('dimensione')->nullable();
$table->text('note')->nullable();
$table->timestamps();
$table->index('tenant_id');
});
if (!Schema::hasTable('documenti')) {
Schema::create('documenti', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('nome_file');
$table->string('file_path');
$table->string('tipo')->nullable();
$table->enum('tipologia', ['avatar', 'galleria', 'documento', 'statuto', 'altro'])->default('documento');
$table->enum('visibilita', ['pubblico', 'gruppo', 'individuo', 'associazione', 'federazione'])->default('gruppo');
$table->unsignedBigInteger('visibilita_target_id')->nullable();
$table->string('visibilita_target_type')->nullable();
$table->string('mime_type')->nullable();
$table->unsignedBigInteger('dimensione')->nullable();
$table->text('note')->nullable();
$table->timestamps();
$table->index('tenant_id');
});
}
}
public function down(): void
@@ -8,43 +8,49 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('mailing_lists', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('nome');
$table->text('descrizione')->nullable();
$table->boolean('attiva')->default(true);
$table->timestamps();
$table->index('tenant_id');
});
if (!Schema::hasTable('mailing_lists')) {
Schema::create('mailing_lists', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('nome');
$table->text('descrizione')->nullable();
$table->boolean('attiva')->default(true);
$table->timestamps();
$table->index('tenant_id');
});
}
Schema::create('mailing_contacts', function (Blueprint $table) {
$table->id();
$table->foreignId('mailing_list_id')->constrained('mailing_lists')->onDelete('cascade');
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
$table->boolean('opt_in')->default(true);
$table->dateTime('opt_in_data')->nullable();
$table->dateTime('opt_out_data')->nullable();
$table->timestamps();
$table->unique(['mailing_list_id', 'individuo_id']);
});
if (!Schema::hasTable('mailing_contacts')) {
Schema::create('mailing_contacts', function (Blueprint $table) {
$table->id();
$table->foreignId('mailing_list_id')->constrained('mailing_lists')->onDelete('cascade');
$table->foreignId('individuo_id')->constrained('individui')->onDelete('cascade');
$table->boolean('opt_in')->default(true);
$table->dateTime('opt_in_data')->nullable();
$table->dateTime('opt_out_data')->nullable();
$table->timestamps();
$table->unique(['mailing_list_id', 'individuo_id']);
});
}
Schema::create('mailing_messaggi', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->foreignId('mailing_list_id')->nullable()->constrained('mailing_lists')->onDelete('set null');
$table->unsignedBigInteger('user_id')->nullable();
$table->string('oggetto');
$table->text('corpo')->nullable();
$table->enum('canale', ['email', 'telegram'])->default('email');
$table->enum('stato', ['bozza', 'in_coda', 'inviato', 'fallito'])->default('bozza');
$table->timestamp('inviato_al')->nullable();
$table->integer('totale_destinatari')->default(0);
$table->integer('invii_ok')->default(0);
$table->integer('invii_falliti')->default(0);
$table->timestamps();
$table->index('tenant_id');
});
if (!Schema::hasTable('mailing_messaggi')) {
Schema::create('mailing_messaggi', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->foreignId('mailing_list_id')->nullable()->constrained('mailing_lists')->onDelete('set null');
$table->unsignedBigInteger('user_id')->nullable();
$table->string('oggetto');
$table->text('corpo')->nullable();
$table->enum('canale', ['email', 'telegram'])->default('email');
$table->enum('stato', ['bozza', 'in_coda', 'inviato', 'fallito'])->default('bozza');
$table->timestamp('inviato_al')->nullable();
$table->integer('totale_destinatari')->default(0);
$table->integer('invii_ok')->default(0);
$table->integer('invii_falliti')->default(0);
$table->timestamps();
$table->index('tenant_id');
});
}
}
public function down(): void
@@ -8,18 +8,20 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('notifiche', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id')->nullable();
$table->string('tipo');
$table->string('titolo');
$table->text('messaggio');
$table->string('link')->nullable();
$table->boolean('is_read')->default(false);
$table->timestamp('read_at')->nullable();
$table->timestamps();
$table->index('user_id');
});
if (!Schema::hasTable('notifiche')) {
Schema::create('notifiche', function (Blueprint $table) {
$table->id();
$table->unsignedBigInteger('user_id')->nullable();
$table->string('tipo');
$table->string('titolo');
$table->text('messaggio');
$table->string('link')->nullable();
$table->boolean('is_read')->default(false);
$table->timestamp('read_at')->nullable();
$table->timestamps();
$table->index('user_id');
});
}
}
public function down(): void
@@ -8,34 +8,40 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->boolean('is_admin')->default(false);
$table->rememberToken();
$table->timestamps();
$table->index('tenant_id');
$table->index('email');
});
if (!Schema::hasTable('users')) {
Schema::create('users', function (Blueprint $table) {
$table->id();
$table->foreignId('tenant_id')->nullable()->constrained('tenants')->onDelete('set null');
$table->string('name');
$table->string('email')->unique();
$table->timestamp('email_verified_at')->nullable();
$table->string('password');
$table->boolean('is_admin')->default(false);
$table->rememberToken();
$table->timestamps();
$table->index('tenant_id');
$table->index('email');
});
}
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
if (!Schema::hasTable('password_reset_tokens')) {
Schema::create('password_reset_tokens', function (Blueprint $table) {
$table->string('email')->primary();
$table->string('token');
$table->timestamp('created_at')->nullable();
});
}
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
if (!Schema::hasTable('sessions')) {
Schema::create('sessions', function (Blueprint $table) {
$table->string('id')->primary();
$table->foreignId('user_id')->nullable()->index();
$table->string('ip_address', 45)->nullable();
$table->text('user_agent')->nullable();
$table->longText('payload');
$table->integer('last_activity')->index();
});
}
}
public function down(): void
@@ -8,50 +8,60 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration');
});
if (!Schema::hasTable('cache')) {
Schema::create('cache', function (Blueprint $table) {
$table->string('key')->primary();
$table->mediumText('value');
$table->integer('expiration');
});
}
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
if (!Schema::hasTable('cache_locks')) {
Schema::create('cache_locks', function (Blueprint $table) {
$table->string('key')->primary();
$table->string('owner');
$table->integer('expiration');
});
}
Schema::create('jobs', function (Blueprint $table) {
$table->id();
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
if (!Schema::hasTable('jobs')) {
Schema::create('jobs', function (Blueprint $table) {
$table->id();
$table->string('queue')->index();
$table->longText('payload');
$table->unsignedTinyInteger('attempts');
$table->unsignedInteger('reserved_at')->nullable();
$table->unsignedInteger('available_at');
$table->unsignedInteger('created_at');
});
}
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
if (!Schema::hasTable('job_batches')) {
Schema::create('job_batches', function (Blueprint $table) {
$table->string('id')->primary();
$table->string('name');
$table->integer('total_jobs');
$table->integer('pending_jobs');
$table->integer('failed_jobs');
$table->longText('failed_job_ids');
$table->mediumText('options')->nullable();
$table->integer('cancelled_at')->nullable();
$table->integer('created_at');
$table->integer('finished_at')->nullable();
});
}
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at');
});
if (!Schema::hasTable('failed_jobs')) {
Schema::create('failed_jobs', function (Blueprint $table) {
$table->id();
$table->string('uuid')->unique();
$table->text('connection');
$table->text('queue');
$table->longText('payload');
$table->longText('exception');
$table->timestamp('failed_at');
});
}
}
public function down(): void
@@ -8,12 +8,14 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('eventi_documenti', function (Blueprint $table) {
$table->id();
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
$table->foreignId('documento_id')->constrained('documenti')->onDelete('cascade');
$table->timestamps();
});
if (!Schema::hasTable('eventi_documenti')) {
Schema::create('eventi_documenti', function (Blueprint $table) {
$table->id();
$table->foreignId('evento_id')->constrained('eventi')->onDelete('cascade');
$table->foreignId('documento_id')->constrained('documenti')->onDelete('cascade');
$table->timestamps();
});
}
}
public function down(): void
@@ -8,19 +8,21 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('viste_report', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->string('nome');
$table->string('tipo'); // individui, gruppi, documenti, eventi
$table->json('colonne_visibili')->nullable();
$table->json('colonne_ordinamento')->nullable(); // [['colonna', 'direzione']]
$table->json('filtri')->nullable(); // [['colonna', 'operatore', 'valore']]
$table->string('ricerca')->nullable();
$table->timestamps();
$table->index('tipo');
$table->index('user_id');
});
if (!Schema::hasTable('viste_report')) {
Schema::create('viste_report', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->string('nome');
$table->string('tipo'); // individui, gruppi, documenti, eventi
$table->json('colonne_visibili')->nullable();
$table->json('colonne_ordinamento')->nullable(); // [['colonna', 'direzione']]
$table->json('filtri')->nullable(); // [['colonna', 'operatore', 'valore']]
$table->string('ricerca')->nullable();
$table->timestamps();
$table->index('tipo');
$table->index('user_id');
});
}
}
public function down(): void
@@ -8,14 +8,16 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('tipologie_documenti', function (Blueprint $table) {
$table->id();
$table->string('nome', 50)->unique();
$table->string('descrizione', 255)->nullable();
$table->integer('ordine')->default(0);
$table->boolean('attiva')->default(true);
$table->timestamps();
});
if (!Schema::hasTable('tipologie_documenti')) {
Schema::create('tipologie_documenti', function (Blueprint $table) {
$table->id();
$table->string('nome', 50)->unique();
$table->string('descrizione', 255)->nullable();
$table->integer('ordine')->default(0);
$table->boolean('attiva')->default(true);
$table->timestamps();
});
}
$defaultTipologie = ['avatar', 'galleria', 'documento', 'statuto', 'altro'];
foreach ($defaultTipologie as $i => $nome) {
@@ -14,28 +14,32 @@ return new class extends Migration
});
}
Schema::create('activity_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('action');
$table->string('module');
$table->string('description')->nullable();
$table->json('details')->nullable();
$table->string('ip_address', 45)->nullable();
$table->timestamp('created_at')->useCurrent();
if (!Schema::hasTable('activity_logs')) {
Schema::create('activity_logs', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained()->onDelete('cascade');
$table->string('action');
$table->string('module');
$table->string('description')->nullable();
$table->json('details')->nullable();
$table->string('ip_address', 45)->nullable();
$table->timestamp('created_at')->useCurrent();
$table->index(['user_id', 'module']);
$table->index(['created_at']);
$table->index(['action']);
});
}
$table->index(['user_id', 'module']);
$table->index(['created_at']);
$table->index(['action']);
});
Schema::create('role_presets', function (Blueprint $table) {
$table->id();
$table->string('name', 50)->unique();
$table->string('description')->nullable();
$table->json('permissions');
$table->timestamps();
});
if (!Schema::hasTable('role_presets')) {
Schema::create('role_presets', function (Blueprint $table) {
$table->id();
$table->string('name', 50)->unique();
$table->string('description')->nullable();
$table->json('permissions');
$table->timestamps();
});
}
}
public function down(): void
@@ -23,96 +23,106 @@ return new class extends Migration
/**
* See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
*/
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
$table->id(); // permission id
$table->string('name');
$table->string('guard_name');
$table->timestamps();
$table->unique(['name', 'guard_name']);
});
if (!Schema::hasTable($tableNames['permissions'])) {
Schema::create($tableNames['permissions'], static function (Blueprint $table) {
$table->id(); // permission id
$table->string('name');
$table->string('guard_name');
$table->timestamps();
$table->unique(['name', 'guard_name']);
});
}
/**
* See `docs/prerequisites.md` for suggested lengths on 'name' and 'guard_name' if "1071 Specified key was too long" errors are encountered.
*/
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
$table->id(); // role id
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
if (!Schema::hasTable($tableNames['roles'])) {
Schema::create($tableNames['roles'], static function (Blueprint $table) use ($teams, $columnNames) {
$table->id(); // role id
if ($teams || config('permission.testing')) { // permission.testing is a fix for sqlite testing
$table->unsignedBigInteger($columnNames['team_foreign_key'])->nullable();
$table->index($columnNames['team_foreign_key'], 'roles_team_foreign_key_index');
}
$table->string('name');
$table->string('guard_name');
$table->timestamps();
if ($teams || config('permission.testing')) {
}
$table->string('name');
$table->string('guard_name');
$table->timestamps();
if ($teams || config('permission.testing')) {
$table->unique([$columnNames['team_foreign_key'], 'name', 'guard_name']);
} else {
} else {
$table->unique(['name', 'guard_name']);
}
});
}
});
}
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
$table->unsignedBigInteger($pivotPermission);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
$table->foreign($pivotPermission)
if (!Schema::hasTable($tableNames['model_has_permissions'])) {
Schema::create($tableNames['model_has_permissions'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotPermission, $teams) {
$table->unsignedBigInteger($pivotPermission);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_permissions_model_id_model_type_index');
$table->foreign($pivotPermission)
->references('id') // permission id
->on($tableNames['permissions'])
->cascadeOnDelete();
if ($teams) {
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_permissions_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], $pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
} else {
} else {
$table->primary([$pivotPermission, $columnNames['model_morph_key'], 'model_type'],
'model_has_permissions_permission_model_type_primary');
}
});
}
});
}
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
$table->unsignedBigInteger($pivotRole);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
$table->foreign($pivotRole)
if (!Schema::hasTable($tableNames['model_has_roles'])) {
Schema::create($tableNames['model_has_roles'], static function (Blueprint $table) use ($tableNames, $columnNames, $pivotRole, $teams) {
$table->unsignedBigInteger($pivotRole);
$table->string('model_type');
$table->unsignedBigInteger($columnNames['model_morph_key']);
$table->index([$columnNames['model_morph_key'], 'model_type'], 'model_has_roles_model_id_model_type_index');
$table->foreign($pivotRole)
->references('id') // role id
->on($tableNames['roles'])
->cascadeOnDelete();
if ($teams) {
if ($teams) {
$table->unsignedBigInteger($columnNames['team_foreign_key']);
$table->index($columnNames['team_foreign_key'], 'model_has_roles_team_foreign_key_index');
$table->primary([$columnNames['team_foreign_key'], $pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
} else {
} else {
$table->primary([$pivotRole, $columnNames['model_morph_key'], 'model_type'],
'model_has_roles_role_model_type_primary');
}
});
}
});
}
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
$table->unsignedBigInteger($pivotPermission);
$table->unsignedBigInteger($pivotRole);
$table->foreign($pivotPermission)
if (!Schema::hasTable($tableNames['role_has_permissions'])) {
Schema::create($tableNames['role_has_permissions'], static function (Blueprint $table) use ($tableNames, $pivotRole, $pivotPermission) {
$table->unsignedBigInteger($pivotPermission);
$table->unsignedBigInteger($pivotRole);
$table->foreign($pivotPermission)
->references('id') // permission id
->on($tableNames['permissions'])
->cascadeOnDelete();
$table->foreign($pivotRole)
$table->foreign($pivotRole)
->references('id') // role id
->on($tableNames['roles'])
->cascadeOnDelete();
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
});
$table->primary([$pivotPermission, $pivotRole], 'role_has_permissions_permission_id_role_id_primary');
});
}
app('cache')
->store(config('permission.cache.store') != 'default' ? config('permission.cache.store') : null)
@@ -8,26 +8,28 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('email_settings', function (Blueprint $table) {
$table->id();
$table->string('imap_host')->nullable();
$table->integer('imap_port')->nullable()->default(993);
$table->string('imap_encryption')->nullable()->default('ssl');
$table->string('imap_username')->nullable();
$table->string('imap_password')->nullable();
$table->string('smtp_host')->nullable();
$table->integer('smtp_port')->nullable()->default(587);
$table->string('smtp_encryption')->nullable()->default('tls');
$table->string('smtp_username')->nullable();
$table->string('smtp_password')->nullable();
$table->string('email_address')->nullable();
$table->string('email_name')->nullable();
$table->string('reply_to')->nullable();
$table->integer('sync_interval_minutes')->default(5);
$table->timestamp('last_sync_at')->nullable();
$table->boolean('is_active')->default(false);
$table->timestamps();
});
if (!Schema::hasTable('email_settings')) {
Schema::create('email_settings', function (Blueprint $table) {
$table->id();
$table->string('imap_host')->nullable();
$table->integer('imap_port')->nullable()->default(993);
$table->string('imap_encryption')->nullable()->default('ssl');
$table->string('imap_username')->nullable();
$table->string('imap_password')->nullable();
$table->string('smtp_host')->nullable();
$table->integer('smtp_port')->nullable()->default(587);
$table->string('smtp_encryption')->nullable()->default('tls');
$table->string('smtp_username')->nullable();
$table->string('smtp_password')->nullable();
$table->string('email_address')->nullable();
$table->string('email_name')->nullable();
$table->string('reply_to')->nullable();
$table->integer('sync_interval_minutes')->default(5);
$table->timestamp('last_sync_at')->nullable();
$table->boolean('is_active')->default(false);
$table->timestamps();
});
}
}
public function down(): void
@@ -8,17 +8,19 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('email_folders', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('imap_folder_name')->nullable();
$table->string('type')->default('custom');
$table->string('icon')->default('fa-folder');
$table->string('color')->nullable();
$table->integer('sort_order')->default(0);
$table->boolean('is_system')->default(false);
$table->timestamps();
});
if (!Schema::hasTable('email_folders')) {
Schema::create('email_folders', function (Blueprint $table) {
$table->id();
$table->string('name');
$table->string('imap_folder_name')->nullable();
$table->string('type')->default('custom');
$table->string('icon')->default('fa-folder');
$table->string('color')->nullable();
$table->integer('sort_order')->default(0);
$table->boolean('is_system')->default(false);
$table->timestamps();
});
}
}
public function down(): void
@@ -8,35 +8,37 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('email_messages', function (Blueprint $table) {
$table->id();
$table->foreignId('email_folder_id')->constrained('email_folders')->onDelete('cascade');
$table->string('message_id')->nullable()->index();
$table->string('subject')->nullable();
$table->string('from_name')->nullable();
$table->string('from_email')->nullable();
$table->string('to_name')->nullable();
$table->string('to_email')->nullable();
$table->text('cc')->nullable();
$table->text('bcc')->nullable();
$table->longText('body_text')->nullable();
$table->longText('body_html')->nullable();
$table->boolean('is_read')->default(false);
$table->boolean('is_starred')->default(false);
$table->boolean('is_important')->default(false);
$table->boolean('is_sent')->default(false);
$table->boolean('is_draft')->default(false);
$table->boolean('is_trash')->default(false);
$table->timestamp('received_at')->nullable();
$table->timestamp('sent_at')->nullable();
$table->unsignedBigInteger('imap_uid')->nullable();
$table->timestamps();
$table->index('is_read');
$table->index('is_starred');
$table->index('is_sent');
$table->index('received_at');
});
if (!Schema::hasTable('email_messages')) {
Schema::create('email_messages', function (Blueprint $table) {
$table->id();
$table->foreignId('email_folder_id')->constrained('email_folders')->onDelete('cascade');
$table->string('message_id')->nullable()->index();
$table->string('subject')->nullable();
$table->string('from_name')->nullable();
$table->string('from_email')->nullable();
$table->string('to_name')->nullable();
$table->string('to_email')->nullable();
$table->text('cc')->nullable();
$table->text('bcc')->nullable();
$table->longText('body_text')->nullable();
$table->longText('body_html')->nullable();
$table->boolean('is_read')->default(false);
$table->boolean('is_starred')->default(false);
$table->boolean('is_important')->default(false);
$table->boolean('is_sent')->default(false);
$table->boolean('is_draft')->default(false);
$table->boolean('is_trash')->default(false);
$table->timestamp('received_at')->nullable();
$table->timestamp('sent_at')->nullable();
$table->unsignedBigInteger('imap_uid')->nullable();
$table->timestamps();
$table->index('is_read');
$table->index('is_starred');
$table->index('is_sent');
$table->index('received_at');
});
}
}
public function down(): void
@@ -8,17 +8,19 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('email_attachments', function (Blueprint $table) {
$table->id();
$table->foreignId('email_message_id')->constrained('email_messages')->onDelete('cascade');
$table->string('filename');
$table->string('mime_type')->nullable();
$table->bigInteger('size')->default(0);
$table->string('file_path');
$table->boolean('is_saved_to_documenti')->default(false);
$table->foreignId('documenti_id')->nullable()->constrained('documenti')->onDelete('set null');
$table->timestamps();
});
if (!Schema::hasTable('email_attachments')) {
Schema::create('email_attachments', function (Blueprint $table) {
$table->id();
$table->foreignId('email_message_id')->constrained('email_messages')->onDelete('cascade');
$table->string('filename');
$table->string('mime_type')->nullable();
$table->bigInteger('size')->default(0);
$table->string('file_path');
$table->boolean('is_saved_to_documenti')->default(false);
$table->foreignId('documenti_id')->nullable()->constrained('documenti')->onDelete('set null');
$table->timestamps();
});
}
}
public function down(): void
@@ -8,14 +8,16 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('ruoli', function (Blueprint $table) {
$table->id();
$table->string('nome', 50)->unique();
$table->string('descrizione', 255)->nullable();
$table->integer('ordine')->default(0);
$table->boolean('attiva')->default(true);
$table->timestamps();
});
if (!Schema::hasTable('ruoli')) {
Schema::create('ruoli', function (Blueprint $table) {
$table->id();
$table->string('nome', 50)->unique();
$table->string('descrizione', 255)->nullable();
$table->integer('ordine')->default(0);
$table->boolean('attiva')->default(true);
$table->timestamps();
});
}
$defaultRuoli = [
['nome' => 'Membro', 'descrizione' => 'Membro del gruppo'],
@@ -8,15 +8,17 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('report_custom', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->string('nome');
$table->text('descrizione')->nullable();
$table->string('tipo_report');
$table->json('config')->nullable();
$table->timestamps();
});
if (!Schema::hasTable('report_custom')) {
Schema::create('report_custom', function (Blueprint $table) {
$table->id();
$table->foreignId('user_id')->constrained('users')->onDelete('cascade');
$table->string('nome');
$table->text('descrizione')->nullable();
$table->string('tipo_report');
$table->json('config')->nullable();
$table->timestamps();
});
}
}
public function down(): void
@@ -10,14 +10,16 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('tipologie_eventi', function (Blueprint $table) {
$table->id();
$table->string('nome', 50)->unique();
$table->string('descrizione', 255)->nullable();
$table->integer('ordine')->default(0);
$table->boolean('attiva')->default(true);
$table->timestamps();
});
if (!Schema::hasTable('tipologie_eventi')) {
Schema::create('tipologie_eventi', function (Blueprint $table) {
$table->id();
$table->string('nome', 50)->unique();
$table->string('descrizione', 255)->nullable();
$table->integer('ordine')->default(0);
$table->boolean('attiva')->default(true);
$table->timestamps();
});
}
DB::table('tipologie_eventi')->insert([
['nome' => 'catechesi', 'descrizione' => 'Catechesi', 'ordine' => 0, 'attiva' => true],
@@ -10,21 +10,23 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('sender_accounts', function (Blueprint $table) {
$table->id();
$table->string('email_address')->unique();
$table->string('email_name')->nullable();
$table->string('smtp_host')->nullable();
$table->integer('smtp_port')->default(587);
$table->string('smtp_encryption')->default('tls');
$table->string('smtp_username')->nullable();
$table->text('smtp_password')->nullable();
$table->string('reply_to')->nullable();
$table->string('verify_email')->nullable();
$table->text('note')->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
});
if (!Schema::hasTable('sender_accounts')) {
Schema::create('sender_accounts', function (Blueprint $table) {
$table->id();
$table->string('email_address')->unique();
$table->string('email_name')->nullable();
$table->string('smtp_host')->nullable();
$table->integer('smtp_port')->default(587);
$table->string('smtp_encryption')->default('tls');
$table->string('smtp_username')->nullable();
$table->text('smtp_password')->nullable();
$table->string('reply_to')->nullable();
$table->string('verify_email')->nullable();
$table->text('note')->nullable();
$table->boolean('is_active')->default(true);
$table->timestamps();
});
}
}
public function down(): void
@@ -8,12 +8,14 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('documenti_cartelle', function (Blueprint $table) {
$table->id();
$table->foreignId('parent_id')->nullable()->constrained('documenti_cartelle')->onDelete('cascade');
$table->string('nome');
$table->timestamps();
});
if (!Schema::hasTable('documenti_cartelle')) {
Schema::create('documenti_cartelle', function (Blueprint $table) {
$table->id();
$table->foreignId('parent_id')->nullable()->constrained('documenti_cartelle')->onDelete('cascade');
$table->string('nome');
$table->timestamps();
});
}
}
public function down(): void
@@ -8,15 +8,17 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('storage_repositories', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('tipo', 50);
$table->text('config');
$table->boolean('is_active')->default(true);
$table->integer('ordine')->default(0);
$table->timestamps();
});
if (!Schema::hasTable('storage_repositories')) {
Schema::create('storage_repositories', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('tipo', 50);
$table->text('config');
$table->boolean('is_active')->default(true);
$table->integer('ordine')->default(0);
$table->timestamps();
});
}
}
public function down(): void
@@ -10,21 +10,23 @@ return new class extends Migration
{
public function up(): void
{
Schema::create('calendario_connessioni', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('tipo'); // google_calendar, caldav
$table->text('config'); // JSON
$table->string('stato')->default('disconnesso'); // connesso, disconnesso, errore
$table->string('sync_direction')->default('bidirectional'); // import, export, bidirectional
$table->integer('sync_interval_minutes')->default(60);
$table->timestamp('last_sync_at')->nullable();
$table->timestamp('last_error_at')->nullable();
$table->text('last_error_message')->nullable();
$table->boolean('is_active')->default(true);
$table->integer('ordine')->default(0);
$table->timestamps();
});
if (!Schema::hasTable('calendario_connessioni')) {
Schema::create('calendario_connessioni', function (Blueprint $table) {
$table->id();
$table->string('nome');
$table->string('tipo'); // google_calendar, caldav
$table->text('config'); // JSON
$table->string('stato')->default('disconnesso'); // connesso, disconnesso, errore
$table->string('sync_direction')->default('bidirectional'); // import, export, bidirectional
$table->integer('sync_interval_minutes')->default(60);
$table->timestamp('last_sync_at')->nullable();
$table->timestamp('last_error_at')->nullable();
$table->text('last_error_message')->nullable();
$table->boolean('is_active')->default(true);
$table->integer('ordine')->default(0);
$table->timestamps();
});
}
}
public function down(): void
@@ -18,7 +18,7 @@ return new class extends Migration
$table->string('color', 7)->nullable();
$table->integer('order_column')->default(0);
$table->timestamps();
});
});
}
if (!Schema::hasTable('taggables')) {
@@ -26,7 +26,7 @@ return new class extends Migration
$table->foreignId('tag_id')->constrained()->cascadeOnDelete();
$table->morphs('taggable');
$table->primary(['tag_id', 'taggable_id', 'taggable_type']);
});
});
}
}
@@ -1,27 +0,0 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::create('google_oauth_connections', function (Blueprint $table) {
$table->id();
$table->string('service', 20)->unique();
$table->string('client_id', 255);
$table->text('client_secret');
$table->text('refresh_token');
$table->timestamps();
});
}
public function down(): void
{
Schema::dropIfExists('google_oauth_connections');
}
};
@@ -1,24 +0,0 @@
<?php
declare(strict_types=1);
use Illuminate\Database\Migrations\Migration;
use Illuminate\Database\Schema\Blueprint;
use Illuminate\Support\Facades\Schema;
return new class extends Migration
{
public function up(): void
{
Schema::table('email_settings', function (Blueprint $table) {
$table->string('auth_method', 20)->default('password');
});
}
public function down(): void
{
Schema::table('email_settings', function (Blueprint $table) {
$table->dropColumn('auth_method');
});
}
};
+1 -1
View File
@@ -68,7 +68,7 @@ class ComuniSeeder extends Seeder
];
foreach ($comuni as $c) {
Comune::create($c);
Comune::firstOrCreate(['codice_istat' => $c['codice_istat']], $c);
}
}
}
+21 -17
View File
@@ -20,23 +20,27 @@ class DatabaseSeeder extends Seeder
DockerBaseDataSeeder::class,
]);
$admin = User::create([
'name' => 'Admin',
'email' => 'admin@glastree.local',
'password' => bcrypt('password'),
'is_admin' => true,
'status' => 'active',
'permissions' => [
'individui' => 2,
'gruppi' => 2,
'eventi' => 2,
'documenti' => 2,
'mailing' => 2,
'viste' => 2,
],
]);
$admin = User::firstOrCreate(
['email' => 'admin@glastree.local'],
[
'name' => 'Admin',
'password' => bcrypt('password'),
'is_admin' => true,
'status' => 'active',
'permissions' => [
'individui' => 2,
'gruppi' => 2,
'eventi' => 2,
'documenti' => 2,
'mailing' => 2,
'viste' => 2,
],
]
);
$admin->role_preset_id = 1;
$admin->save();
if (!$admin->role_preset_id) {
$admin->role_preset_id = 1;
$admin->save();
}
}
}
+1 -1
View File
@@ -149,7 +149,7 @@ class DiocesiSeeder extends Seeder
];
foreach ($diocesi as $d) {
Diocesi::create($d);
Diocesi::firstOrCreate(['nome' => $d['nome']], $d);
}
}
}
Binary file not shown.
@@ -1,66 +0,0 @@
@php
$title = 'Google Services - ' . config('app.name');
@endphp
@extends('layouts.adminlte')
@section('content')
<section class="content-header">
<div class="container-fluid">
<div class="row mb-2">
<div class="col-sm-6">
<h1>Google Services</h1>
</div>
</div>
</div>
</section>
<section class="content">
<div class="container-fluid">
@if(session('success'))
<div class="alert alert-success alert-dismissible">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
{{ session('success') }}
</div>
@endif
@if(session('error'))
<div class="alert alert-danger alert-dismissible">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
{{ session('error') }}
</div>
@endif
<div class="row">
@foreach($services as $svc)
<div class="col-md-4">
<div class="card">
<div class="card-header">
<h3 class="card-title">
<i class="{{ $svc['service']->icon() }}"></i>
{{ $svc['service']->label() }}
</h3>
</div>
<div class="card-body">
<p>{{ $svc['service']->label() . ' - ' . $svc['service']->scope() }}</p>
@if($svc['authorized'])
<span class="badge bg-success">Connected</span>
<form action="{{ route('google.oauth.revoke', $svc['service']->value) }}" method="POST" class="d-inline">
@csrf
@method('DELETE')
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Revoke {{ $svc['service']->label() }} access?')">
Revoke
</button>
</form>
@else
<span class="badge bg-secondary">Not connected</span>
<a href="{{ route('google.oauth.authorize', $svc['service']->value) }}" class="btn btn-primary btn-sm">
Connect
</a>
@endif
</div>
</div>
</div>
@endforeach
</div>
</div>
</section>
</div>
@endsection
@@ -35,9 +35,6 @@
<a href="#calendario" class="list-group-item list-group-item-action" data-toggle="tab">
<i class="nav-icon fas fa-calendar-alt mr-2"></i> Calendario
</a>
<a href="#google-services" class="list-group-item list-group-item-action" data-toggle="tab">
<i class="nav-icon fab fa-google mr-2"></i> Google Services
</a>
<a href="#tipologie" class="list-group-item list-group-item-action" data-toggle="tab">
<i class="nav-icon fas fa-file mr-2"></i> Tipologie Documenti
</a>
@@ -400,19 +397,6 @@
<h3 class="card-title">Configurazione Server IMAP</h3>
</div>
<div class="card-body">
<div class="form-group">
<label for="auth_method">Metodo di autenticazione</label>
<select name="auth_method" id="auth_method" class="form-control">
<option value="password" {{ ($emailSettings->auth_method ?? 'password') === 'password' ? 'selected' : '' }}>Password / App Password</option>
<option value="oauth" {{ ($emailSettings->auth_method ?? 'password') === 'oauth' ? 'selected' : '' }}>OAuth 2.0 (Google)</option>
</select>
<small class="form-text text-muted">
<i class="fas fa-info-circle"></i>
Scegli "OAuth 2.0" per usare il token OAuth centralizzato di Google.
Per Gmail con password, usa una
<a href="https://support.google.com/accounts/answer/185833" target="_blank">App Password</a>.
</small>
</div>
<div class="row">
<div class="col-md-6">
<div class="form-group">
@@ -862,85 +846,6 @@
</div>
</div>
{{-- === GOOGLE SERVICES === --}}
<div class="tab-pane" id="google-services">
<div class="card card-primary">
<div class="card-header">
<h3 class="card-title"><i class="fab fa-google mr-2"></i> Google Services</h3>
<div class="card-tools">
<a href="{{ route('google.oauth.status') }}" class="btn btn-sm btn-info">
<i class="fas fa-eye mr-1"></i> Stato Dettagliato
</a>
</div>
</div>
<div class="card-body">
<p class="text-muted">Gestisci le connessioni OAuth 2.0 centralizzate per Gmail, Google Drive e Google Calendar. Ogni servizio può essere configurato separatamente.</p>
@php
$gsvc = app(\App\Services\GoogleOAuthService::class);
$gservices = [
\App\Enums\GoogleService::Gmail,
\App\Enums\GoogleService::Drive,
\App\Enums\GoogleService::Calendar,
];
@endphp
@if(session('success'))
<div class="alert alert-success alert-dismissible">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
{{ session('success') }}
</div>
@endif
@if(session('error'))
<div class="alert alert-danger alert-dismissible">
<button type="button" class="btn-close" data-bs-dismiss="alert"></button>
{{ session('error') }}
</div>
@endif
<div class="row">
@foreach($gservices as $svc)
@php $gAuth = $gsvc->isAuthorized($svc); @endphp
<div class="col-md-4">
<div class="card">
<div class="card-header" style="border-left: 4px solid {{ $svc->hexColor() }}">
<h3 class="card-title">
<i class="{{ $svc->icon() }}"></i>
{{ $svc->label() }}
</h3>
</div>
<div class="card-body">
<hr>
@if($gAuth)
<span class="badge bg-success"><i class="fas fa-check-circle"></i> Connected</span>
<form action="{{ route('google.oauth.revoke', $svc->value) }}" method="POST" class="d-inline">
@csrf @method('DELETE')
<button type="submit" class="btn btn-danger btn-sm" onclick="return confirm('Revocare l\'accesso a {{ $svc->label() }}?')">
<i class="fas fa-ban mr-1"></i> Revoca
</button>
</form>
@else
<span class="badge bg-secondary"><i class="fas fa-times-circle"></i> Non connesso</span>
<a href="{{ route('google.oauth.authorize', $svc->value) }}" class="btn btn-primary btn-sm">
<i class="fas fa-plug mr-1"></i> Connetti
</a>
@endif
</div>
</div>
</div>
@endforeach
</div>
<div class="alert alert-info mt-3">
<i class="fas fa-info-circle mr-2"></i>
<strong>Nota:</strong> Se utilizzi Gmail con OAuth 2.0, torna al tab
<a href="#email" class="alert-link" data-toggle="tab">Email</a>
e imposta il campo "Metodo di autenticazione" su "OAuth 2.0" nelle impostazioni IMAP.
</div>
</div>
</div>
</div>
{{-- === TIPOLOGIE DOCUMENTI === --}}
<div class="tab-pane active" id="tipologie">
<div class="card card-primary">
+1 -16
View File
@@ -275,19 +275,4 @@ Route::prefix('admin')->middleware(['auth', AdminOnly::class])->group(function (
Route::post('/backup/config', [\App\Http\Controllers\Admin\BackupController::class, 'updateConfig'])->name('admin.backup.config');
Route::post('/backup/toggle-auto', [\App\Http\Controllers\Admin\BackupController::class, 'toggleAuto'])->name('admin.backup.toggle-auto');
Route::post('/backup/save-auto', [\App\Http\Controllers\Admin\BackupController::class, 'saveAutoConfig'])->name('admin.backup.save-auto');
});
// Google OAuth 2.0 centralized routes
Route::get('/auth/google/{service}/authorize', [\App\Http\Controllers\GoogleOAuthController::class, 'connect'])
->name('google.oauth.authorize')
->whereIn('service', ['gmail', 'drive', 'calendar'])
->middleware('auth');
Route::get('/auth/google/callback', [\App\Http\Controllers\GoogleOAuthController::class, 'callback'])
->name('google.oauth.callback');
Route::delete('/auth/google/{service}/revoke', [\App\Http\Controllers\GoogleOAuthController::class, 'revoke'])
->name('google.oauth.revoke')
->whereIn('service', ['gmail', 'drive', 'calendar'])
->middleware('auth');
Route::get('/auth/google/status', [\App\Http\Controllers\GoogleOAuthController::class, 'status'])
->name('google.oauth.status')
->middleware('auth');
});
+2 -2
View File
@@ -3,7 +3,7 @@
'name' => 'laravel/laravel',
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '43a2c813123405dc2d6db20c0c5d318c344465c3',
'reference' => '7999c663fe184a13576026be3af0abc1d91d036d',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),
@@ -427,7 +427,7 @@
'laravel/laravel' => array(
'pretty_version' => 'dev-main',
'version' => 'dev-main',
'reference' => '43a2c813123405dc2d6db20c0c5d318c344465c3',
'reference' => '7999c663fe184a13576026be3af0abc1d91d036d',
'type' => 'project',
'install_path' => __DIR__ . '/../../',
'aliases' => array(),