Skip to content

Commit 52b655a

Browse files
committed
fix(supabase): remove non-functional SQL introspection path, harden storage URL encoding, add missing storage endpoints
- introspect.ts no longer attempts raw SQL via a nonexistent PostgREST RPC endpoint (always failed); now uses the OpenAPI-spec path directly with honest heuristic/best-effort documentation for PK/FK/index fields - storage tools now trim + URL-encode bucket/path segments before building request URLs (download, list, get_public_url, create_signed_url, delete_bucket, delete, and the upload API route) - storage_create_signed_url guards against a missing signedURL field in the response instead of silently building a broken URL - add supabase_storage_create_signed_upload_url, supabase_storage_update_bucket, and supabase_storage_empty_bucket tools + block wiring - change insert/upsert `data` param type from 'array' to 'json' to match actual accepted shapes (array or single object) - bump tool versions to semver (1.0 -> 1.0.0) - rewrite a BlockMeta template that implied unsupported Supabase Auth Admin user-provisioning
1 parent 62ef96f commit 52b655a

32 files changed

Lines changed: 530 additions & 477 deletions

apps/sim/app/api/tools/supabase/storage-upload/route.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ import { processSingleFileToUserFile } from '@/lib/uploads/utils/file-utils'
1111
import { downloadServableFileFromStorage } from '@/lib/uploads/utils/file-utils.server'
1212
import { docNotReadyResponse } from '@/lib/uploads/utils/servable-file-response'
1313
import { assertToolFileAccess } from '@/app/api/files/authorization'
14+
import { encodeStoragePath, encodeStorageSegment } from '@/tools/supabase/utils'
1415

1516
export const dynamic = 'force-dynamic'
1617

@@ -185,7 +186,9 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
185186
return NextResponse.json({ success: false, error: projectValidation.error }, { status: 400 })
186187
}
187188

188-
const supabaseUrl = `https://${projectValidation.sanitized}.supabase.co/storage/v1/object/${validatedData.bucket}/${fullPath}`
189+
const encodedBucket = encodeStorageSegment(validatedData.bucket)
190+
const encodedPath = encodeStoragePath(fullPath)
191+
const supabaseUrl = `https://${projectValidation.sanitized}.supabase.co/storage/v1/object/${encodedBucket}/${encodedPath}`
189192

190193
const headers: Record<string, string> = {
191194
apikey: validatedData.apiKey,
@@ -248,7 +251,7 @@ export const POST = withRouteHandler(async (request: NextRequest) => {
248251
path: fullPath,
249252
})
250253

251-
const publicUrl = `https://${projectValidation.sanitized}.supabase.co/storage/v1/object/public/${validatedData.bucket}/${fullPath}`
254+
const publicUrl = `https://${projectValidation.sanitized}.supabase.co/storage/v1/object/public/${encodedBucket}/${encodedPath}`
252255

253256
return NextResponse.json({
254257
success: true,

apps/sim/blocks/blocks/supabase.ts

Lines changed: 51 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -45,7 +45,10 @@ export const SupabaseBlock: BlockConfig<SupabaseResponse> = {
4545
{ label: 'Storage: Copy File', id: 'storage_copy' },
4646
{ label: 'Storage: Get Public URL', id: 'storage_get_public_url' },
4747
{ label: 'Storage: Create Signed URL', id: 'storage_create_signed_url' },
48+
{ label: 'Storage: Create Signed Upload URL', id: 'storage_create_signed_upload_url' },
4849
{ label: 'Storage: Create Bucket', id: 'storage_create_bucket' },
50+
{ label: 'Storage: Update Bucket', id: 'storage_update_bucket' },
51+
{ label: 'Storage: Empty Bucket', id: 'storage_empty_bucket' },
4952
{ label: 'Storage: List Buckets', id: 'storage_list_buckets' },
5053
{ label: 'Storage: Delete Bucket', id: 'storage_delete_bucket' },
5154
],
@@ -686,9 +689,12 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
686689
'storage_move',
687690
'storage_copy',
688691
'storage_create_bucket',
692+
'storage_update_bucket',
693+
'storage_empty_bucket',
689694
'storage_delete_bucket',
690695
'storage_get_public_url',
691696
'storage_create_signed_url',
697+
'storage_create_signed_upload_url',
692698
],
693699
},
694700
required: true,
@@ -917,21 +923,43 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
917923
{ label: 'True (Public)', id: 'true' },
918924
],
919925
value: () => 'false',
920-
condition: { field: 'operation', value: 'storage_create_bucket' },
926+
condition: { field: 'operation', value: ['storage_create_bucket', 'storage_update_bucket'] },
921927
},
922928
{
923929
id: 'fileSizeLimit',
924930
title: 'File Size Limit (bytes)',
925931
type: 'short-input',
926932
placeholder: '52428800',
927-
condition: { field: 'operation', value: 'storage_create_bucket' },
933+
condition: { field: 'operation', value: ['storage_create_bucket', 'storage_update_bucket'] },
934+
mode: 'advanced',
928935
},
929936
{
930937
id: 'allowedMimeTypes',
931938
title: 'Allowed MIME Types (JSON array)',
932939
type: 'code',
933940
placeholder: '["image/png", "image/jpeg"]',
934-
condition: { field: 'operation', value: 'storage_create_bucket' },
941+
condition: { field: 'operation', value: ['storage_create_bucket', 'storage_update_bucket'] },
942+
mode: 'advanced',
943+
},
944+
{
945+
id: 'path',
946+
title: 'Destination Path',
947+
type: 'short-input',
948+
placeholder: 'folder/file.jpg',
949+
condition: { field: 'operation', value: 'storage_create_signed_upload_url' },
950+
required: true,
951+
},
952+
{
953+
id: 'upsert',
954+
title: 'Allow Overwrite',
955+
type: 'dropdown',
956+
options: [
957+
{ label: 'False', id: 'false' },
958+
{ label: 'True', id: 'true' },
959+
],
960+
value: () => 'false',
961+
condition: { field: 'operation', value: 'storage_create_signed_upload_url' },
962+
mode: 'advanced',
935963
},
936964
],
937965
tools: {
@@ -955,10 +983,13 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
955983
'supabase_storage_move',
956984
'supabase_storage_copy',
957985
'supabase_storage_create_bucket',
986+
'supabase_storage_update_bucket',
987+
'supabase_storage_empty_bucket',
958988
'supabase_storage_list_buckets',
959989
'supabase_storage_delete_bucket',
960990
'supabase_storage_get_public_url',
961991
'supabase_storage_create_signed_url',
992+
'supabase_storage_create_signed_upload_url',
962993
],
963994
config: {
964995
tool: (params) => {
@@ -1001,6 +1032,10 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
10011032
return 'supabase_storage_copy'
10021033
case 'storage_create_bucket':
10031034
return 'supabase_storage_create_bucket'
1035+
case 'storage_update_bucket':
1036+
return 'supabase_storage_update_bucket'
1037+
case 'storage_empty_bucket':
1038+
return 'supabase_storage_empty_bucket'
10041039
case 'storage_list_buckets':
10051040
return 'supabase_storage_list_buckets'
10061041
case 'storage_delete_bucket':
@@ -1009,6 +1044,8 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
10091044
return 'supabase_storage_get_public_url'
10101045
case 'storage_create_signed_url':
10111046
return 'supabase_storage_create_signed_url'
1047+
case 'storage_create_signed_upload_url':
1048+
return 'supabase_storage_create_signed_upload_url'
10121049
default:
10131050
throw new Error(`Invalid Supabase operation: ${params.operation}`)
10141051
}
@@ -1281,7 +1318,15 @@ Return ONLY the PostgREST filter expression - no explanations, no markdown, no e
12811318
},
12821319
signedUrl: {
12831320
type: 'string',
1284-
description: 'Temporary signed URL for storage file',
1321+
description: 'Temporary signed URL for storage file (download or upload)',
1322+
},
1323+
token: {
1324+
type: 'string',
1325+
description: 'Upload token embedded in the signed upload URL',
1326+
},
1327+
path: {
1328+
type: 'string',
1329+
description: 'Destination object path for the signed upload URL',
12851330
},
12861331
tables: {
12871332
type: 'json',
@@ -1300,9 +1345,9 @@ export const SupabaseBlockMeta = {
13001345
templates: [
13011346
{
13021347
icon: SupabaseIcon,
1303-
title: 'Supabase user provisioning',
1348+
title: 'Supabase customer record sync',
13041349
prompt:
1305-
'Build a workflow that listens for Stripe new-customer events, provisions a Supabase user with the correct role and metadata, and emails the welcome login link.',
1350+
'Build a workflow that listens for Stripe new-customer events and upserts a row into a Supabase customers table with the correct plan and metadata, then emails a welcome message.',
13061351
modules: ['agent', 'workflows'],
13071352
category: 'operations',
13081353
tags: ['enterprise', 'automation'],

apps/sim/tools/registry.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3691,14 +3691,17 @@ import {
36913691
supabaseRpcTool,
36923692
supabaseStorageCopyTool,
36933693
supabaseStorageCreateBucketTool,
3694+
supabaseStorageCreateSignedUploadUrlTool,
36943695
supabaseStorageCreateSignedUrlTool,
36953696
supabaseStorageDeleteBucketTool,
36963697
supabaseStorageDeleteTool,
36973698
supabaseStorageDownloadTool,
3699+
supabaseStorageEmptyBucketTool,
36983700
supabaseStorageGetPublicUrlTool,
36993701
supabaseStorageListBucketsTool,
37003702
supabaseStorageListTool,
37013703
supabaseStorageMoveTool,
3704+
supabaseStorageUpdateBucketTool,
37023705
supabaseStorageUploadTool,
37033706
supabaseTextSearchTool,
37043707
supabaseUpdateTool,
@@ -5192,10 +5195,13 @@ export const tools: Record<string, ToolConfig> = {
51925195
supabase_storage_move: supabaseStorageMoveTool,
51935196
supabase_storage_copy: supabaseStorageCopyTool,
51945197
supabase_storage_create_bucket: supabaseStorageCreateBucketTool,
5198+
supabase_storage_update_bucket: supabaseStorageUpdateBucketTool,
5199+
supabase_storage_empty_bucket: supabaseStorageEmptyBucketTool,
51955200
supabase_storage_list_buckets: supabaseStorageListBucketsTool,
51965201
supabase_storage_delete_bucket: supabaseStorageDeleteBucketTool,
51975202
supabase_storage_get_public_url: supabaseStorageGetPublicUrlTool,
51985203
supabase_storage_create_signed_url: supabaseStorageCreateSignedUrlTool,
5204+
supabase_storage_create_signed_upload_url: supabaseStorageCreateSignedUploadUrlTool,
51995205
tailscale_list_devices: tailscaleListDevicesTool,
52005206
tailscale_get_device: tailscaleGetDeviceTool,
52015207
tailscale_delete_device: tailscaleDeleteDeviceTool,

apps/sim/tools/supabase/count.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const countTool: ToolConfig<SupabaseCountParams, SupabaseCountResponse> =
77
id: 'supabase_count',
88
name: 'Supabase Count',
99
description: 'Count rows in a Supabase table',
10-
version: '1.0',
10+
version: '1.0.0',
1111

1212
params: {
1313
projectId: {

apps/sim/tools/supabase/delete.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const deleteTool: ToolConfig<SupabaseDeleteParams, SupabaseDeleteResponse
77
id: 'supabase_delete',
88
name: 'Supabase Delete Row',
99
description: 'Delete rows from a Supabase table based on filter criteria',
10-
version: '1.0',
10+
version: '1.0.0',
1111

1212
params: {
1313
projectId: {

apps/sim/tools/supabase/get_row.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const getRowTool: ToolConfig<SupabaseGetRowParams, SupabaseGetRowResponse
77
id: 'supabase_get_row',
88
name: 'Supabase Get Row',
99
description: 'Get a single row from a Supabase table based on filter criteria',
10-
version: '1.0',
10+
version: '1.0.0',
1111

1212
params: {
1313
projectId: {

apps/sim/tools/supabase/index.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,17 @@ import { queryTool } from '@/tools/supabase/query'
88
import { rpcTool } from '@/tools/supabase/rpc'
99
import { storageCopyTool } from '@/tools/supabase/storage_copy'
1010
import { storageCreateBucketTool } from '@/tools/supabase/storage_create_bucket'
11+
import { storageCreateSignedUploadUrlTool } from '@/tools/supabase/storage_create_signed_upload_url'
1112
import { storageCreateSignedUrlTool } from '@/tools/supabase/storage_create_signed_url'
1213
import { storageDeleteTool } from '@/tools/supabase/storage_delete'
1314
import { storageDeleteBucketTool } from '@/tools/supabase/storage_delete_bucket'
1415
import { storageDownloadTool } from '@/tools/supabase/storage_download'
16+
import { storageEmptyBucketTool } from '@/tools/supabase/storage_empty_bucket'
1517
import { storageGetPublicUrlTool } from '@/tools/supabase/storage_get_public_url'
1618
import { storageListTool } from '@/tools/supabase/storage_list'
1719
import { storageListBucketsTool } from '@/tools/supabase/storage_list_buckets'
1820
import { storageMoveTool } from '@/tools/supabase/storage_move'
21+
import { storageUpdateBucketTool } from '@/tools/supabase/storage_update_bucket'
1922
import { storageUploadTool } from '@/tools/supabase/storage_upload'
2023
import { textSearchTool } from '@/tools/supabase/text_search'
2124
import { updateTool } from '@/tools/supabase/update'
@@ -45,3 +48,6 @@ export const supabaseStorageListBucketsTool = storageListBucketsTool
4548
export const supabaseStorageDeleteBucketTool = storageDeleteBucketTool
4649
export const supabaseStorageGetPublicUrlTool = storageGetPublicUrlTool
4750
export const supabaseStorageCreateSignedUrlTool = storageCreateSignedUrlTool
51+
export const supabaseStorageCreateSignedUploadUrlTool = storageCreateSignedUploadUrlTool
52+
export const supabaseStorageUpdateBucketTool = storageUpdateBucketTool
53+
export const supabaseStorageEmptyBucketTool = storageEmptyBucketTool

apps/sim/tools/supabase/insert.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ export const insertTool: ToolConfig<SupabaseInsertParams, SupabaseInsertResponse
77
id: 'supabase_insert',
88
name: 'Supabase Insert',
99
description: 'Insert data into a Supabase table',
10-
version: '1.0',
10+
version: '1.0.0',
1111

1212
params: {
1313
projectId: {
@@ -30,7 +30,7 @@ export const insertTool: ToolConfig<SupabaseInsertParams, SupabaseInsertResponse
3030
'Database schema to insert into (default: public). Use this to access tables in other schemas.',
3131
},
3232
data: {
33-
type: 'array',
33+
type: 'json',
3434
required: true,
3535
visibility: 'user-or-llm',
3636
description: 'The data to insert (array of objects or a single object)',

0 commit comments

Comments
 (0)