'use server' import type { WebhookDeliveriesPage, WebhookDeliveriesQuery } from './types' import { requireRole, UserRole } from '@admin/auth/middleware' import db from '@admin/db' import { webhookDeliveries, webhooks } from '@admin/db/schema' import { getExclusiveDateUpperBound } from '@admin/utils/date/get-exclusive-date-upper-bound' import { and, asc, desc, eq, gte, ilike, lt, or, sql } from 'drizzle-orm' const SORTABLE_COLUMNS = { createdAt: webhookDeliveries.createdAt, event: webhookDeliveries.event, webhookName: webhooks.name, attempt: webhookDeliveries.attempt, durationMs: webhookDeliveries.durationMs, statusCode: webhookDeliveries.statusCode, success: webhookDeliveries.success } as const // Deliberately uncached: delivery rows are written inside after(), where cache // invalidation is unavailable, so cached reads could never be refreshed. export async function getWebhookDeliveries( query?: WebhookDeliveriesQuery ): Promise { await requireRole([UserRole.ADMIN]) try { const conditions = [] const search = query?.search if (search && typeof search === 'string' && search.trim()) { const searchTerm = `%${search.trim().toLowerCase()}%` conditions.push( or( ilike(webhookDeliveries.event, searchTerm), ilike(webhookDeliveries.eventId, searchTerm), ilike(webhooks.name, searchTerm) ) ) } if (query?.success !== undefined) { conditions.push(eq(webhookDeliveries.success, query.success)) } if (query?.webhookId) { conditions.push(eq(webhookDeliveries.webhookId, query.webhookId)) } if (query?.createdAtFrom) { conditions.push(gte(webhookDeliveries.createdAt, query.createdAtFrom)) } const createdAtExclusiveTo = getExclusiveDateUpperBound(query?.createdAtTo) if (createdAtExclusiveTo) { conditions.push(lt(webhookDeliveries.createdAt, createdAtExclusiveTo)) } const baseQuery = db .select({ id: webhookDeliveries.id, webhookId: webhookDeliveries.webhookId, webhookName: webhooks.name, event: webhookDeliveries.event, eventId: webhookDeliveries.eventId, attempt: webhookDeliveries.attempt, success: webhookDeliveries.success, statusCode: webhookDeliveries.statusCode, durationMs: webhookDeliveries.durationMs, error: webhookDeliveries.error, createdAt: webhookDeliveries.createdAt }) .from(webhookDeliveries) .innerJoin(webhooks, eq(webhookDeliveries.webhookId, webhooks.id)) const sortColumn = query?.orderBy && query.orderBy in SORTABLE_COLUMNS ? SORTABLE_COLUMNS[query.orderBy as keyof typeof SORTABLE_COLUMNS] : webhookDeliveries.createdAt const sortFn = query?.orderDirection === 'asc' ? asc : desc const orderClause = sortFn(sortColumn) const orderedQuery = conditions.length > 0 ? baseQuery.where(and(...conditions)).orderBy(orderClause) : baseQuery.orderBy(orderClause) const rawLimit = query?.limit const limit = typeof rawLimit === 'number' ? Number.isFinite(rawLimit) ? Math.max(1, rawLimit) : 1 : undefined const rawOffset = query?.offset const offset = typeof rawOffset === 'number' && Number.isFinite(rawOffset) ? Math.max(0, rawOffset) : 0 const isPaginated = typeof limit === 'number' const deliveries = isPaginated ? await (offset > 0 ? orderedQuery.limit(limit).offset(offset) : orderedQuery.limit(limit)) : await orderedQuery const total = isPaginated ? Number( ( await db .select({ count: sql`count(*)` }) .from(webhookDeliveries) .innerJoin(webhooks, eq(webhookDeliveries.webhookId, webhooks.id)) .where(conditions.length > 0 ? and(...conditions) : undefined) )[0]?.count ?? 0 ) : deliveries.length return { deliveries, total } } catch (error) { console.error('Error fetching webhook deliveries:', error) throw new Error('Failed to fetch webhook deliveries') } }