( events: CalendarEvent[], calendarId: string, eventTypeFilter: CalendarEventTypeFilter | undefined, webhookData: PollWebhookContext['webhookData'], workflowData: PollWebhookContext['workflowData'], requestId: string, logger: Logger )
| 286 | } |
| 287 | |
| 288 | async function processEvents( |
| 289 | events: CalendarEvent[], |
| 290 | calendarId: string, |
| 291 | eventTypeFilter: CalendarEventTypeFilter | undefined, |
| 292 | webhookData: PollWebhookContext['webhookData'], |
| 293 | workflowData: PollWebhookContext['workflowData'], |
| 294 | requestId: string, |
| 295 | logger: Logger |
| 296 | ): Promise<{ processedCount: number; failedCount: number; latestUpdated: string | null }> { |
| 297 | let processedCount = 0 |
| 298 | let failedCount = 0 |
| 299 | let latestUpdated: string | null = null |
| 300 | |
| 301 | for (const event of events) { |
| 302 | // Track the latest `updated` timestamp for clock-skew-free state tracking |
| 303 | if (event.updated) { |
| 304 | if (!latestUpdated || event.updated > latestUpdated) { |
| 305 | latestUpdated = event.updated |
| 306 | } |
| 307 | } |
| 308 | |
| 309 | // Client-side event type filter — skip before idempotency so filtered events aren't cached |
| 310 | const computedEventType = determineEventType(event) |
| 311 | if (eventTypeFilter && computedEventType !== eventTypeFilter) { |
| 312 | continue |
| 313 | } |
| 314 | |
| 315 | try { |
| 316 | // Idempotency key includes `updated` so re-edits of the same event re-trigger |
| 317 | const idempotencyKey = `${webhookData.id}:${event.id}:${event.updated || event.created || ''}` |
| 318 | |
| 319 | await pollingIdempotency.executeWithIdempotency( |
| 320 | 'google-calendar', |
| 321 | idempotencyKey, |
| 322 | async () => { |
| 323 | const simplified = simplifyEvent(event, computedEventType) |
| 324 | |
| 325 | const payload: GoogleCalendarWebhookPayload = { |
| 326 | event: simplified, |
| 327 | calendarId, |
| 328 | timestamp: new Date().toISOString(), |
| 329 | } |
| 330 | |
| 331 | const result = await processPolledWebhookEvent( |
| 332 | webhookData, |
| 333 | workflowData, |
| 334 | payload, |
| 335 | requestId |
| 336 | ) |
| 337 | |
| 338 | if (!result.success) { |
| 339 | logger.error( |
| 340 | `[${requestId}] Failed to process webhook for event ${event.id}:`, |
| 341 | result.statusCode, |
| 342 | result.error |
| 343 | ) |
| 344 | throw new Error(`Webhook processing failed (${result.statusCode}): ${result.error}`) |
| 345 | } |
no test coverage detected