chore: import upstream snapshot with attribution
This commit is contained in:
@@ -0,0 +1,13 @@
|
||||
import { listObjects, getObject, listAttributes } from './objects'
|
||||
import { listRecords, getRecord, createRecord, updateRecord } from './record'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export default {
|
||||
listRecords,
|
||||
getRecord,
|
||||
createRecord,
|
||||
updateRecord,
|
||||
listObjects,
|
||||
getObject,
|
||||
listAttributes,
|
||||
} as const satisfies bp.IntegrationProps['actions']
|
||||
@@ -0,0 +1,53 @@
|
||||
//
|
||||
import { RuntimeError } from '@botpress/client'
|
||||
import { AttioApiClient } from '../attio-api'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export const listObjects: bp.IntegrationProps['actions']['listObjects'] = async (props) => {
|
||||
const { ctx } = props
|
||||
const accessToken = ctx.configuration.accessToken
|
||||
|
||||
const attioApiClient = new AttioApiClient(accessToken)
|
||||
|
||||
try {
|
||||
const data = await attioApiClient.listObjects()
|
||||
return { data: data.data }
|
||||
} catch (thrown) {
|
||||
const error = thrown instanceof Error ? thrown : new Error(String(thrown))
|
||||
throw new RuntimeError(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
export const getObject: bp.IntegrationProps['actions']['getObject'] = async (props) => {
|
||||
const { ctx, input } = props
|
||||
const accessToken = ctx.configuration.accessToken
|
||||
|
||||
const attioApiClient = new AttioApiClient(accessToken)
|
||||
|
||||
const { object } = input
|
||||
|
||||
try {
|
||||
const data = await attioApiClient.getObject(object)
|
||||
return { data: data.data }
|
||||
} catch (thrown) {
|
||||
const error = thrown instanceof Error ? thrown : new Error(String(thrown))
|
||||
throw new RuntimeError(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
export const listAttributes: bp.IntegrationProps['actions']['listAttributes'] = async (props) => {
|
||||
const { ctx, input } = props
|
||||
const accessToken = ctx.configuration.accessToken
|
||||
|
||||
const attioApiClient = new AttioApiClient(accessToken)
|
||||
|
||||
const { object } = input
|
||||
|
||||
try {
|
||||
const data = await attioApiClient.listAttributes(object)
|
||||
return { data: data.data }
|
||||
} catch (thrown) {
|
||||
const error = thrown instanceof Error ? thrown : new Error(String(thrown))
|
||||
throw new RuntimeError(error.message)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,185 @@
|
||||
//
|
||||
import { RuntimeError } from '@botpress/client'
|
||||
import { AttioApiClient, Attribute } from '../attio-api'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
type AttioRecordIdentifier = {
|
||||
workspace_id: string
|
||||
object_id: string
|
||||
record_id: string
|
||||
}
|
||||
|
||||
type AttioRecord = {
|
||||
id: AttioRecordIdentifier
|
||||
created_at: string
|
||||
web_url: string
|
||||
values: Record<string, unknown>
|
||||
}
|
||||
|
||||
function _buildAttributeMaps(attributes: Attribute[]) {
|
||||
const idToSlug: Record<string, string> = {}
|
||||
const slugToOptionLabelById: Record<string, Record<string, string>> = {}
|
||||
|
||||
for (const attr of attributes) {
|
||||
// Handle new id structure (object with attribute_id) or fallback to old string id
|
||||
const id = typeof attr.id === 'object' ? attr.id?.attribute_id : String(attr.id ?? '')
|
||||
const slug = String(attr.slug ?? attr.api_slug ?? id)
|
||||
if (id) idToSlug[id] = slug
|
||||
|
||||
if (Array.isArray(attr.options) && attr.options.length > 0) {
|
||||
const map: Record<string, string> = {}
|
||||
for (const opt of attr.options) {
|
||||
const oid = String(opt.id ?? '')
|
||||
if (!oid) continue
|
||||
const label = String(opt.label ?? opt.name ?? opt.value ?? opt.title ?? opt.slug ?? oid)
|
||||
map[oid] = label
|
||||
}
|
||||
slugToOptionLabelById[slug] = map
|
||||
}
|
||||
}
|
||||
|
||||
return { idToSlug, slugToOptionLabelById }
|
||||
}
|
||||
|
||||
function _mapValueUsingOptions(value: unknown, optionMap: Record<string, string>) {
|
||||
const mapOne = (v: unknown) => {
|
||||
if (v && typeof v === 'object' && !Array.isArray(v)) {
|
||||
const candidateId = (v as { id?: string }).id
|
||||
if (candidateId && optionMap[candidateId]) return optionMap[candidateId]
|
||||
return v
|
||||
}
|
||||
if (typeof v === 'string' && optionMap[v]) return optionMap[v]
|
||||
return v
|
||||
}
|
||||
|
||||
if (Array.isArray(value)) return value.map((v) => mapOne(v))
|
||||
return mapOne(value)
|
||||
}
|
||||
|
||||
function _humanizeRecordValues(
|
||||
record: AttioRecord,
|
||||
idToSlug: Record<string, string>,
|
||||
slugToOptionLabelById: Record<string, Record<string, string>>
|
||||
) {
|
||||
const src: Record<string, unknown> = record?.values ?? {}
|
||||
const dst: Record<string, unknown> = {}
|
||||
|
||||
for (const key of Object.keys(src)) {
|
||||
const slug = idToSlug[key] ?? key
|
||||
const optionMap = slugToOptionLabelById[slug]
|
||||
const raw = src[key]
|
||||
const value = optionMap ? _mapValueUsingOptions(raw, optionMap) : raw
|
||||
dst[slug] = value
|
||||
}
|
||||
|
||||
return { ...record, values: dst }
|
||||
}
|
||||
|
||||
function _mapValues(values: { attribute: string; value: string }[]) {
|
||||
return values.reduce((acc: Record<string, any>, curr) => {
|
||||
acc[curr.attribute] = curr.value
|
||||
return acc
|
||||
}, {})
|
||||
}
|
||||
|
||||
export const listRecords: bp.IntegrationProps['actions']['listRecords'] = async (props) => {
|
||||
const { ctx, input } = props
|
||||
const accessToken = ctx.configuration.accessToken
|
||||
|
||||
const attioApiClient = new AttioApiClient(accessToken)
|
||||
|
||||
const { object, filter, sorts, limit, offset } = input
|
||||
|
||||
const filterMap = filter ? _mapValues(filter) : undefined
|
||||
|
||||
try {
|
||||
const data = await attioApiClient.listRecords(object, { filter: filterMap, sorts, limit, offset })
|
||||
const attributes = await attioApiClient.listAttributes(object)
|
||||
|
||||
const { idToSlug, slugToOptionLabelById } = _buildAttributeMaps(attributes.data)
|
||||
const humanized = data.data.map((rec) => _humanizeRecordValues(rec, idToSlug, slugToOptionLabelById))
|
||||
|
||||
return { data: humanized }
|
||||
} catch (thrown) {
|
||||
const error = thrown instanceof Error ? thrown : new Error(String(thrown))
|
||||
throw new RuntimeError(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
export const getRecord: bp.IntegrationProps['actions']['getRecord'] = async (props) => {
|
||||
const { ctx, input } = props
|
||||
const accessToken = ctx.configuration.accessToken
|
||||
|
||||
const attioApiClient = new AttioApiClient(accessToken)
|
||||
|
||||
const { object, id } = input
|
||||
|
||||
try {
|
||||
const data = await attioApiClient.getRecord(object, id.record_id)
|
||||
const attributes = await attioApiClient.listAttributes(object)
|
||||
|
||||
const { idToSlug, slugToOptionLabelById } = _buildAttributeMaps(attributes.data)
|
||||
const humanized = _humanizeRecordValues(data.data, idToSlug, slugToOptionLabelById)
|
||||
|
||||
return { data: humanized }
|
||||
} catch (thrown) {
|
||||
const error = thrown instanceof Error ? thrown : new Error(String(thrown))
|
||||
throw new RuntimeError(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
export const createRecord: bp.IntegrationProps['actions']['createRecord'] = async (props) => {
|
||||
const { ctx, input } = props
|
||||
const accessToken = ctx.configuration.accessToken
|
||||
|
||||
const attioApiClient = new AttioApiClient(accessToken)
|
||||
|
||||
const { object, data } = input
|
||||
const { values } = data
|
||||
|
||||
try {
|
||||
const valuesMap = _mapValues(values)
|
||||
|
||||
const data = await attioApiClient.createRecord(object, { data: { values: valuesMap } })
|
||||
return { data: data.data }
|
||||
} catch (thrown) {
|
||||
const error = thrown instanceof Error ? thrown : new Error(String(thrown))
|
||||
throw new RuntimeError(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
export const updateRecord: bp.IntegrationProps['actions']['updateRecord'] = async (props) => {
|
||||
const { ctx, input } = props
|
||||
const accessToken = ctx.configuration.accessToken
|
||||
|
||||
const attioApiClient = new AttioApiClient(accessToken)
|
||||
|
||||
const { object, id, data } = input
|
||||
const { values } = data
|
||||
|
||||
try {
|
||||
const valuesMap = _mapValues(values)
|
||||
const data = await attioApiClient.updateRecord(object, id.record_id, { data: { values: valuesMap } })
|
||||
return { data: data.data }
|
||||
} catch (thrown) {
|
||||
const error = thrown instanceof Error ? thrown : new Error(String(thrown))
|
||||
throw new RuntimeError(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
export const listAttributes: bp.IntegrationProps['actions']['listAttributes'] = async (props) => {
|
||||
const { ctx, input } = props
|
||||
const accessToken = ctx.configuration.accessToken
|
||||
|
||||
const attioApiClient = new AttioApiClient(accessToken)
|
||||
|
||||
const { object } = input
|
||||
|
||||
try {
|
||||
const data = await attioApiClient.listAttributes(object)
|
||||
return { data: data.data }
|
||||
} catch (thrown) {
|
||||
const error = thrown instanceof Error ? thrown : new Error(String(thrown))
|
||||
throw new RuntimeError(error.message)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
// Types for API responses and requests
|
||||
export type RecordIdentifier = {
|
||||
workspace_id: string
|
||||
object_id: string
|
||||
record_id: string
|
||||
}
|
||||
|
||||
export type AttioRecord = {
|
||||
id: RecordIdentifier
|
||||
created_at: string
|
||||
web_url: string
|
||||
values: Record<string, unknown>
|
||||
}
|
||||
|
||||
export type AttioObject = {
|
||||
id?: {
|
||||
workspace_id: string
|
||||
object_id: string
|
||||
}
|
||||
api_slug?: string
|
||||
singular_noun?: string
|
||||
plural_noun?: string
|
||||
created_at?: string
|
||||
}
|
||||
|
||||
export type Attribute = {
|
||||
id?: {
|
||||
workspace_id: string
|
||||
object_id: string
|
||||
attribute_id: string
|
||||
}
|
||||
title?: string
|
||||
description?: string | null
|
||||
api_slug?: string
|
||||
type?: string
|
||||
slug?: string
|
||||
options?: Array<{
|
||||
id?: string
|
||||
label?: string
|
||||
name?: string
|
||||
value?: string
|
||||
title?: string
|
||||
slug?: string
|
||||
}>
|
||||
}
|
||||
|
||||
export type Sort = {
|
||||
direction: 'asc' | 'desc'
|
||||
attribute: string
|
||||
field: string
|
||||
}
|
||||
|
||||
export type ListRecordsParams = {
|
||||
filter?: Record<string, string>
|
||||
sorts?: Sort[]
|
||||
limit?: number
|
||||
offset?: number
|
||||
}
|
||||
|
||||
export type CreateRecordData = {
|
||||
data: {
|
||||
values: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
export type UpdateRecordData = {
|
||||
data: {
|
||||
values: Record<string, unknown>
|
||||
}
|
||||
}
|
||||
|
||||
export type ApiResponse<T> = {
|
||||
data: T
|
||||
}
|
||||
|
||||
export type WebhookData = {
|
||||
event_type: string
|
||||
filter: Record<string, string> | null
|
||||
}
|
||||
|
||||
export type WebhookCreateData = {
|
||||
data: {
|
||||
target_url: string
|
||||
subscriptions: WebhookData[]
|
||||
}
|
||||
}
|
||||
|
||||
export type WebhookId = {
|
||||
workspace_id: string
|
||||
webhook_id: string
|
||||
}
|
||||
|
||||
export type WebhookResponse = {
|
||||
id: WebhookId
|
||||
target_url: string
|
||||
subscriptions: WebhookData[]
|
||||
created_at: string
|
||||
secret: string
|
||||
}
|
||||
|
||||
export type ListRecordsResponse = ApiResponse<AttioRecord[]>
|
||||
export type GetRecordResponse = ApiResponse<AttioRecord>
|
||||
export type CreateRecordResponse = ApiResponse<AttioRecord>
|
||||
export type UpdateRecordResponse = ApiResponse<AttioRecord>
|
||||
export type ListObjectsResponse = ApiResponse<AttioObject[]>
|
||||
export type GetObjectResponse = ApiResponse<AttioObject>
|
||||
export type ListAttributesResponse = ApiResponse<Attribute[]>
|
||||
|
||||
export type CreateWebhookResponse = ApiResponse<WebhookResponse>
|
||||
|
||||
export class AttioApiClient {
|
||||
private _accessToken: string
|
||||
private _baseUrl: string = 'https://api.attio.com/v2'
|
||||
|
||||
public constructor(accessToken: string) {
|
||||
this._accessToken = accessToken
|
||||
}
|
||||
|
||||
private async _makeRequest<T>(
|
||||
method: 'GET' | 'POST' | 'PUT' | 'PATCH' | 'DELETE',
|
||||
endpoint: string,
|
||||
body?: unknown
|
||||
): Promise<T> {
|
||||
const url = `${this._baseUrl}${endpoint}`
|
||||
|
||||
const headers: Record<string, string> = {
|
||||
Authorization: `Bearer ${this._accessToken}`,
|
||||
'Content-Type': 'application/json',
|
||||
}
|
||||
|
||||
const config: RequestInit = {
|
||||
method,
|
||||
headers,
|
||||
}
|
||||
|
||||
if (body && (method === 'POST' || method === 'PUT' || method === 'PATCH')) {
|
||||
config.body = JSON.stringify(body)
|
||||
}
|
||||
|
||||
const response = await fetch(url, config)
|
||||
|
||||
if (!response.ok) {
|
||||
const errorText = await response.text()
|
||||
throw new Error(`Attio API Error: ${response.status} ${response.statusText} - ${errorText}`)
|
||||
}
|
||||
|
||||
const data = await response.json()
|
||||
return data
|
||||
}
|
||||
|
||||
// Records API methods
|
||||
public async listRecords(object: string, params?: ListRecordsParams): Promise<ListRecordsResponse> {
|
||||
const endpoint = `/objects/${object}/records/query`
|
||||
|
||||
const body = {
|
||||
filter: params?.filter,
|
||||
sorts: params?.sorts,
|
||||
limit: params?.limit,
|
||||
offset: params?.offset,
|
||||
}
|
||||
|
||||
return this._makeRequest<ListRecordsResponse>('POST', endpoint, body)
|
||||
}
|
||||
|
||||
public async getRecord(object: string, recordId: string): Promise<GetRecordResponse> {
|
||||
const endpoint = `/objects/${object}/records/${recordId}`
|
||||
return this._makeRequest<GetRecordResponse>('GET', endpoint)
|
||||
}
|
||||
|
||||
public async createRecord(object: string, data: CreateRecordData): Promise<CreateRecordResponse> {
|
||||
const endpoint = `/objects/${object}/records`
|
||||
return this._makeRequest<CreateRecordResponse>('POST', endpoint, data)
|
||||
}
|
||||
|
||||
public async updateRecord(object: string, recordId: string, data: UpdateRecordData): Promise<UpdateRecordResponse> {
|
||||
const endpoint = `/objects/${object}/records/${recordId}`
|
||||
return this._makeRequest<UpdateRecordResponse>('PUT', endpoint, data)
|
||||
}
|
||||
|
||||
// Objects API methods
|
||||
public async listObjects(): Promise<ListObjectsResponse> {
|
||||
const endpoint = '/objects'
|
||||
return this._makeRequest<ListObjectsResponse>('GET', endpoint)
|
||||
}
|
||||
|
||||
public async getObject(object: string): Promise<GetObjectResponse> {
|
||||
const endpoint = `/objects/${object}`
|
||||
return this._makeRequest<GetObjectResponse>('GET', endpoint)
|
||||
}
|
||||
|
||||
// Attributes API methods
|
||||
public async listAttributes(object: string): Promise<ListAttributesResponse> {
|
||||
const endpoint = `/objects/${object}/attributes`
|
||||
return this._makeRequest<ListAttributesResponse>('GET', endpoint)
|
||||
}
|
||||
|
||||
// Webhook API methods
|
||||
public async createWebhook(data: WebhookCreateData): Promise<CreateWebhookResponse> {
|
||||
const endpoint = '/webhooks'
|
||||
return this._makeRequest<CreateWebhookResponse>('POST', endpoint, data)
|
||||
}
|
||||
|
||||
public async deleteWebhook(webhookId: string): Promise<void> {
|
||||
const endpoint = `/webhooks/${webhookId}`
|
||||
await this._makeRequest<void>('DELETE', endpoint)
|
||||
}
|
||||
|
||||
// Utility method to test connection
|
||||
public async testConnection(): Promise<void> {
|
||||
await this._makeRequest<{ data: unknown }>('GET', '/self')
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
import { z } from '@botpress/sdk'
|
||||
import { recordEventSchema } from 'src/schemas'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export const recordCreated = async ({
|
||||
payload,
|
||||
client,
|
||||
logger,
|
||||
}: {
|
||||
payload: z.infer<typeof recordEventSchema>
|
||||
client: bp.Client
|
||||
logger: bp.Logger
|
||||
}) => {
|
||||
await client.createEvent({
|
||||
type: 'recordCreated',
|
||||
payload: {
|
||||
...payload,
|
||||
event_type: 'record.created',
|
||||
},
|
||||
})
|
||||
|
||||
logger.forBot().info(`Record created: ${payload.id.record_id}`)
|
||||
}
|
||||
|
||||
export const recordUpdated = async ({
|
||||
payload,
|
||||
client,
|
||||
logger,
|
||||
}: {
|
||||
payload: z.infer<typeof recordEventSchema>
|
||||
client: bp.Client
|
||||
logger: bp.Logger
|
||||
}) => {
|
||||
await client.createEvent({
|
||||
type: 'recordUpdated',
|
||||
payload: {
|
||||
...payload,
|
||||
event_type: 'record.updated',
|
||||
},
|
||||
})
|
||||
|
||||
logger.forBot().info(`Record updated: ${payload.id.record_id}`)
|
||||
}
|
||||
|
||||
export const recordDeleted = async ({
|
||||
payload,
|
||||
client,
|
||||
logger,
|
||||
}: {
|
||||
payload: z.infer<typeof recordEventSchema>
|
||||
client: bp.Client
|
||||
logger: bp.Logger
|
||||
}) => {
|
||||
await client.createEvent({
|
||||
type: 'recordDeleted',
|
||||
payload: {
|
||||
...payload,
|
||||
event_type: 'record.deleted',
|
||||
},
|
||||
})
|
||||
|
||||
logger.forBot().info(`Record deleted: ${payload.id.record_id}`)
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import actions from './actions'
|
||||
import { register, unregister, handler } from './setup'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export default new bp.Integration({
|
||||
register,
|
||||
unregister,
|
||||
actions,
|
||||
channels: {},
|
||||
handler,
|
||||
})
|
||||
@@ -0,0 +1,16 @@
|
||||
import { z } from '@botpress/sdk'
|
||||
import { baseIdentifierSchema } from '../definitions/common'
|
||||
|
||||
export const recordEventSchema = z.object({
|
||||
event_type: z.string(),
|
||||
id: baseIdentifierSchema.extend({ record_id: z.string() }),
|
||||
actor: z.object({
|
||||
type: z.string(),
|
||||
id: z.string(),
|
||||
}),
|
||||
})
|
||||
|
||||
export const webhookPayloadSchema = z.object({
|
||||
webhook_id: z.string(),
|
||||
events: z.array(recordEventSchema),
|
||||
})
|
||||
@@ -0,0 +1,61 @@
|
||||
import { webhookPayloadSchema } from 'src/schemas'
|
||||
import { recordCreated, recordUpdated, recordDeleted } from '../events'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export function safeJsonParse(x: any) {
|
||||
try {
|
||||
return { data: JSON.parse(x), success: true }
|
||||
} catch {
|
||||
return { data: x, success: false }
|
||||
}
|
||||
}
|
||||
|
||||
export const handler: bp.IntegrationProps['handler'] = async ({ req, logger, client }) => {
|
||||
if (!req.body) {
|
||||
const errorMsg = 'Webhook processing failed: empty body'
|
||||
logger.forBot().error(errorMsg)
|
||||
return {
|
||||
status: 400,
|
||||
body: JSON.stringify({ error: errorMsg }),
|
||||
}
|
||||
}
|
||||
|
||||
// Parse and validate the webhook payload
|
||||
const webhookData = safeJsonParse(req.body)
|
||||
if (!webhookData.success) {
|
||||
const errorMsg = 'Webhook processing failed: invalid JSON'
|
||||
logger.forBot().error(errorMsg)
|
||||
return { status: 400, body: JSON.stringify({ error: errorMsg }) }
|
||||
}
|
||||
|
||||
const parseResult = webhookPayloadSchema.safeParse(webhookData.data)
|
||||
if (!parseResult.success) {
|
||||
const errorMsg = `Webhook processing failed: ${parseResult.error.message}`
|
||||
logger.forBot().error(errorMsg)
|
||||
return { status: 400, body: JSON.stringify({ error: errorMsg }) }
|
||||
}
|
||||
|
||||
const { webhook_id, events } = parseResult.data
|
||||
logger.forBot().info(`Processing webhook ${webhook_id} with ${events.length} events`)
|
||||
|
||||
// Process each event
|
||||
for (const attioEvent of events) {
|
||||
const event = attioEvent.event_type
|
||||
|
||||
switch (event) {
|
||||
case 'record.created':
|
||||
await recordCreated({ payload: attioEvent, client, logger })
|
||||
break
|
||||
case 'record.updated':
|
||||
await recordUpdated({ payload: attioEvent, client, logger })
|
||||
break
|
||||
case 'record.deleted':
|
||||
await recordDeleted({ payload: attioEvent, client, logger })
|
||||
break
|
||||
default:
|
||||
logger.forBot().warn(`Unsupported event type: ${event}`)
|
||||
}
|
||||
}
|
||||
|
||||
return { status: 200 }
|
||||
}
|
||||
@@ -0,0 +1,2 @@
|
||||
export { register, unregister } from './setup'
|
||||
export { handler } from './handler'
|
||||
@@ -0,0 +1,76 @@
|
||||
import * as sdk from '@botpress/sdk'
|
||||
import { AttioApiClient } from '../attio-api'
|
||||
import * as bp from '.botpress'
|
||||
|
||||
export const register: bp.IntegrationProps['register'] = async ({ ctx, client, webhookUrl, logger }) => {
|
||||
try {
|
||||
const accessToken = ctx.configuration.accessToken
|
||||
const attioClient = new AttioApiClient(accessToken)
|
||||
|
||||
// Test the connection using the API client
|
||||
logger.forBot().info('Testing connection to Attio...')
|
||||
await attioClient.testConnection()
|
||||
logger.forBot().info('Connection to Attio successful')
|
||||
|
||||
// Check if webhooks already exist
|
||||
logger.forBot().info('Checking if webhooks already exist...')
|
||||
const { state } = await client.getOrSetState({
|
||||
name: 'attioIntegrationInfo',
|
||||
type: 'integration',
|
||||
id: ctx.integrationId,
|
||||
payload: {
|
||||
attioWebhookId: '',
|
||||
},
|
||||
})
|
||||
|
||||
if (!state.payload.attioWebhookId) {
|
||||
logger.forBot().info('Webhooks do not exist. Creating webhooks...')
|
||||
const webhookResp = await attioClient.createWebhook({
|
||||
data: {
|
||||
target_url: webhookUrl,
|
||||
subscriptions: [
|
||||
{ event_type: 'record.created', filter: null },
|
||||
{ event_type: 'record.updated', filter: null },
|
||||
{ event_type: 'record.deleted', filter: null },
|
||||
],
|
||||
},
|
||||
})
|
||||
const attioWebhookId = String(webhookResp.data.id.webhook_id)
|
||||
await client.setState({
|
||||
type: 'integration',
|
||||
name: 'attioIntegrationInfo',
|
||||
id: ctx.integrationId,
|
||||
payload: { attioWebhookId },
|
||||
})
|
||||
}
|
||||
|
||||
logger.forBot().info('Webhooks created')
|
||||
} catch (thrown) {
|
||||
const error = thrown instanceof Error ? thrown : new Error(String(thrown))
|
||||
throw new sdk.RuntimeError(error.message)
|
||||
}
|
||||
}
|
||||
|
||||
export const unregister: bp.IntegrationProps['unregister'] = async ({ ctx, client, logger }) => {
|
||||
try {
|
||||
const accessToken = ctx.configuration.accessToken
|
||||
const attioClient = new AttioApiClient(accessToken)
|
||||
|
||||
const stateAttioIntegrationInfo = await client.getState({
|
||||
id: ctx.integrationId,
|
||||
name: 'attioIntegrationInfo',
|
||||
type: 'integration',
|
||||
})
|
||||
|
||||
const { state } = stateAttioIntegrationInfo
|
||||
const { attioWebhookId } = state.payload
|
||||
|
||||
if (attioWebhookId) {
|
||||
await attioClient.deleteWebhook(attioWebhookId)
|
||||
logger.forBot().info('Webhook successfully deleted')
|
||||
}
|
||||
} catch (thrown) {
|
||||
const error = thrown instanceof Error ? thrown : new Error(String(thrown))
|
||||
throw new sdk.RuntimeError(error.message)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user