Skip to content

Commit 672a267

Browse files
fix(files): address PR review — public CSV OOM, content cache, share FK, soft-delete filter, download anchor
1 parent f8831aa commit 672a267

8 files changed

Lines changed: 28 additions & 13 deletions

File tree

apps/sim/app/api/files/public/[token]/content/route.ts

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,14 @@ export const GET = withRouteHandler(
5050

5151
logger.info('Public shared file served', { token, key: file.key, size: buffer.length })
5252

53-
return createFileResponse({ buffer, contentType, filename: file.originalName })
53+
// Revalidate every request: a shared file can be unshared, edited, or deleted,
54+
// so the fixed token URL must never serve stale bytes from a long-lived cache.
55+
return createFileResponse({
56+
buffer,
57+
contentType,
58+
filename: file.originalName,
59+
cacheControl: 'private, no-cache, must-revalidate',
60+
})
5461
} catch (error) {
5562
logger.error('Error serving public shared file:', error)
5663
if (error instanceof FileNotFoundError) {

apps/sim/app/f/[token]/public-file-view.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,9 @@ export function PublicFileView({
101101
const anchor = document.createElement('a')
102102
anchor.href = contentUrl
103103
anchor.download = name
104+
document.body.appendChild(anchor)
104105
anchor.click()
106+
anchor.remove()
105107
}}
106108
>
107109
Download

apps/sim/app/workspace/[workspaceId]/files/components/file-viewer/file-viewer.tsx

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -108,6 +108,12 @@ export function FileViewer({
108108

109109
if (category === 'text-editable') {
110110
if (readOnly) {
111+
// ReadOnlyTextPreview loads the whole file as text; a large CSV would OOM the
112+
// browser. CsvTablePreview's streamed fallback is workspace-only, so on the
113+
// read-only public path a large CSV is download-only.
114+
if (isCsvStreamOnly(file)) {
115+
return <UnsupportedPreview file={file} />
116+
}
111117
return <ReadOnlyTextPreview file={file} workspaceId={workspaceId} />
112118
}
113119
// A large CSV can't be loaded whole into the editor (the browser OOMs on the full text).

apps/sim/lib/public-shares/share-manager.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@ import { db } from '@sim/db'
22
import { publicShare, user, workspace, workspaceFiles } from '@sim/db/schema'
33
import { createLogger } from '@sim/logger'
44
import { generateId, generateShortId } from '@sim/utils/id'
5-
import { and, eq, inArray } from 'drizzle-orm'
5+
import { and, eq, inArray, isNull } from 'drizzle-orm'
66
import type { z } from 'zod'
77
import type { ShareRecord, shareResourceTypeSchema } from '@/lib/api/contracts/public-shares'
88
import { getBaseUrl } from '@/lib/core/utils/urls'
@@ -136,13 +136,13 @@ export async function resolveActiveShareByToken(token: string): Promise<Resolved
136136
and(
137137
eq(publicShare.token, token),
138138
eq(publicShare.isActive, true),
139-
eq(publicShare.resourceType, 'file')
139+
eq(publicShare.resourceType, 'file'),
140+
isNull(workspaceFiles.deletedAt)
140141
)
141142
)
142143
.limit(1)
143144

144145
if (!row) return null
145-
if (row.file.deletedAt) return null
146146

147147
return {
148148
share: row.share,

packages/db/migrations/0242_public_share.sql

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@ CREATE TABLE "public_share" (
33
"resource_type" text NOT NULL,
44
"resource_id" text NOT NULL,
55
"workspace_id" text NOT NULL,
6-
"created_by" text NOT NULL,
6+
"created_by" text,
77
"token" text NOT NULL,
88
"is_active" boolean DEFAULT true NOT NULL,
99
"created_at" timestamp DEFAULT now() NOT NULL,
1010
"updated_at" timestamp DEFAULT now() NOT NULL
1111
);
1212
--> statement-breakpoint
1313
ALTER TABLE "public_share" ADD CONSTRAINT "public_share_workspace_id_workspace_id_fk" FOREIGN KEY ("workspace_id") REFERENCES "public"."workspace"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
14-
ALTER TABLE "public_share" ADD CONSTRAINT "public_share_created_by_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."user"("id") ON DELETE cascade ON UPDATE no action;--> statement-breakpoint
14+
ALTER TABLE "public_share" ADD CONSTRAINT "public_share_created_by_user_id_fk" FOREIGN KEY ("created_by") REFERENCES "public"."user"("id") ON DELETE set null ON UPDATE no action;--> statement-breakpoint
1515
CREATE UNIQUE INDEX "public_share_token_unique" ON "public_share" USING btree ("token");--> statement-breakpoint
1616
CREATE UNIQUE INDEX "public_share_resource_unique" ON "public_share" USING btree ("resource_type","resource_id");--> statement-breakpoint
1717
CREATE INDEX "public_share_resource_id_idx" ON "public_share" USING btree ("resource_id");--> statement-breakpoint

packages/db/migrations/meta/0242_snapshot.json

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
{
2-
"id": "a99c0c8d-17cb-4153-8888-409e9af9c167",
2+
"id": "78b8f3d4-c24c-4303-89ec-8ae29d2ea5c8",
33
"prevId": "f7a9fd28-8bd2-4421-a048-a0a768fc8475",
44
"version": "7",
55
"dialect": "postgresql",
@@ -9990,7 +9990,7 @@
99909990
"name": "created_by",
99919991
"type": "text",
99929992
"primaryKey": false,
9993-
"notNull": true
9993+
"notNull": false
99949994
},
99959995
"token": {
99969996
"name": "token",
@@ -10104,7 +10104,7 @@
1010410104
"tableTo": "user",
1010510105
"columnsFrom": ["created_by"],
1010610106
"columnsTo": ["id"],
10107-
"onDelete": "cascade",
10107+
"onDelete": "set null",
1010810108
"onUpdate": "no action"
1010910109
}
1011010110
},

packages/db/migrations/meta/_journal.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1692,7 +1692,7 @@
16921692
{
16931693
"idx": 242,
16941694
"version": "7",
1695-
"when": 1781816705434,
1695+
"when": 1781818772450,
16961696
"tag": "0242_public_share",
16971697
"breakpoints": true
16981698
}

packages/db/schema.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1403,9 +1403,9 @@ export const publicShare = pgTable(
14031403
workspaceId: text('workspace_id')
14041404
.notNull()
14051405
.references(() => workspace.id, { onDelete: 'cascade' }),
1406-
createdBy: text('created_by')
1407-
.notNull()
1408-
.references(() => user.id, { onDelete: 'cascade' }),
1406+
// SET NULL (not CASCADE) so a share — and its public link — outlives the user
1407+
// who created it; the file still belongs to the workspace.
1408+
createdBy: text('created_by').references(() => user.id, { onDelete: 'set null' }),
14091409
token: text('token').notNull(),
14101410
isActive: boolean('is_active').notNull().default(true),
14111411
createdAt: timestamp('created_at').notNull().defaultNow(),

0 commit comments

Comments
 (0)