Skip to content

Commit 04535ee

Browse files
authored
feat(buffer): add Buffer integration with posts, channels, and ideas (#5637)
* feat(buffer): add Buffer integration with posts, channels, and ideas * fix(buffer): return clear tool error when account lookup yields no account * fix(buffer): default schedulingType server-side so basic-mode blocks never fail validation * fix(buffer): probe Content-Type for extensionless media URLs so videos are not sent as images * feat(buffer): add get_ideas and get_idea_groups tools, harden media URL classification * fix(buffer): guard missing post on PostActionSuccess responses
1 parent 836ceda commit 04535ee

27 files changed

Lines changed: 2990 additions & 2 deletions

File tree

apps/docs/components/icons.tsx

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2533,6 +2533,24 @@ l57 -85 -48 -124 c-203 -517 -79 -930 346 -1155 159 -85 441 -71 585 28 l111
25332533
)
25342534
}
25352535

2536+
export function BufferIcon(props: SVGProps<SVGSVGElement>) {
2537+
return (
2538+
<svg
2539+
{...props}
2540+
viewBox='0 0 86.7 97.9'
2541+
fill='currentColor'
2542+
role='img'
2543+
xmlns='http://www.w3.org/2000/svg'
2544+
>
2545+
<path
2546+
fillRule='evenodd'
2547+
clipRule='evenodd'
2548+
d='M0,22.3L43.1,0l43.6,22.3L43.1,44.8L0,22.3z M43.1,83.1l-29.4-16L0,74.5l43.1,23.4l43.6-23.4l-13.9-7.4 L43.1,83.1z M13.7,42l29.4,14.5L72.9,42l13.9,6.8L43.1,69.9L0,48.7L13.7,42z'
2549+
/>
2550+
</svg>
2551+
)
2552+
}
2553+
25362554
export function Mem0Icon(props: SVGProps<SVGSVGElement>) {
25372555
return (
25382556
<svg

apps/docs/components/ui/icon-mapping.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ import {
2727
BrexIcon,
2828
BrightDataIcon,
2929
BrowserUseIcon,
30+
BufferIcon,
3031
CalComIcon,
3132
CalendlyIcon,
3233
CirclebackIcon,
@@ -264,6 +265,7 @@ export const blockTypeToIconMap: Record<string, IconComponent> = {
264265
brex: BrexIcon,
265266
brightdata: BrightDataIcon,
266267
browser_use: BrowserUseIcon,
268+
buffer: BufferIcon,
267269
calcom: CalComIcon,
268270
calendly: CalendlyIcon,
269271
circleback: CirclebackIcon,

apps/docs/content/docs/en/integrations/buffer.mdx

Lines changed: 355 additions & 0 deletions
Large diffs are not rendered by default.

apps/docs/content/docs/en/integrations/meta.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,7 @@
2525
"brex",
2626
"brightdata",
2727
"browser_use",
28+
"buffer",
2829
"calcom",
2930
"calendly",
3031
"circleback",
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { createLogger } from '@sim/logger'
2+
import { getErrorMessage } from '@sim/utils/errors'
3+
import { type NextRequest, NextResponse } from 'next/server'
4+
import { bufferCreatePostContract } from '@/lib/api/contracts/tools/buffer'
5+
import { getValidationErrorMessage, parseRequest } from '@/lib/api/server'
6+
import { checkInternalAuth } from '@/lib/auth/hybrid'
7+
import { generateRequestId } from '@/lib/core/utils/request'
8+
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
9+
import { forwardPostMutation } from '@/app/api/tools/buffer/server-utils'
10+
11+
export const dynamic = 'force-dynamic'
12+
13+
const logger = createLogger('BufferCreatePostAPI')
14+
15+
export const POST = withRouteHandler(async (request: NextRequest) => {
16+
const requestId = generateRequestId()
17+
18+
try {
19+
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
20+
if (!authResult.success || !authResult.userId) {
21+
logger.warn(`[${requestId}] Unauthorized Buffer create post attempt`, {
22+
error: authResult.error || 'Missing userId',
23+
})
24+
return NextResponse.json(
25+
{ success: false, error: authResult.error || 'Unauthorized' },
26+
{ status: 401 }
27+
)
28+
}
29+
30+
const parsed = await parseRequest(
31+
bufferCreatePostContract,
32+
request,
33+
{},
34+
{
35+
validationErrorResponse: (error) => {
36+
logger.warn(`[${requestId}] Invalid request data`, { errors: error.issues })
37+
return NextResponse.json(
38+
{
39+
success: false,
40+
error: getValidationErrorMessage(error, 'Invalid request data'),
41+
},
42+
{ status: 400 }
43+
)
44+
},
45+
}
46+
)
47+
if (!parsed.success) return parsed.response
48+
const body = parsed.data.body
49+
50+
return await forwardPostMutation({
51+
apiKey: body.apiKey,
52+
channelId: body.channelId,
53+
text: body.text,
54+
mode: body.mode,
55+
schedulingType: body.schedulingType,
56+
dueAt: body.dueAt,
57+
saveToDraft: body.saveToDraft,
58+
media: body.media,
59+
mediaAltText: body.mediaAltText,
60+
userId: authResult.userId,
61+
requestId,
62+
logger,
63+
})
64+
} catch (error) {
65+
const message = getErrorMessage(error, 'Failed to create post')
66+
logger.error(`[${requestId}] Buffer create post failed`, { error: message })
67+
return NextResponse.json({ success: false, error: message }, { status: 500 })
68+
}
69+
})
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
import { createLogger } from '@sim/logger'
2+
import { getErrorMessage } from '@sim/utils/errors'
3+
import { type NextRequest, NextResponse } from 'next/server'
4+
import { bufferEditPostContract } from '@/lib/api/contracts/tools/buffer'
5+
import { getValidationErrorMessage, parseRequest } from '@/lib/api/server'
6+
import { checkInternalAuth } from '@/lib/auth/hybrid'
7+
import { generateRequestId } from '@/lib/core/utils/request'
8+
import { withRouteHandler } from '@/lib/core/utils/with-route-handler'
9+
import { forwardPostMutation } from '@/app/api/tools/buffer/server-utils'
10+
11+
export const dynamic = 'force-dynamic'
12+
13+
const logger = createLogger('BufferEditPostAPI')
14+
15+
export const POST = withRouteHandler(async (request: NextRequest) => {
16+
const requestId = generateRequestId()
17+
18+
try {
19+
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
20+
if (!authResult.success || !authResult.userId) {
21+
logger.warn(`[${requestId}] Unauthorized Buffer edit post attempt`, {
22+
error: authResult.error || 'Missing userId',
23+
})
24+
return NextResponse.json(
25+
{ success: false, error: authResult.error || 'Unauthorized' },
26+
{ status: 401 }
27+
)
28+
}
29+
30+
const parsed = await parseRequest(
31+
bufferEditPostContract,
32+
request,
33+
{},
34+
{
35+
validationErrorResponse: (error) => {
36+
logger.warn(`[${requestId}] Invalid request data`, { errors: error.issues })
37+
return NextResponse.json(
38+
{
39+
success: false,
40+
error: getValidationErrorMessage(error, 'Invalid request data'),
41+
},
42+
{ status: 400 }
43+
)
44+
},
45+
}
46+
)
47+
if (!parsed.success) return parsed.response
48+
const body = parsed.data.body
49+
50+
return await forwardPostMutation({
51+
apiKey: body.apiKey,
52+
postId: body.postId,
53+
text: body.text,
54+
mode: body.mode,
55+
schedulingType: body.schedulingType,
56+
dueAt: body.dueAt,
57+
saveToDraft: body.saveToDraft,
58+
media: body.media,
59+
mediaAltText: body.mediaAltText,
60+
userId: authResult.userId,
61+
requestId,
62+
logger,
63+
})
64+
} catch (error) {
65+
const message = getErrorMessage(error, 'Failed to edit post')
66+
logger.error(`[${requestId}] Buffer edit post failed`, { error: message })
67+
return NextResponse.json({ success: false, error: message }, { status: 500 })
68+
}
69+
})

0 commit comments

Comments
 (0)