Skip to content

Commit d65bdaf

Browse files
aadamgoughAdam GoughAdam Goughicecrasher321
authored
feat(wealthbox): added wealthbox crm (#669)
* feat: wealthbox * feat: added tools * feat: tested and finished tools * feat: tested and finished tools * feat: added refresh token * fix: added docs * bun lint * feat: removed files #669 * fix: greptile comments * fix: stringified messages #669 * add visibilty to params --------- Co-authored-by: Adam Gough <[email protected]> Co-authored-by: Adam Gough <[email protected]> Co-authored-by: Vikhyath Mondreti <[email protected]>
1 parent 348b524 commit d65bdaf

File tree

23 files changed

+2949
-0
lines changed

23 files changed

+2949
-0
lines changed

apps/docs/content/docs/tools/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@
4848
"twilio_sms",
4949
"typeform",
5050
"vision",
51+
"wealthbox",
5152
"whatsapp",
5253
"x",
5354
"youtube"
Lines changed: 190 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,190 @@
1+
---
2+
title: Wealthbox
3+
description: Interact with Wealthbox
4+
---
5+
6+
import { BlockInfoCard } from "@/components/ui/block-info-card"
7+
8+
<BlockInfoCard
9+
type="wealthbox"
10+
color="#E0E0E0"
11+
icon={true}
12+
iconSvg={`<svg className="block-icon"
13+
14+
xmlns='http://www.w3.org/2000/svg'
15+
version='1.0'
16+
17+
18+
viewBox='50 -50 200 200'
19+
>
20+
<g fill='#106ED4' stroke='none' transform='translate(0, 200) scale(0.15, -0.15)'>
21+
<path d='M764 1542 c-110 -64 -230 -134 -266 -156 -42 -24 -71 -49 -78 -65 -7 -19 -10 -126 -8 -334 3 -291 4 -307 23 -326 11 -11 103 -67 205 -126 102 -59 219 -127 261 -151 42 -24 85 -44 96 -44 23 0 527 288 561 320 22 22 22 23 22 340 0 288 -2 320 -17 338 -32 37 -537 322 -569 321 -18 0 -107 -46 -230 -117z m445 -144 c108 -62 206 -123 219 -135 22 -22 22 -26 22 -261 0 -214 -2 -242 -17 -260 -23 -26 -414 -252 -437 -252 -9 0 -70 31 -134 69 -64 37 -161 94 -215 125 l-97 57 2 261 3 261 210 123 c116 67 219 123 229 123 10 1 107 -50 215 -111z' />
22+
<path d='M700 1246 l-55 -32 -3 -211 -2 -211 37 -23 c21 -12 52 -30 69 -40 l30 -18 103 59 c56 33 109 60 117 60 8 0 62 -27 119 -60 l104 -60 63 37 c35 21 66 42 70 48 4 5 8 101 8 212 l0 202 -62 35 -63 35 -3 -197 c-1 -108 -6 -200 -11 -205 -5 -5 -54 17 -114 52 -58 34 -108 61 -111 61 -2 0 -51 -27 -107 -60 -56 -32 -106 -57 -111 -54 -4 3 -8 95 -8 205 0 109 -3 199 -7 199 -5 -1 -33 -16 -63 -34z' />
23+
</g>
24+
</svg>`}
25+
/>
26+
27+
## Usage Instructions
28+
29+
Integrate Wealthbox functionality to manage notes, contacts, and tasks. Read content from existing notes, contacts, and tasks and write to them using OAuth authentication. Supports text content manipulation for note creation and editing.
30+
31+
32+
33+
## Tools
34+
35+
### `wealthbox_read_note`
36+
37+
Read content from a Wealthbox note
38+
39+
#### Input
40+
41+
| Parameter | Type | Required | Description |
42+
| --------- | ---- | -------- | ----------- |
43+
| `accessToken` | string | Yes | The access token for the Wealthbox API |
44+
| `noteId` | string | No | The ID of the note to read \(optional\) |
45+
46+
#### Output
47+
48+
| Parameter | Type |
49+
| --------- | ---- |
50+
| `note` | string |
51+
| `metadata` | string |
52+
| `noteId` | string |
53+
| `itemType` | string |
54+
55+
### `wealthbox_write_note`
56+
57+
Create or update a Wealthbox note
58+
59+
#### Input
60+
61+
| Parameter | Type | Required | Description |
62+
| --------- | ---- | -------- | ----------- |
63+
| `accessToken` | string | Yes | The access token for the Wealthbox API |
64+
| `content` | string | Yes | The main body of the note |
65+
| `contactId` | string | No | ID of contact to link to this note |
66+
67+
#### Output
68+
69+
| Parameter | Type |
70+
| --------- | ---- |
71+
| `note` | string |
72+
| `metadata` | string |
73+
| `itemType` | string |
74+
75+
### `wealthbox_read_contact`
76+
77+
Read content from a Wealthbox contact
78+
79+
#### Input
80+
81+
| Parameter | Type | Required | Description |
82+
| --------- | ---- | -------- | ----------- |
83+
| `accessToken` | string | Yes | The access token for the Wealthbox API |
84+
| `contactId` | string | Yes | The ID of the contact to read |
85+
86+
#### Output
87+
88+
| Parameter | Type |
89+
| --------- | ---- |
90+
| `contact` | string |
91+
| `metadata` | string |
92+
| `contactId` | string |
93+
| `itemType` | string |
94+
95+
### `wealthbox_write_contact`
96+
97+
Create a new Wealthbox contact
98+
99+
#### Input
100+
101+
| Parameter | Type | Required | Description |
102+
| --------- | ---- | -------- | ----------- |
103+
| `accessToken` | string | Yes | The access token for the Wealthbox API |
104+
| `firstName` | string | Yes | The first name of the contact |
105+
| `lastName` | string | Yes | The last name of the contact |
106+
| `emailAddress` | string | No | The email address of the contact |
107+
| `backgroundInformation` | string | No | Background information about the contact |
108+
109+
#### Output
110+
111+
| Parameter | Type |
112+
| --------- | ---- |
113+
| `contact` | string |
114+
| `metadata` | string |
115+
| `itemType` | string |
116+
117+
### `wealthbox_read_task`
118+
119+
Read content from a Wealthbox task
120+
121+
#### Input
122+
123+
| Parameter | Type | Required | Description |
124+
| --------- | ---- | -------- | ----------- |
125+
| `accessToken` | string | Yes | The access token for the Wealthbox API |
126+
| `taskId` | string | No | The ID of the task to read \(optional\) |
127+
128+
#### Output
129+
130+
| Parameter | Type |
131+
| --------- | ---- |
132+
| `task` | string |
133+
| `metadata` | string |
134+
| `taskId` | string |
135+
| `itemType` | string |
136+
137+
### `wealthbox_write_task`
138+
139+
Create or update a Wealthbox task
140+
141+
#### Input
142+
143+
| Parameter | Type | Required | Description |
144+
| --------- | ---- | -------- | ----------- |
145+
| `accessToken` | string | Yes | The access token for the Wealthbox API |
146+
| `title` | string | Yes | The name/title of the task |
147+
| `dueDate` | string | Yes | The due date and time of the task |
148+
| `complete` | boolean | No | Whether the task is complete |
149+
| `category` | number | No | The category ID the task belongs to |
150+
| `contactId` | string | No | ID of contact to link to this task |
151+
152+
#### Output
153+
154+
| Parameter | Type |
155+
| --------- | ---- |
156+
| `task` | string |
157+
| `metadata` | string |
158+
| `taskId` | string |
159+
| `itemType` | string |
160+
161+
162+
163+
## Block Configuration
164+
165+
### Input
166+
167+
| Parameter | Type | Required | Description |
168+
| --------- | ---- | -------- | ----------- |
169+
| `operation` | string | Yes | Operation |
170+
171+
172+
173+
### Outputs
174+
175+
| Output | Type | Description |
176+
| ------ | ---- | ----------- |
177+
| `note` | any | note output from the block |
178+
| `notes` | any | notes output from the block |
179+
| `contact` | any | contact output from the block |
180+
| `contacts` | any | contacts output from the block |
181+
| `task` | any | task output from the block |
182+
| `tasks` | any | tasks output from the block |
183+
| `metadata` | json | metadata output from the block |
184+
| `success` | any | success output from the block |
185+
186+
187+
## Notes
188+
189+
- Category: `tools`
190+
- Type: `wealthbox`
Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
import { eq } from 'drizzle-orm'
2+
import { type NextRequest, NextResponse } from 'next/server'
3+
import { getSession } from '@/lib/auth'
4+
import { createLogger } from '@/lib/logs/console-logger'
5+
import { db } from '@/db'
6+
import { account } from '@/db/schema'
7+
import { refreshAccessTokenIfNeeded } from '../../utils'
8+
9+
export const dynamic = 'force-dynamic'
10+
11+
const logger = createLogger('WealthboxItemAPI')
12+
13+
/**
14+
* Get a single item (note, contact, task) from Wealthbox
15+
*/
16+
export async function GET(request: NextRequest) {
17+
const requestId = crypto.randomUUID().slice(0, 8)
18+
19+
try {
20+
// Get the session
21+
const session = await getSession()
22+
23+
// Check if the user is authenticated
24+
if (!session?.user?.id) {
25+
logger.warn(`[${requestId}] Unauthenticated request rejected`)
26+
return NextResponse.json({ error: 'User not authenticated' }, { status: 401 })
27+
}
28+
29+
// Get parameters from query
30+
const { searchParams } = new URL(request.url)
31+
const credentialId = searchParams.get('credentialId')
32+
const itemId = searchParams.get('itemId')
33+
const type = searchParams.get('type') || 'contact'
34+
35+
if (!credentialId || !itemId) {
36+
logger.warn(`[${requestId}] Missing required parameters`, { credentialId, itemId })
37+
return NextResponse.json({ error: 'Credential ID and Item ID are required' }, { status: 400 })
38+
}
39+
40+
// Validate item type - only handle contacts now
41+
if (type !== 'contact') {
42+
logger.warn(`[${requestId}] Invalid item type: ${type}`)
43+
return NextResponse.json(
44+
{ error: 'Invalid item type. Only contact is supported.' },
45+
{ status: 400 }
46+
)
47+
}
48+
49+
// Get the credential from the database
50+
const credentials = await db.select().from(account).where(eq(account.id, credentialId)).limit(1)
51+
52+
if (!credentials.length) {
53+
logger.warn(`[${requestId}] Credential not found`, { credentialId })
54+
return NextResponse.json({ error: 'Credential not found' }, { status: 404 })
55+
}
56+
57+
const credential = credentials[0]
58+
59+
// Check if the credential belongs to the user
60+
if (credential.userId !== session.user.id) {
61+
logger.warn(`[${requestId}] Unauthorized credential access attempt`, {
62+
credentialUserId: credential.userId,
63+
requestUserId: session.user.id,
64+
})
65+
return NextResponse.json({ error: 'Unauthorized' }, { status: 403 })
66+
}
67+
68+
// Refresh access token if needed
69+
const accessToken = await refreshAccessTokenIfNeeded(credentialId, session.user.id, requestId)
70+
71+
if (!accessToken) {
72+
logger.error(`[${requestId}] Failed to obtain valid access token`)
73+
return NextResponse.json({ error: 'Failed to obtain valid access token' }, { status: 401 })
74+
}
75+
76+
// Determine the endpoint based on item type - only contacts
77+
const endpoints = {
78+
contact: 'contacts',
79+
}
80+
const endpoint = endpoints[type as keyof typeof endpoints]
81+
82+
logger.info(`[${requestId}] Fetching ${type} ${itemId} from Wealthbox`)
83+
84+
// Make request to Wealthbox API
85+
const response = await fetch(`https://api.crmworkspace.com/v1/${endpoint}/${itemId}`, {
86+
headers: {
87+
Authorization: `Bearer ${accessToken}`,
88+
'Content-Type': 'application/json',
89+
},
90+
})
91+
92+
if (!response.ok) {
93+
const errorText = await response.text()
94+
logger.error(
95+
`[${requestId}] Wealthbox API error: ${response.status} ${response.statusText}`,
96+
{
97+
error: errorText,
98+
endpoint,
99+
itemId,
100+
}
101+
)
102+
103+
if (response.status === 404) {
104+
return NextResponse.json({ error: 'Item not found' }, { status: 404 })
105+
}
106+
107+
return NextResponse.json(
108+
{ error: `Failed to fetch ${type} from Wealthbox` },
109+
{ status: response.status }
110+
)
111+
}
112+
113+
const data = await response.json()
114+
115+
logger.info(`[${requestId}] Wealthbox API response structure`, {
116+
type,
117+
dataKeys: Object.keys(data || {}),
118+
hasContacts: !!data.contacts,
119+
totalCount: data.meta?.total_count,
120+
})
121+
122+
// Transform the response to match our expected format
123+
let items: any[] = []
124+
125+
if (type === 'contact') {
126+
// Handle single contact response - API returns contact data directly when fetching by ID
127+
if (data?.id) {
128+
// Single contact response
129+
const item = {
130+
id: data.id?.toString() || '',
131+
name: `${data.first_name || ''} ${data.last_name || ''}`.trim() || `Contact ${data.id}`,
132+
type: 'contact',
133+
content: data.background_info || '',
134+
createdAt: data.created_at,
135+
updatedAt: data.updated_at,
136+
}
137+
items = [item]
138+
} else {
139+
logger.warn(`[${requestId}] Unexpected contact response format`, { data })
140+
items = []
141+
}
142+
}
143+
144+
logger.info(
145+
`[${requestId}] Successfully fetched ${items.length} ${type}s from Wealthbox (total: ${data.meta?.total_count || 'unknown'})`
146+
)
147+
148+
return NextResponse.json({ item: items[0] }, { status: 200 })
149+
} catch (error) {
150+
logger.error(`[${requestId}] Error fetching Wealthbox item`, error)
151+
return NextResponse.json({ error: 'Internal server error' }, { status: 500 })
152+
}
153+
}

0 commit comments

Comments
 (0)