diff --git a/src/routes/admin/apisearcher.ts b/src/routes/admin/apisearcher.ts index 06a07f4..f50f8ae 100644 --- a/src/routes/admin/apisearcher.ts +++ b/src/routes/admin/apisearcher.ts @@ -688,7 +688,13 @@ export const setupAPISearchRoutes = function(app: Express, prisma: PrismaClient) skip: offset, }); return res.json({ - data: webhooks, + // Replace delivery_id with id (for apisearcher compatibility) + data: webhooks.map((webhook) => { + return { + ...webhook, + id: webhook.delivery_id, + }; + }), meta: { pagination: { total: await prisma.intraWebhook.count(), @@ -709,7 +715,13 @@ export const setupAPISearchRoutes = function(app: Express, prisma: PrismaClient) }, }); return res.json({ - data: webhooks, + // Replace delivery_id with id (for apisearcher compatibility) + data: webhooks.map((webhook) => { + return { + ...webhook, + id: webhook.delivery_id, + }; + }), meta: { pagination: { total: webhooks.length, @@ -743,7 +755,13 @@ export const setupAPISearchRoutes = function(app: Express, prisma: PrismaClient) skip: offset, }); return res.json({ - data: webhooks, + // Replace delivery_id with id (for apisearcher compatibility) + data: webhooks.map((webhook) => { + return { + ...webhook, + id: webhook.delivery_id, + }; + }), meta: { pagination: { total: webhookCount, @@ -775,7 +793,13 @@ export const setupAPISearchRoutes = function(app: Express, prisma: PrismaClient) }, }); return res.json({ - data: webhooks, + // Replace delivery_id with id (for apisearcher compatibility) + data: webhooks.map((webhook) => { + return { + ...webhook, + id: webhook.delivery_id, + }; + }), meta: { pagination: { total: webhookCount, @@ -807,7 +831,13 @@ export const setupAPISearchRoutes = function(app: Express, prisma: PrismaClient) }, }); return res.json({ - data: webhooks, + // Replace delivery_id with id (for apisearcher compatibility) + data: webhooks.map((webhook) => { + return { + ...webhook, + id: webhook.delivery_id, + }; + }), meta: { pagination: { total: webhookCount, @@ -843,7 +873,13 @@ export const setupAPISearchRoutes = function(app: Express, prisma: PrismaClient) }, }); return res.json({ - data: webhooks, + // Replace delivery_id with id (for apisearcher compatibility) + data: webhooks.map((webhook) => { + return { + ...webhook, + id: webhook.delivery_id, + }; + }), meta: { pagination: { total: webhookCount, diff --git a/src/routes/admin/hooksmgmt.ts b/src/routes/admin/hooksmgmt.ts index ba6f1ce..c96261b 100644 --- a/src/routes/admin/hooksmgmt.ts +++ b/src/routes/admin/hooksmgmt.ts @@ -1,6 +1,7 @@ import { Express } from 'express'; import { PrismaClient } from '@prisma/client'; import { CatchupOperation, startCatchupOperation } from '../hooks/catchup'; +import { handleWebhook } from '../hooks'; const catchupOperation: CatchupOperation = { ongoing: false, @@ -20,6 +21,26 @@ export const setupWebhookManagementRoutes = function(app: Express, prisma: Prism return res.render('admin/hooks/history.njk'); }); + app.get('/admin/hooks/retrigger/:deliveryId', async (req, res) => { // :deliveryId is at the end of the path because the apisearcher requires this + const deliveryId = req.params.deliveryId; + const webhook = await prisma.intraWebhook.findUnique({ + where: { + delivery_id: deliveryId, + }, + }); + if (!webhook) { + return res.status(404).json({ error: 'Webhook not found' }); + } + try { + await handleWebhook(prisma, webhook.model, JSON.parse(webhook.body), null, deliveryId); + return res.status(200).json({ status: 'ok' }); + } + catch (err) { + console.error('Failed to retrigger webhook:', err); + return res.status(500).json({ error: 'Failed to retrigger webhook', details: err }); + } + }); + app.get('/admin/hooks/secrets', async (req, res) => { const secrets = await prisma.intraWebhookSecret.findMany(); return res.render('admin/hooks/secrets.njk', { secrets }); diff --git a/src/routes/hooks.ts b/src/routes/hooks.ts index 068ab72..0534f3b 100644 --- a/src/routes/hooks.ts +++ b/src/routes/hooks.ts @@ -69,6 +69,32 @@ export const respondWebHookHandledStatus = async function(prisma: PrismaClient, return res.status(200).json({ status: status }); // Always return 200 OK, we save the webhook in our database anyways and can easily trigger it again from our side }; +export const handleWebhook = async function(prisma: PrismaClient, modelType: string, body: { [key: string]: any }, res: Response | null, deliveryId: string): Promise> | null> { + if (!body) { + throw new Error("Missing body"); + } + if (typeof body !== 'object') { + throw new Error("Invalid body"); + } + switch (modelType) { + case "location": // location close + const location: Location = body as Location; + return await handleLocationCloseWebhook(prisma, location, res, deliveryId); + case "projects_user": // project or exam validation + const projectUser: ProjectUser = body as ProjectUser; + return await handleProjectsUserUpdateWebhook(prisma, projectUser, res, deliveryId); + case "scale_team": // scale team (evaluation) update + const scaleTeam: ScaleTeam = body as ScaleTeam; + return await handleScaleTeamUpdateWebhook(prisma, scaleTeam, res, deliveryId); + case "pool": // pool point_given + const pointGiven: PointGiven = body as PointGiven; + return await handlePointGivenWebhook(prisma, pointGiven, res, deliveryId); + default: + console.warn("Unknown model type", modelType); + return await (res ? respondWebHookHandledStatus(prisma, deliveryId, res, WebhookHandledStatus.Error) : null); + } +}; + export const setupWebhookRoutes = function(app: Express, prisma: PrismaClient): void { app.post('/hooks/intra', async (req, res) => { // Handle all Intra webhooks @@ -106,29 +132,7 @@ export const setupWebhookRoutes = function(app: Express, prisma: PrismaClient): // Actually handle the webhook, but do catch any errors try { - if (!req.body) { - throw new Error("Missing body"); - } - if (typeof req.body !== 'object') { - throw new Error("Invalid body"); - } - switch (webhookHeaders.modelType) { - case "location": // location close - const location: Location = req.body as Location; - return await handleLocationCloseWebhook(prisma, location, res, webhookHeaders.deliveryId); - case "projects_user": // project or exam validation - const projectUser: ProjectUser = req.body as ProjectUser; - return await handleProjectsUserUpdateWebhook(prisma, projectUser, res, webhookHeaders.deliveryId); - case "scale_team": // scale team (evaluation) update - const scaleTeam: ScaleTeam = req.body as ScaleTeam; - return await handleScaleTeamUpdateWebhook(prisma, scaleTeam, res, webhookHeaders.deliveryId); - case "pool": // pool point_given - const pointGiven: PointGiven = req.body as PointGiven; - return await handlePointGivenWebhook(prisma, pointGiven, res, webhookHeaders.deliveryId); - default: - console.warn("Unknown model type", webhookHeaders.modelType); - return await respondWebHookHandledStatus(prisma, webhookHeaders.deliveryId, res, WebhookHandledStatus.Error); - } + return handleWebhook(prisma, webhookHeaders.modelType, req.body, res, webhookHeaders.deliveryId); } catch (err) { console.error("Failed to handle webhook", err); diff --git a/templates/admin/hooks/history.njk b/templates/admin/hooks/history.njk index 269f6e1..cca0222 100644 --- a/templates/admin/hooks/history.njk +++ b/templates/admin/hooks/history.njk @@ -32,7 +32,7 @@ - + @@ -60,7 +60,11 @@ const apiSearcher = new ApiSearcherTable({ pageNav: '#pagenav', noPoints: true, actions: [ - // TODO: add option to retrigger the webhook + { + name: 'Retrigger', + url: window.location.origin + '/admin/hooks/retrigger/', // id will be appended + background: true, // background request, set to false to open in the same tab instead + } ] });
Delivery IDDelivery ID Received at Handled at Model