Skip to content

Google Integrations

Google integration uses a provider-agnostic OAuth system:

  • oauth_connections — Stores tokens per (teacherId, provider, providerAccountId)
  • integration_settings — Stores typed JSONB config per (teacherId, integrationType)

Integration types: calendar_read, calendar_write, drive, meet

Use ConnectionService methods — never read/write tokens directly from the teachers table.

When a new teacher completes Google OAuth for the first time, the platform automatically creates a dedicated secondary Google Calendar:

  1. OAuth callback exchanges code for tokens + user info
  2. TeacherAuthService.findOrCreateByGoogle() creates the teacher record
  3. Calls GoogleCalendarService.createCalendar(teacherId, 'PinTeach', 'Clases gestionadas con PinTeach')
  4. Stores the calendar ID in integration_settings (type: calendar_write, config: { writeCalendarId })
  5. All future session events are created on this secondary calendar

Fallback: If calendar creation fails, the system gracefully falls back to 'primary' (the teacher’s main Google Calendar). No broken state.

Post-signup: Teachers can switch the write target via PATCH /teacher/settings/google/calendars in Settings.

userinfo.profile, userinfo.email, calendar (full R/W), drive.file

The calendar scope grants full access to all Google Calendars the teacher owns (create, read, update, delete). Consent is always forced (prompt: 'consent') to ensure a refresh token is granted.

The Slot Engine uses Google Calendar’s freebusy.query API to check teacher availability:

  1. Fetch selected calendars from integration_settings (calendar_read config)
  2. Query FreeBusy API for the date range
  3. Merge busy blocks with DB sessions
  4. Remove overlapping slots

When sessions are confirmed, calendar events are created/updated:

Session confirmed → Create/update Google Calendar event (+ Google Meet link)
Session cancelled → Cancel calendar event
Session rescheduled → Update event time

Sync tracked in session_calendar_sync:

  • syncStatus: synced / pending / failed
  • googleCalendarEventId: event reference
  • googleMeetLink: Meet link generado por Google
  • meetingUrl: URL que se expone al profesor y al alumno (populated from googleMeetLink)

Google Meet — via Calendar Events (modo existente)

Section titled “Google Meet — via Calendar Events (modo existente)”

Cada evento de calendario creado por PinTeach solicita automaticamente una sala de Google Meet. No existe separacion de API — el link se genera via el parametro conferenceData de la Calendar Events API al insertar el evento.

Flujo:

  1. calendar-sync-worker.ts llama a GoogleCalendarService.createEvent(teacherId, event, { requestMeetLink: true })
  2. El service envia conferenceDataVersion=1 + conferenceData.createRequest con un requestId unico (pinteach-{teacherId8}-{timestamp})
  3. Google devuelve el evento con conferenceData.entryPoints — se extrae la URI del entry point de tipo video
  4. El worker guarda tanto googleMeetLink como meetingUrl en session_calendar_sync
  5. El frontend lee meetingUrl y muestra el boton “Unirse a la clase” en el panel de evento, el portal del alumno y el perfil de alumno

Requisito: El profesor debe tener Google Calendar conectado. Si no lo tiene, no se genera Meet link pero la sesion se crea normalmente.

Componentes afectados:

Archivo Que muestra
apps/app/src/components/calendar/event-detail-panel.tsx Boton “Unirse” en panel lateral del calendario
apps/app/src/components/student/tab-classes.tsx Boton “Unirse” en portal del alumno
apps/app/src/components/students/student-profile-view.tsx Boton “Enlace de clase” en perfil del alumno

Google Meet — Meet Spaces API v2 (modo independiente)

Section titled “Google Meet — Meet Spaces API v2 (modo independiente)”

Un segundo mecanismo genera links de Meet directamente via la Meet Spaces API v2 (googleapis v146+), independiente del calendar sync. Permite tener links incluso si el profesor no tiene Google Calendar conectado (solo necesita el scope meetings.space.created).

OAuth incremental: El profesor activa esta integracion en Settings → Integraciones → Google Meet. Esto lanza un OAuth que añade el scope meetings.space.created a la conexion existente sin re-autorizar todo (include_granted_scopes: true). El scope se guarda en oauth_connections.scopes (array JSONB).

Flujo de creacion:

  1. Se crea una sesion → session-service.ts llama queueMeetLink(sessionId, teacherId) (fire-and-forget, junto a queueCalendarSync)
  2. queueMeetLink comprueba hasMeetScope leyendo oauth_connections.scopes — si el scope no existe, no encola nada
  3. meet-link-worker recoge el job → llama google.meet({ version: 'v2', auth }).spaces.create({})
  4. Obtiene data.meetingUri → llama upsertSessionCalendarSync(sessionId, { meetingUrl })
  5. Worker salta si meetingUrl ya esta poblado (el calendar sync puede haberlo hecho antes)

Coexistencia: Ambos mecanismos (calendar events + Spaces API) escriben en session_calendar_sync.meetingUrl. El primero en llegar gana; el segundo salta por el check de meetingUrl existente. No hay conflicto.

Circuit breaker: Provider 'google-meet' — threshold 5 fallos / ventana 60s / recuperacion 30s.

A cron job (every 5 minutes) retries pending calendar sync operations:

  • Batch of 20 records at a time
  • Skips if google-calendar circuit breaker is open
  • Automatic retry with backoff

Teachers can add Drive files as resources via the file picker:

  1. Teacher opens Drive file picker
  2. Selects files to import as resources
  3. Files tracked in teacher_resources (provider: ‘drive’)

When Drive resources are linked to a session, files are automatically copied to student folders:

1. Resource linked to session
2. DriveCopyService queues BullMQ job
3. Worker copies file to student's folder (students.driveFolderId)
4. Progress tracked in drive_file_copies table
Status: pending → copying → copied / failed

Daily cron (3 AM) verifies Drive file integrity:

  • Checks if files still exist in Drive
  • Marks trashed/deleted files
  • Reports inconsistencies

Both Calendar and Drive API calls are protected by circuit breakers:

Service Failure Threshold Window Recovery
Google Calendar 5 failures 60s 30s
Google Drive 5 failures 60s 60s
Google Meet 5 failures 60s 30s

Only infrastructure errors (429, 5xx, timeouts) trip the circuit.

Calendar fallback: Sets syncStatus='pending', session creation never fails. Calendar-sync cron retries later.

Drive fallback: Workers skip and retry later via BullMQ DelayedError.

Redis-backed sliding-window rate limiting at two levels:

Service Per-Teacher Global
Google Calendar 10 req/10s 400/100s
Google Drive 5 req/10s 10,000/60s

Execution order: Circuit Breaker → Rate Limiter → Token refresh → API call → 429 Retry