Skip to content

Commit 9b18ea6

Browse files
committed
fix(zoho-desk): stop duplicating /api/v1 when resolving a relative attachment href
A relative attachment href that already starts with `api/v1` (as Zoho's hrefs often do) was concatenated onto getZohoDeskApiBase (which ends in /api/v1), producing `/api/v1/api/v1/...` and a failing download. Extract a tested resolveZohoAttachmentUrl helper that uses absolute hrefs as-is and strips a leading slash + `api/v1/` prefix from relative ones before joining, so the path is correct for absolute, root-relative, and api/v1-prefixed hrefs alike.
1 parent 20e65a6 commit 9b18ea6

3 files changed

Lines changed: 48 additions & 5 deletions

File tree

apps/sim/app/api/tools/zoho_desk/attachment/route.ts

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import {
1010
buildZohoDeskHeaders,
1111
deriveAttachmentName,
1212
getZohoDeskApiBase,
13+
resolveZohoAttachmentUrl,
1314
} from '@/tools/zoho_desk/utils'
1415

1516
export const dynamic = 'force-dynamic'
@@ -64,11 +65,10 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
6465

6566
let downloadUrl: URL
6667
try {
67-
downloadUrl = href.startsWith('http')
68-
? new URL(href)
69-
: new URL(
70-
`${getZohoDeskApiBase({ apiDomain: apiDomain ?? undefined })}/${href.replace(/^\/+/, '')}`
71-
)
68+
downloadUrl = resolveZohoAttachmentUrl(
69+
href,
70+
getZohoDeskApiBase({ apiDomain: apiDomain ?? undefined })
71+
)
7272
} catch {
7373
return NextResponse.json({ success: false, error: 'Invalid attachment href' }, { status: 400 })
7474
}

apps/sim/tools/zoho_desk/utils.test.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import {
99
deriveZohoContentText,
1010
getZohoDeskApiBase,
1111
getZohoDeskErrorMessage,
12+
resolveZohoAttachmentUrl,
1213
withDerivedContentText,
1314
} from '@/tools/zoho_desk/utils'
1415

@@ -92,6 +93,34 @@ describe('zoho desk tool utils', () => {
9293
})
9394
})
9495

96+
describe('resolveZohoAttachmentUrl', () => {
97+
const apiBase = 'https://desk.zoho.com/api/v1'
98+
99+
it('uses an absolute http(s) href as-is', () => {
100+
expect(
101+
resolveZohoAttachmentUrl('https://desk.zoho.eu/api/v1/tickets/1/x/content', apiBase).href
102+
).toBe('https://desk.zoho.eu/api/v1/tickets/1/x/content')
103+
})
104+
105+
it('does not duplicate /api/v1 when the relative href already includes it', () => {
106+
expect(
107+
resolveZohoAttachmentUrl('/api/v1/tickets/1/attachments/2/content', apiBase).href
108+
).toBe('https://desk.zoho.com/api/v1/tickets/1/attachments/2/content')
109+
expect(resolveZohoAttachmentUrl('api/v1/tickets/1/attachments/2/content', apiBase).href).toBe(
110+
'https://desk.zoho.com/api/v1/tickets/1/attachments/2/content'
111+
)
112+
})
113+
114+
it('resolves a relative href without an api/v1 prefix against the api base', () => {
115+
expect(resolveZohoAttachmentUrl('/tickets/1/x/content', apiBase).href).toBe(
116+
'https://desk.zoho.com/api/v1/tickets/1/x/content'
117+
)
118+
expect(resolveZohoAttachmentUrl('tickets/1/x/content', apiBase).href).toBe(
119+
'https://desk.zoho.com/api/v1/tickets/1/x/content'
120+
)
121+
})
122+
})
123+
95124
describe('convertZohoHtmlToText', () => {
96125
it('strips HTML tags to readable plain text', () => {
97126
const html = '<div style="direction: ltr; font-size: 13px;"><div>testing</div></div>'

apps/sim/tools/zoho_desk/utils.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,20 @@ export function getZohoDeskApiBase(params: Pick<ZohoDeskBaseParams, 'apiDomain'>
6262
return `${base}/api/v1`
6363
}
6464

65+
/**
66+
* Resolve an attachment `href` into an absolute download URL. Absolute hrefs are
67+
* used as-is; a relative href is resolved against the Desk API base (`apiBase`,
68+
* which ends in `/api/v1`). A leading slash and an already-present `api/v1/`
69+
* prefix are stripped first so a Zoho href like `/api/v1/tickets/1/.../content`
70+
* does not produce a duplicated `/api/v1/api/v1/...` path. Throws on an
71+
* unparseable result (the caller maps that to a 400).
72+
*/
73+
export function resolveZohoAttachmentUrl(href: string, apiBase: string): URL {
74+
if (/^https?:\/\//i.test(href)) return new URL(href)
75+
const path = href.replace(/^\/+/, '').replace(/^api\/v1\//i, '')
76+
return new URL(`${apiBase.replace(/\/+$/, '')}/${path}`)
77+
}
78+
6579
/** Build the auth + org headers required on every Zoho Desk API call. */
6680
export function buildZohoDeskHeaders(
6781
params: Pick<ZohoDeskBaseParams, 'accessToken' | 'orgId'>

0 commit comments

Comments
 (0)