Files
speckle-server/packages/server/modules/webhooks/services/webhooks.js
T
andrewwallacespeckle 0045c353c6 Feature: FE2 - Project Webhooks Page (#1792)
* Layout Pages

* Add Multi Select with Badges

* Add MultiBadge

* Add prevent close on click outside

* Fix import issue

* Import Table

* Add Classnames to buttons

* Add Switch Component

* Update for webhooks

* skip precommit hooks

* Remove Infinite Load. Update Types

* Create Webhook Dialog

* Tidy Ups

* Edit Webhook dialog

* WIP Breadcurmbs

* Changes from calls with Fabians

* Breadcrumbs

* Reorders

* Fix Create Dialog

* Rename MultiBadge to BadgeSelected

* Fix and update Story file for Table

* Adjust Padding for Buttons in Table

* Add extra story, adjust padding for no buttons

* Fix bug with Edit Select

* fixed Webhook sorting + added Webhook.hasSecret and Webhook.projectId

* fixed hydration mismatch

* Changes from PR feedback

* Validation Rule for Select

* Reset Dialogs on Cancel. Conditionally render headers in Table

* stricter webhook gql types

* stricter webhook gql types

* Fix initial dialogs

* Quick Fixes

* Add projectWebhooksRoute

* Remove TableItemType

* Fixes from PR

* Fix broken Query

* Fixes from PR

* Fix based on PR

* Fix from PR

* Changes to index

* Fix in index

* Updates to Validation and Table

* Add "by" prop to FormSelectBadges and renamed component

* Use defineModel for Switch

* Revert "Use defineModel for Switch"

This reverts commit 6bc9e07a767cdc64f06c03b028150915e013ed4f.

* Replace breadcrumbs with projectWebhooksRoute

* Rename FormValues to WebhookFormValues

* Add target blank and simplify trigger mapping

* Fix casing of webhookFormValues

* Change webhookModel to prevent props mutation

* Remove unnecessary typecast

* Webhook deletion now uses fieldNameWhitelist.

* Use convertThrowIntoFetchResult and getFirstErrorMessage in Create

* Use defineModel for handling open state of Dialogs

* Optimise Switch component with defineModel

* Merge Create and Edit Dialogs

* Fix issue with Status Icons

* Remove console log

* WIP Merge of Edit and Create

* Add optional placeholder to SelectBase. Update Events placeholder.

* Add secret to Create webhook dialog

* Update Watch

* Rename Dialogs. Fix active select items

* Fix Select active items

* Simplify triggers, add secret to create call

* Remove $webhooksId: String

* fix: stale form state across edit/create webhook dialog sessions

* Fix from PR

* Swap t.text for t.id

* Use enum for historyStatus

* Use consistent story formatting

* More consistent create/edit mutations

* fix be linting errors

---------

Co-authored-by: Kristaps Fabians Geikins <fabis94@live.com>
2023-09-26 15:41:29 +02:00

164 lines
4.6 KiB
JavaScript

'use strict'
const knex = require('@/db/knex')
const { getStream } = require('@/modules/core/repositories/streams')
const crs = require('crypto-random-string')
const WebhooksConfig = () => knex('webhooks_config')
const WebhooksEvents = () => knex('webhooks_events')
const Users = () => knex('users')
const { getServerInfo } = require('../../core/services/generic')
const MAX_STREAM_WEBHOOKS = 100
module.exports = {
async createWebhook({ streamId, url, description, secret, enabled, triggers }) {
const streamWebhookCount = await module.exports.getStreamWebhooksCount({ streamId })
if (streamWebhookCount >= MAX_STREAM_WEBHOOKS) {
throw new Error(
`Maximum number of webhooks for a stream reached (${MAX_STREAM_WEBHOOKS})`
)
}
const triggersObj = Object.assign({}, ...triggers.map((x) => ({ [x]: true })))
const [{ id }] = await WebhooksConfig()
.returning('id')
.insert({
id: crs({ length: 10 }),
streamId,
url,
description,
secret,
enabled,
triggers: triggersObj
})
return id
},
async getWebhook({ id }) {
const webhook = await WebhooksConfig().select('*').where({ id }).first()
if (webhook) {
webhook.triggers = Object.keys(webhook.triggers)
}
return webhook
},
async updateWebhook({ id, url, description, secret, enabled, triggers }) {
const fieldsToUpdate = {
updatedAt: new Date()
}
if (url !== undefined) fieldsToUpdate.url = url
if (description !== undefined) fieldsToUpdate.description = description
if (secret !== undefined) fieldsToUpdate.secret = secret
if (enabled !== undefined) fieldsToUpdate.enabled = enabled
if (triggers !== undefined) {
const triggersObj = Object.assign({}, ...triggers.map((x) => ({ [x]: true })))
fieldsToUpdate.triggers = triggersObj
}
const [{ id: res }] = await WebhooksConfig()
.returning('id')
.where({ id })
.update(fieldsToUpdate)
return res
},
async deleteWebhook({ id }) {
return await WebhooksConfig().where({ id }).del()
},
async getStreamWebhooks({ streamId }) {
const webhooks = await WebhooksConfig()
.select('*')
.where({ streamId })
.orderBy('updatedAt', 'desc')
for (const webhook of webhooks) {
webhook.triggers = Object.keys(webhook.triggers)
}
return webhooks
},
async getStreamWebhooksCount({ streamId }) {
const [res] = await WebhooksConfig().count().where({ streamId })
return parseInt(res.count)
},
async dispatchStreamEvent({ streamId, event, eventPayload }, { trx } = {}) {
// Add server info
eventPayload.server = await getServerInfo()
eventPayload.server.canonicalUrl = process.env.CANONICAL_URL
delete eventPayload.server.id
// Add stream info
if (eventPayload.streamId) {
eventPayload.stream = await getStream(
{
streamId: eventPayload.streamId,
userId: eventPayload.userId
},
{ trx }
)
}
// Add user info (except email and pwd)
if (eventPayload.userId) {
eventPayload.user = await Users()
.where({ id: eventPayload.userId })
.select('*')
.first()
if (eventPayload.user) {
delete eventPayload.user.passwordDigest
delete eventPayload.user.email
}
}
// with this select, we must have the streamid available on the webhook config,
// even when the stream is deleted, to dispatch the stream deleted webhook events
const { rows } = await knex.raw(
`
SELECT * FROM webhooks_config WHERE "streamId" = ?
`,
[streamId]
)
for (const wh of rows) {
if (!wh.enabled) continue
if (!(event in wh.triggers)) continue
// Add webhook info (the key `webhook` will be replaced for each webhook configured, before serializing the payload and storing it)
eventPayload.webhook = wh
eventPayload.webhook.triggers = Object.keys(eventPayload.webhook.triggers)
delete eventPayload.webhook.secret
const q = WebhooksEvents().insert({
id: crs({ length: 20 }),
webhookId: wh.id,
payload: JSON.stringify(eventPayload)
})
if (trx) q.transacting(trx)
await q
}
},
async getLastWebhookEvents({ webhookId, limit }) {
if (!limit) {
limit = 100
}
return await WebhooksEvents()
.select('*')
.where({ webhookId })
.orderBy('lastUpdate', 'desc')
.limit(limit)
},
async getWebhookEventsCount({ webhookId }) {
const [res] = await WebhooksEvents().count().where({ webhookId })
return parseInt(res.count)
}
}