Skip to content
← Public packages

@charlesmatte/granola

Search and read Granola meeting notes, summaries and transcripts from any agent; manage webhooks and legal holds safely.

src/webhooks/update.ts

60 lines · 3.4 KB · TypeScript
Raw
import { mutationSpec, parseWithAccount } from '../lib/api.ts'
import { granolaRequest } from '../lib/client.ts'
import { checkWebhookUrl, echo, mutationMode, preview } from '../lib/guard.ts'
import { FOLDER_ID_PATTERN, WEBHOOK_ID_PATTERN } from '../lib/validate.ts'
import { checkEnumList, findWebhookEndpoint, WEBHOOK_EVENTS, WEBHOOK_SCOPES } from '../lib/webhooks.ts'

/**
 * Change a webhook endpoint (`PATCH /v1/webhook-endpoints/{id}`): URL, scopes, events, folder
 * filter, or `enabled` (re-enable one Granola disabled after failed deliveries). Only the fields
 * you pass change. WRITE: previews unless `confirm: true`; the preview shows the current endpoint.
 *
 * @param input.account - Account name (secret `granola-<account>`). Required. @param input.secretName - Secret override.
 * @param input.webhookId - `whe_…` id (from ./webhooks/list). Required.
 * @param input.url - New public HTTPS URL. @param input.scopes - personal/public/workspace. @param input.events - Event subset.
 * @param input.folderIds - Folder filter (`[]` clears it). @param input.enabled - Enable/disable deliveries.
 * @param input.dryRun - Preview only. @param input.confirm - `true` to apply.
 * @returns Preview `{ dryRun: true, current, request }`, or `{ executed: true, webhookEndpoint }`
 *
 * @example
 * import updateWebhook from 'kody:@charlesmatte/granola/webhooks/update'
 *
 * await updateWebhook({ account: 'work', webhookId: 'whe_2mKr8fQxLp7Ta3', enabled: true, confirm: true })
 */
export default async function updateGranolaWebhook(input: unknown) {
	const p = parseWithAccount<{
		webhookId: string; url?: string; scopes?: Array<string>; events?: Array<string>; folderIds?: Array<string>; enabled?: boolean; dryRun?: boolean; confirm?: boolean
	}>(
		input,
		{
			webhookId: { type: 'string', required: true, pattern: WEBHOOK_ID_PATTERN },
			url: { type: 'string', maxLength: 2000 },
			scopes: { type: 'stringArray', maxItems: 3 },
			events: { type: 'stringArray', maxItems: 3 },
			folderIds: { type: 'stringArray', maxItems: 100, allowEmptyArray: true },
			enabled: { type: 'boolean' },
			...mutationSpec,
		},
		'webhooks/update',
	)
	checkEnumList('scopes', p.scopes, WEBHOOK_SCOPES)
	checkEnumList('events', p.events, WEBHOOK_EVENTS)
	const badFolder = (p.folderIds ?? []).find((f) => !FOLDER_ID_PATTERN.test(f))
	if (badFolder) throw new Error(`webhooks/update: folder id "${badFolder}" is not a fol_… id.`)
	const body: Record<string, unknown> = {}
	if (p.url !== undefined) body.url = checkWebhookUrl(p.url)
	if (p.scopes !== undefined) body.scopes = p.scopes
	if (p.events !== undefined) body.events = p.events
	if (p.folderIds !== undefined) body.folder_ids = p.folderIds
	if (p.enabled !== undefined) body.enabled = p.enabled
	if (Object.keys(body).length === 0) throw new Error('webhooks/update: pass at least one of url, scopes, events, folderIds, enabled.')
	const mode = mutationMode(p)
	const path = `/v1/webhook-endpoints/${p.webhookId}`
	const target = `webhook ${p.webhookId}`
	if (!mode.execute) {
		const current = await findWebhookEndpoint(p.acct, p.webhookId).catch(() => null)
		return preview(p.acct, 'webhooks/update', target, mode, { method: 'PATCH', path, body }, { current: current ?? 'not visible to this key (the update would likely 404)' })
	}
	const res = await granolaRequest(p.acct, { method: 'PATCH', path, json: body, retries: 1 })
	return { dryRun: false, executed: true, echo: echo(p.acct, 'webhooks/update', target, mode), webhookEndpoint: res.data }
}