fix(webhooks): corrected webhooks last triggered date (#582)

This commit is contained in:
Corentin Thomasset
2025-10-25 02:12:57 +02:00
committed by GitHub
parent 75340f0ce7
commit 182ccbb30b
3 changed files with 70 additions and 3 deletions

View File

@@ -0,0 +1,5 @@
---
"@papra/docker": patch
---
Fixed the webhook last triggered date always showing "never" in the webhook list.

View File

@@ -0,0 +1,50 @@
import { describe, expect, test } from 'vitest';
import { createInMemoryDatabase } from '../app/database/database.test-utils';
import { createWebhookRepository } from './webhook.repository';
describe('webhook repository', () => {
describe('getOrganizationWebhooks', () => {
test('includes the most recent webhook delivery timestamp as lastTriggeredAt', async () => {
const { db } = await createInMemoryDatabase({
organizations: [
{ id: 'org_1', name: 'Test Organization' },
],
webhooks: [
{ id: 'wbh_1', name: 'Test Webhook', url: 'https://example.com/webhook', organizationId: 'org_1' },
],
webhookDeliveries: [
{ id: 'wbh_dlv_1', webhookId: 'wbh_1', eventName: 'document:created', requestPayload: '{}', responsePayload: '{}', responseStatus: 200, createdAt: new Date('2025-01-01') },
{ id: 'wbh_dlv_2', webhookId: 'wbh_1', eventName: 'document:updated', requestPayload: '{}', responsePayload: '{}', responseStatus: 200, createdAt: new Date('2025-01-15') },
{ id: 'wbh_dlv_3', webhookId: 'wbh_1', eventName: 'document:deleted', requestPayload: '{}', responsePayload: '{}', responseStatus: 200, createdAt: new Date('2025-01-10') },
],
});
const webhookRepository = createWebhookRepository({ db });
const { webhooks } = await webhookRepository.getOrganizationWebhooks({ organizationId: 'org_1' });
expect(webhooks).to.have.length(1);
const [webhook] = webhooks;
expect(webhook?.lastTriggeredAt).to.eql(new Date('2025-01-15'));
});
test('no deliveries results in null lastTriggeredAt', async () => {
const { db } = await createInMemoryDatabase({
organizations: [
{ id: 'org_1', name: 'Test Organization' },
],
webhooks: [
{ id: 'wbh_1', name: 'Test Webhook', url: 'https://example.com/webhook', organizationId: 'org_1' },
],
});
const webhookRepository = createWebhookRepository({ db });
const { webhooks } = await webhookRepository.getOrganizationWebhooks({ organizationId: 'org_1' });
expect(webhooks).to.have.length(1);
const [webhook] = webhooks;
expect(webhook?.lastTriggeredAt).to.eql(null);
});
});
});

View File

@@ -2,7 +2,7 @@ import type { EventName } from '@papra/webhooks';
import type { Database } from '../app/database/database.types';
import type { Webhook, WebhookDeliveryInsert, WebhookEvent } from './webhooks.types';
import { injectArguments } from '@corentinth/chisels';
import { and, eq, getTableColumns } from 'drizzle-orm';
import { and, eq, getTableColumns, max } from 'drizzle-orm';
import { omitUndefined } from '../shared/utils';
import { webhookDeliveriesTable, webhookEventsTable, webhooksTable } from './webhooks.tables';
@@ -113,14 +113,25 @@ async function deleteOrganizationWebhook({ db, webhookId, organizationId }: { db
}
async function getOrganizationWebhooks({ db, organizationId }: { db: Database; organizationId: string }) {
// Create a subquery for the latest delivery date per webhook
const latestDeliverySubquery = db
.select({
webhookId: webhookDeliveriesTable.webhookId,
lastTriggeredAt: max(webhookDeliveriesTable.createdAt).as('last_triggered_at'),
})
.from(webhookDeliveriesTable)
.groupBy(webhookDeliveriesTable.webhookId)
.as('latest_delivery');
const rawWebhooks = await db
.select()
.from(webhooksTable)
.leftJoin(webhookEventsTable, eq(webhooksTable.id, webhookEventsTable.webhookId))
.leftJoin(latestDeliverySubquery, eq(webhooksTable.id, latestDeliverySubquery.webhookId))
.where(eq(webhooksTable.organizationId, organizationId));
const webhooksRecord = rawWebhooks
.reduce((acc, { webhooks, webhook_events }) => {
.reduce((acc, { webhooks, webhook_events, latest_delivery }) => {
const webhookId = webhooks.id;
const webhookEvents = webhook_events;
@@ -128,6 +139,7 @@ async function getOrganizationWebhooks({ db, organizationId }: { db: Database; o
acc[webhookId] = {
...webhooks,
events: [],
lastTriggeredAt: latest_delivery?.lastTriggeredAt ?? null,
};
}
@@ -136,7 +148,7 @@ async function getOrganizationWebhooks({ db, organizationId }: { db: Database; o
}
return acc;
}, {} as Record<string, Webhook & { events: WebhookEvent[] }>);
}, {} as Record<string, Webhook & { events: WebhookEvent[]; lastTriggeredAt: Date | null }>);
return { webhooks: Object.values(webhooksRecord) };
}