( options: WebhookProcessorOptions )
| 338 | * the path first, so one tenant can never receive another's webhook deliveries. |
| 339 | */ |
| 340 | export async function findAllWebhooksForPath( |
| 341 | options: WebhookProcessorOptions |
| 342 | ): Promise<Array<{ webhook: any; workflow: any }>> { |
| 343 | if (!options.path) { |
| 344 | return [] |
| 345 | } |
| 346 | |
| 347 | const results = await db |
| 348 | .select({ |
| 349 | webhook: webhook, |
| 350 | workflow: workflow, |
| 351 | }) |
| 352 | .from(webhook) |
| 353 | .innerJoin(workflow, eq(webhook.workflowId, workflow.id)) |
| 354 | .leftJoin( |
| 355 | workflowDeploymentVersion, |
| 356 | and( |
| 357 | eq(workflowDeploymentVersion.workflowId, workflow.id), |
| 358 | eq(workflowDeploymentVersion.isActive, true) |
| 359 | ) |
| 360 | ) |
| 361 | .where( |
| 362 | and( |
| 363 | eq(webhook.path, options.path), |
| 364 | eq(webhook.isActive, true), |
| 365 | isNull(webhook.archivedAt), |
| 366 | isNull(workflow.archivedAt), |
| 367 | or( |
| 368 | eq(webhook.deploymentVersionId, workflowDeploymentVersion.id), |
| 369 | and(isNull(workflowDeploymentVersion.id), isNull(webhook.deploymentVersionId)) |
| 370 | ) |
| 371 | ) |
| 372 | ) |
| 373 | |
| 374 | if (results.length === 0) { |
| 375 | logger.warn(`[${options.requestId}] No active webhooks found for path: ${options.path}`) |
| 376 | return results |
| 377 | } |
| 378 | |
| 379 | const distinctWorkflowIds = new Set(results.map((result) => result.webhook.workflowId)) |
| 380 | |
| 381 | if (distinctWorkflowIds.size > 1) { |
| 382 | const owner = results.reduce((earliest, candidate) => { |
| 383 | const candidateTime = new Date(candidate.webhook.createdAt).getTime() |
| 384 | const earliestTime = new Date(earliest.webhook.createdAt).getTime() |
| 385 | if (candidateTime !== earliestTime) { |
| 386 | return candidateTime < earliestTime ? candidate : earliest |
| 387 | } |
| 388 | return candidate.webhook.id < earliest.webhook.id ? candidate : earliest |
| 389 | }) |
| 390 | const ownerWorkflowId = owner.webhook.workflowId |
| 391 | const ownerResults = results.filter((result) => result.webhook.workflowId === ownerWorkflowId) |
| 392 | |
| 393 | logger.error( |
| 394 | `[${options.requestId}] Cross-tenant webhook path collision for path: ${options.path}. Found ${results.length} active webhooks across ${distinctWorkflowIds.size} workflows. Dispatching only to owner workflow ${ownerWorkflowId} and dropping ${results.length - ownerResults.length} foreign webhook(s).` |
| 395 | ) |
| 396 | |
| 397 | return ownerResults |
no test coverage detected