ops

Sync Google/Outlook Calendar

Sincronizzazione bidirezionale eventi tra agenda interna e Google Calendar/Microsoft Outlook 365 con OAuth, webhook push e gestione conflitti.

Cosa fa questo modulo

Modulo «Sync Google/Outlook Calendar»: integrazione bidirezionale tra l'agenda/calendario interno dell'applicazione (appuntamenti, prenotazioni, lezioni, interventi, visite, scadenze, turni, slot operatore) e i calendari personali degli utenti su Google Calendar (Workspace o consumer Gmail) e Microsoft Outlook 365/Exchange Online, in modo che ogni evento creato o modificato da una parte si rifletta in tempo reale (o quasi) sull'altra senza doppia digitazione. Architettura: connessione provider via OAuth 2.0 (Google: scope `https://www.googleapis.com/auth/calendar` o granulari `calendar.events`/`calendar.calendars.readonly`/`calendar.events.owned`, redirect URI registrato in Google Cloud Console, refresh token con offline access; Microsoft Graph: scope `Calendars.ReadWrite`/`Calendars.ReadWrite.Shared`/`offline_access`, registrazione app in Azure AD/Entra ID con client_id+client_secret e redirect, consenso admin per multi-tenant), gestione token (storage cifrato `access_token`/`refresh_token`/`expires_at` per utente in tabella `calendar_integrations`, refresh automatico transparent con retry su 401, revoca token con cleanup eventi orfani su disconnessione utente). Mapping eventi: tabella locale `calendar_events_external_map` (event_id_locale, provider, external_event_id, external_calendar_id, etag/changeKey, last_synced_at, sync_direction, sync_status, conflict_state, deleted_at) con index su tuple per lookup veloce; selezione calendari (l'utente sceglie quali calendari Google/Outlook sincronizzare in entrambe direzioni — es. «calendario lavoro Google ↔ agenda app», esclude calendari personali; oppure «push only» dall'app verso provider come «Agenda Footility - Holiday Self Drive»); mapping campi (titolo, descrizione/note, start/end con timezone IANA, all-day flag, location/luogo geocodato, partecipanti con email/risposta RSVP, organizzatore, recurrence RRULE iCalendar RFC5545, reminder/popup, color/categoria, status confirmed/tentative/cancelled, visibility public/private, conference link Google Meet/Microsoft Teams autocreato con `conferenceData.createRequest`/`onlineMeeting`, attachment file via Drive/OneDrive link). Sync push outbound (app → provider): job queued su create/update/delete di evento locale (observer Eloquent o esplicito service), chiamata REST a Google Calendar API (`events.insert`/`events.patch`/`events.delete` con `If-Match: etag` per optimistic concurrency) o Microsoft Graph (`/me/events` POST/PATCH/DELETE con `If-Match: changeKey`), retry esponenziale su 5xx/429 con backoff (Retry-After header rispettato), idempotency via `requestId`/client-side id, batch endpoint dove disponibile per bulk create iniziale. Sync pull inbound (provider → app) via webhook push: Google `events.watch` con channel registration (callback URL HTTPS verificato dominio, token segreto X-Goog-Channel-Token, scadenza max 30gg con auto-rinnovo via scheduler), Microsoft Graph subscription (`/subscriptions` con `changeType=created,updated,deleted`, notificationUrl verificato con validationToken sync, clientState segreto, scadenza max ~3gg con rinnovo cron); ricezione notifica → lookup token utente → fetch delta via `events.list` con `syncToken` (Google) o `delta` query (Graph) per ottenere solo cambiamenti dall'ultima sync, applicazione delle modifiche all'evento locale con resolve conflitti, persistenza nuovo syncToken/deltaLink. Fallback polling se webhook non disponibile (es. dietro NAT senza pubblico HTTPS, dev locale, account user senza permessi watch): cron job ogni N minuti (configurabile, default 5-15 min, finestra dinamica basata su attività utente) che richiama delta API per ogni integrazione attiva, con throttling per non saturare quote API. Gestione conflitti: rilevamento simultaneo update da entrambe parti tramite etag/changeKey mismatch durante push, strategie configurabili globali o per-utente: «last write wins» (più recente per `updated_at` vince), «server (provider) wins», «client (app) wins», «manual» (entrambe versioni mostrate UI con diff side-by-side e operatore sceglie quale mantenere o merge campi specifici), notifica utente su conflitto via email/in-app. Eventi ricorrenti: parsing RRULE bidirezionale con libreria `simshaun/recurr` o `sabre/vobject`, gestione eccezioni single instance vs intera serie (modificare un'occorrenza singola crea exception, modificare la serie aggiorna master), `recurringEventId` Google e `seriesMasterId` Graph rispettati, cancellazione di una singola occorrenza marcata come `cancelled` invece di hard-delete per preservare ricorrenza. Timezone handling: persistenza esplicita timezone evento (IANA come `Europe/Rome`), conversione UTC↔locale solo a display, supporto eventi cross-timezone (organizzatore Roma, partecipante New York), all-day events distinti da timed events. Privacy granulare: opzione utente «sincronizza titolo ed orario, nascondi dettagli» (l'evento provider mostra solo «Occupato» con timeblock, dettagli rimangono solo in app per riservatezza clienti), filtri pre-sync (sincronizza solo eventi tipo `appuntamento_cliente` o solo certe categorie/sale), prefisso titolo configurabile (es. «[Footility] Visita Mario Rossi» per distinguere). Multi-utente / multi-account: ogni utente collega i propri account provider in autonomia da `/profilo/integrazioni`, lista calendari connessi con last_sync_at/quota usage/health status, disconnect con conferma e revoca token lato Google/Microsoft (chiamata revoke endpoint), supporto utente con più account simultanei (es. Google personale + Google lavoro + Outlook 365 aziendale tutti collegati). Eventi business: integrazione observer sui modelli applicativi `Appuntamento`/`Booking`/`Lezione`/`Intervento`/`Visita` che, su create/update/delete, accodano sync job per ciascun operatore/utente con account collegati e per cui l'evento è rilevante (es. tutti gli operatori invitati o solo l'organizzatore). Risorse condivise: sincronizzazione calendari sale/risorse (sale riunioni, mezzi, attrezzature) via service account Google Workspace o `room mailbox` Microsoft per disponibilità centralizzata. Stato sync UI: badge in agenda app accanto evento (icona Google/Outlook + tooltip last_synced_at + stato OK/pending/error), pannello «Stato integrazione» con tabella eventi non sincronizzati e motivo errore (token scaduto, permessi insufficienti, quota exceeded, conflict pending, evento eliminato remotamente), retry manuale per evento o bulk re-sync intera integrazione. Logging e audit: ogni operazione sync loggata (event_id, provider, action, payload hash, response status, latenza, errore se presente) con retention N giorni per troubleshooting, integrazione con #8330 audit log per tracciare modifiche eventi attribuibili a sync remoto vs azione utente. Notifiche: invio reminder via #8331 mail / #8332 SMS / #8334 push se evento si avvicina e configurazione utente lo prevede (questi reminder non sostituiscono ma affiancano i reminder nativi Google/Outlook), invito automatico a partecipanti email tramite provider (Google `sendUpdates=all`, Graph `sendInvitations=true`) con RSVP track inbound nel sync. Rate limiting e quote: Google Calendar API quota default 1M query/giorno, Microsoft Graph throttling per app+tenant (\~10K req/10min); implementazione token bucket per utente+provider con backoff su 429 + Retry-After, batch quando possibile (Google batch endpoint, Graph `$batch`), watch channel per ridurre polling. Sicurezza: token cifrati at rest con `Crypt::encryptString` Laravel (chiave APP_KEY rotata periodicamente), HTTPS forzato per OAuth callback e webhook endpoint, validazione firma webhook (Google channel token comparison constant-time, Graph clientState match), nessun token in log/email, scope minimal necessari, revoca automatica token se utente disabilitato/eliminato app-side, audit log accessi su tabella token. Compliance: consenso utente esplicito su scope calendario con descrizione testuale chiara (cosa viene letto/scritto), opzione disconnessione one-click, esportazione storico sync per richiesta GDPR utente, cancellazione tokens su diritto oblio. Setup admin: pannello configurazione provider con campi `google_client_id`/`google_client_secret`/`microsoft_client_id`/`microsoft_client_secret`/`microsoft_tenant_id` cifrati, test connessione (tentativo OAuth simulato), gestione webhook URL pubblico (URL ngrok in dev / dominio prod), comando artisan `php artisan calendar:sync:health` per check globale stato integrazioni. UI/UX: wizard «Collega il tuo calendario» (step1: scegli provider Google/Microsoft, step2: OAuth consent screen, step3: seleziona quali calendari sincronizzare e direzione e privacy livello, step4: conferma e prima sync), pulsante «Disconnetti» con conferma e opzione «mantieni eventi già sincronizzati» o «rimuovi da provider». Endpoint developer: API interna (Sanctum) per integrazione mobile app futura o terze parti (es. dashboard analytics) con scope readonly su stato integrazioni e ultimi sync. Performance: sync iniziale full su connessione spalmata in chunk e job batch (evita timeout su agende con migliaia eventi storici), incremental delta da quel punto in poi, debounce su modifiche multiple ravvicinate dello stesso evento (raggruppa in unico payload PATCH), cleanup periodico mapping orfani. Casi d'uso: gestionali servizi (Holiday Self Drive: noleggi e ritiri sincronizzati con calendario operatori; Casarile: sopralluoghi e cantieri; Altramusica: lezioni docenti su loro Google personale; Studi professionali: appuntamenti clienti su Outlook 365 commercialisti/avvocati), prenotazioni online (slot e booking esposti su #X booking module riflessi sul calendario operatore in tempo reale per blocco doppia prenotazione), interventi tecnici (assegnazione tecnico + auto-creazione evento sul suo Google con location cliente per navigatore), sales/CRM (call con lead automaticamente piazzate su calendario commerciale + invito al lead). Differenza da iCal export passivo (.ics statico read-only, nessun aggiornamento bidirezionale, nessun cancel propagation): qui sync attiva live con webhook e gestione conflitti. Differenza da Zapier/Make integration low-code (latenza minuti, costo per task, controllo limitato su mapping, vendor lock-in): qui integrazione nativa custom owned, latenza sub-minuto via push, mapping completo campi e ricorrenze, nessun costo terzo per task. Differenza da soluzioni iframe Google Calendar embed (solo visualizzazione, no scrittura, no eventi app): qui sync vero database↔database. Differenza da CalDAV server bridge (protocollo complesso, supporto provider variabile, problemi auth moderni): qui REST API ufficiali Google/Microsoft più affidabili e ricche. Estensioni future: supporto Apple iCloud Calendar (CalDAV via AppleID app-specific password — più fragile, fattibile), provider terzi (Fastmail, Proton Calendar) via CalDAV, AI suggerimento slot ottimale per appuntamenti (#8336 search slot liberi cross-calendari partecipanti), busy-time aggregation per disponibilità in tempo reale (booking module mostra solo slot dove tutti gli operatori invitati sono liberi su tutti i loro calendari connessi). Costo: 3-4 giorni OAuth Google + Microsoft + token storage cifrato + refresh management, 3-4 giorni sync engine bidirezionale + mapping eventi + ricorrenze + timezone, 2-3 giorni webhook push (Google watch channels + Graph subscriptions + verifica + rinnovo) + delta sync, 2 giorni gestione conflitti UI + privacy livelli + filtri, 1-2 giorni wizard collegamento + pannello stato integrazione + UI agenda con badge, 1-2 giorni testing edge cases (ricorrenze complesse, timezone DST, eventi cross-account, token revocati lato provider).

Esempi d'uso

  • ops

Disponibile nei pacchetti