Skip to content

Commit 5e5d132

Browse files
committed
fix(short-io): address PR review feedback
- Change apiKey visibility from 'hidden' to 'user-only' in all 6 tools - Simplify block tool selector to string interpolation - Move QR code generation to server-side API route, return as file object (name, mimeType, data, size) matching standard file pattern - Update block outputs and docs to reflect file type for QR code
1 parent 9dd9066 commit 5e5d132

File tree

9 files changed

+121
-50
lines changed

9 files changed

+121
-50
lines changed

apps/docs/content/docs/en/tools/short_io.mdx

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -135,9 +135,7 @@ Generate a QR code for a Short.io link. Returns a base64 data URL (e.g. data:ima
135135

136136
| Parameter | Type | Description |
137137
| ---------- | ------- | ------------------------------------------------ |
138-
| `success` | boolean | Success status |
139-
| `qrCodeURL`| string | Base64 data URL of the QR code image |
140-
| `error` | string | Error message |
138+
| `file` | file | Generated QR code image file |
141139

142140
### `short_io_get_analytics`
143141

Lines changed: 98 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
import { createLogger } from '@sim/logger'
2+
import { type NextRequest, NextResponse } from 'next/server'
3+
import { z } from 'zod'
4+
import { checkInternalAuth } from '@/lib/auth/hybrid'
5+
import { generateRequestId } from '@/lib/core/utils/request'
6+
7+
export const dynamic = 'force-dynamic'
8+
9+
const logger = createLogger('ShortIoQrAPI')
10+
11+
const ShortIoQrSchema = z.object({
12+
apiKey: z.string().min(1, 'API key is required'),
13+
linkId: z.string().min(1, 'Link ID is required'),
14+
color: z.string().optional(),
15+
backgroundColor: z.string().optional(),
16+
size: z.number().min(1).max(99).optional(),
17+
type: z.enum(['png', 'svg']).optional(),
18+
useDomainSettings: z.boolean().optional(),
19+
})
20+
21+
export async function POST(request: NextRequest) {
22+
const requestId = generateRequestId()
23+
24+
try {
25+
const authResult = await checkInternalAuth(request, { requireWorkflowId: false })
26+
27+
if (!authResult.success) {
28+
logger.warn(`[${requestId}] Unauthorized Short.io QR request: ${authResult.error}`)
29+
return NextResponse.json(
30+
{ success: false, error: authResult.error || 'Authentication required' },
31+
{ status: 401 }
32+
)
33+
}
34+
35+
const body = await request.json()
36+
const validated = ShortIoQrSchema.parse(body)
37+
38+
const qrBody: Record<string, unknown> = {
39+
useDomainSettings: validated.useDomainSettings ?? true,
40+
}
41+
if (validated.color) qrBody.color = validated.color
42+
if (validated.backgroundColor) qrBody.backgroundColor = validated.backgroundColor
43+
if (validated.size) qrBody.size = validated.size
44+
if (validated.type) qrBody.type = validated.type
45+
46+
const response = await fetch(`https://api.short.io/links/qr/${validated.linkId}`, {
47+
method: 'POST',
48+
headers: {
49+
Authorization: validated.apiKey,
50+
'Content-Type': 'application/json',
51+
},
52+
body: JSON.stringify(qrBody),
53+
})
54+
55+
if (!response.ok) {
56+
const errorText = await response.text().catch(() => response.statusText)
57+
logger.error(`[${requestId}] Short.io QR API error: ${errorText}`)
58+
return NextResponse.json(
59+
{ success: false, error: `Short.io API error: ${errorText}` },
60+
{ status: response.status }
61+
)
62+
}
63+
64+
const contentType = response.headers.get('Content-Type') ?? 'image/png'
65+
const fileBuffer = Buffer.from(await response.arrayBuffer())
66+
const mimeType = contentType.split(';')[0]?.trim() || 'image/png'
67+
const ext = validated.type === 'svg' ? 'svg' : 'png'
68+
const fileName = `qr-${validated.linkId}.${ext}`
69+
70+
logger.info(`[${requestId}] QR code generated`, {
71+
linkId: validated.linkId,
72+
size: fileBuffer.length,
73+
mimeType,
74+
})
75+
76+
return NextResponse.json({
77+
success: true,
78+
output: {
79+
file: {
80+
name: fileName,
81+
mimeType,
82+
data: fileBuffer.toString('base64'),
83+
size: fileBuffer.length,
84+
},
85+
},
86+
})
87+
} catch (error: unknown) {
88+
if (error instanceof z.ZodError) {
89+
return NextResponse.json(
90+
{ success: false, error: `Validation error: ${error.errors.map((e) => e.message).join(', ')}` },
91+
{ status: 400 }
92+
)
93+
}
94+
const message = error instanceof Error ? error.message : 'Unknown error'
95+
logger.error(`[${requestId}] Short.io QR error: ${message}`)
96+
return NextResponse.json({ success: false, error: message }, { status: 500 })
97+
}
98+
}

apps/sim/blocks/blocks/short_io.ts

Lines changed: 2 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -159,24 +159,7 @@ export const ShortIoBlock: BlockConfig<ToolResponse> = {
159159
'short_io_get_analytics',
160160
],
161161
config: {
162-
tool: (params) => {
163-
switch (params.operation) {
164-
case 'create_link':
165-
return 'short_io_create_link'
166-
case 'list_domains':
167-
return 'short_io_list_domains'
168-
case 'list_links':
169-
return 'short_io_list_links'
170-
case 'delete_link':
171-
return 'short_io_delete_link'
172-
case 'get_qr_code':
173-
return 'short_io_get_qr_code'
174-
case 'get_analytics':
175-
return 'short_io_get_analytics'
176-
default:
177-
throw new Error(`Invalid Short.io operation: ${params.operation}`)
178-
}
179-
},
162+
tool: (params) => `short_io_${params.operation}`,
180163
params: (params) => {
181164
const { apiKey, operation, size, domainId, limit, ...rest } = params
182165
const out: Record<string, unknown> = { ...rest, apiKey }
@@ -222,7 +205,7 @@ export const ShortIoBlock: BlockConfig<ToolResponse> = {
222205
links: { type: 'array', description: 'List of links (from List Links)' },
223206
nextPageToken: { type: 'string', description: 'Pagination token for next page' },
224207
deleted: { type: 'boolean', description: 'Whether the link was deleted' },
225-
qrCodeURL: { type: 'string', description: 'Base64 data URL of the generated QR code image' },
208+
file: { type: 'file', description: 'Generated QR code image file' },
226209
clicks: { type: 'number', description: 'Total clicks in period' },
227210
totalClicks: { type: 'number', description: 'Total clicks' },
228211
humanClicks: { type: 'number', description: 'Human clicks' },

apps/sim/tools/short_io/create_link.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const shortIoCreateLinkTool: ToolConfig<ShortIoCreateLinkParams, ToolResp
1010
apiKey: {
1111
type: 'string',
1212
required: true,
13-
visibility: 'hidden',
13+
visibility: 'user-only',
1414
description: 'Short.io Secret API Key',
1515
},
1616
domain: {

apps/sim/tools/short_io/delete_link.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const shortIoDeleteLinkTool: ToolConfig<ShortIoDeleteLinkParams, ToolResp
77
description: 'Delete a short link by ID (e.g. lnk_abc123_abcdef). Rate limit 20/s.',
88
version: '1.0',
99
params: {
10-
apiKey: { type: 'string', required: true, visibility: 'hidden', description: 'Short.io Secret API Key' },
10+
apiKey: { type: 'string', required: true, visibility: 'user-only', description: 'Short.io Secret API Key' },
1111
linkId: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Link ID to delete' },
1212
},
1313
request: {

apps/sim/tools/short_io/get_analytics.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ export const shortIoGetAnalyticsTool: ToolConfig<ShortIoGetAnalyticsParams, Tool
2222
apiKey: {
2323
type: 'string',
2424
required: true,
25-
visibility: 'hidden',
25+
visibility: 'user-only',
2626
description: 'Short.io Secret API Key',
2727
},
2828
linkId: { type: 'string', required: true, visibility: 'user-or-llm', description: 'Link ID' },

apps/sim/tools/short_io/get_qr_code.ts

Lines changed: 15 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ export const shortIoGetQrCodeTool: ToolConfig<ShortIoGetQrParams, ToolResponse>
1010
apiKey: {
1111
type: 'string',
1212
required: true,
13-
visibility: 'hidden',
13+
visibility: 'user-only',
1414
description: 'Short.io Secret API Key',
1515
},
1616
linkId: {
@@ -46,14 +46,15 @@ export const shortIoGetQrCodeTool: ToolConfig<ShortIoGetQrParams, ToolResponse>
4646
},
4747
},
4848
request: {
49-
url: (params) => `https://api.short.io/links/qr/${params.linkId}`,
49+
url: '/api/tools/short_io/qr',
5050
method: 'POST',
51-
headers: (params) => ({
52-
Authorization: params.apiKey,
51+
headers: () => ({
5352
'Content-Type': 'application/json',
5453
}),
5554
body: (params) => {
5655
const body: Record<string, unknown> = {
56+
apiKey: params.apiKey,
57+
linkId: params.linkId,
5758
useDomainSettings: params.useDomainSettings ?? true,
5859
}
5960
if (params.color != null && params.color !== '') body.color = params.color
@@ -64,31 +65,22 @@ export const shortIoGetQrCodeTool: ToolConfig<ShortIoGetQrParams, ToolResponse>
6465
},
6566
},
6667
transformResponse: async (response: Response) => {
67-
if (!response.ok) {
68-
const err = await response.text().catch(() => response.statusText)
69-
return { success: false, output: { success: false, error: err } }
68+
const data = await response.json().catch(() => ({}))
69+
if (!response.ok || !data.success) {
70+
return {
71+
success: false,
72+
output: { success: false, error: data.error || response.statusText },
73+
}
7074
}
71-
72-
const contentType = response.headers.get('Content-Type') ?? ''
73-
const blob = await response.blob()
74-
const arrayBuffer = await blob.arrayBuffer()
75-
const bytes = new Uint8Array(arrayBuffer)
76-
let binary = ''
77-
for (let i = 0; i < bytes.length; i++) binary += String.fromCharCode(bytes[i]!)
78-
const base64 = typeof btoa !== 'undefined' ? btoa(binary) : ''
79-
const mediaType = contentType.split(';')[0]?.trim() || 'image/png'
80-
const dataUrl = base64 ? `data:${mediaType};base64,${base64}` : ''
8175
return {
8276
success: true,
83-
output: { success: true, qrCodeURL: dataUrl },
77+
output: data.output,
8478
}
8579
},
8680
outputs: {
87-
success: { type: 'boolean', description: 'Success status' },
88-
qrCodeURL: {
89-
type: 'string',
90-
description: 'Base64 data URL of the QR code image (e.g. data:image/png;base64,...)',
81+
file: {
82+
type: 'file',
83+
description: 'Generated QR code image file',
9184
},
92-
error: { type: 'string', description: 'Error message' },
9385
},
9486
}

apps/sim/tools/short_io/list_domains.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const shortIoListDomainsTool: ToolConfig<ShortIoListDomainsParams, ToolRe
77
description: 'List Short.io domains. Returns domain IDs and details for use in List Links.',
88
version: '1.0',
99
params: {
10-
apiKey: { type: 'string', required: true, visibility: 'hidden', description: 'Short.io Secret API Key' },
10+
apiKey: { type: 'string', required: true, visibility: 'user-only', description: 'Short.io Secret API Key' },
1111
},
1212
request: {
1313
url: 'https://api.short.io/api/domains',

apps/sim/tools/short_io/list_links.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const shortIoListLinksTool: ToolConfig<ShortIoListLinksParams, ToolRespon
88
'List short links for a domain. Requires domain_id (from List Domains or dashboard). Max 150 per request.',
99
version: '1.0',
1010
params: {
11-
apiKey: { type: 'string', required: true, visibility: 'hidden', description: 'Short.io Secret API Key' },
11+
apiKey: { type: 'string', required: true, visibility: 'user-only', description: 'Short.io Secret API Key' },
1212
domainId: {
1313
type: 'number',
1414
required: true,

0 commit comments

Comments
 (0)