Skip to content

Commit 6f8882e

Browse files
committed
refactor: persist file owners with native foreign keys
1 parent 2613baa commit 6f8882e

55 files changed

Lines changed: 1325 additions & 1856 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

‎apps/sim/app/api/files/authorization.ts‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -129,7 +129,7 @@ export async function verifyFileAccess(
129129
const requireWrite = options?.requireWrite ?? false
130130
try {
131131
const canonical = await getFileMetadataByKey(cloudKey, undefined, { includeDeleted: true })
132-
if (canonical?.entityType === 'project' || canonical?.context === 'project') return false
132+
if (canonical?.projectId != null || canonical?.context === 'project') return false
133133
const keyContext = inferContextFromKey(cloudKey)
134134
/** Organization logos are changed only through the organization-authorized upload lifecycle. */
135135
if (keyContext === 'organization-logos') return !requireWrite

‎apps/sim/app/api/folders/[id]/duplicate/route.test.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@ function folderRow(overrides: Record<string, unknown> = {}) {
6565
return {
6666
id: SOURCE_FOLDER_ID,
6767
resourceType: 'workflow',
68-
entityType: null,
69-
entityId: null,
68+
projectId: null,
7069
name: 'Source',
7170
userId: TEST_USER.id,
7271
workspaceId: WORKSPACE_ID,

‎apps/sim/app/api/folders/[id]/route.test.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -38,8 +38,7 @@ const mockFolder = {
3838
name: 'Test Folder',
3939
userId: TEST_USER.id,
4040
workspaceId: 'workspace-123',
41-
entityType: null,
42-
entityId: null,
41+
projectId: null,
4342
parentId: null,
4443
color: '#6B7280',
4544
sortOrder: 1,

‎apps/sim/app/api/folders/route.test.ts‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -82,8 +82,7 @@ describe('Folders API Route', () => {
8282
name: 'Test Folder 1',
8383
userId: 'user-123',
8484
workspaceId: 'workspace-123',
85-
entityType: null,
86-
entityId: null,
85+
projectId: null,
8786
parentId: null,
8887
color: '#6B7280',
8988
isExpanded: true,
@@ -96,8 +95,7 @@ describe('Folders API Route', () => {
9695
name: 'Test Folder 2',
9796
userId: 'user-123',
9897
workspaceId: 'workspace-123',
99-
entityType: null,
100-
entityId: null,
98+
projectId: null,
10199
parentId: 'folder-1',
102100
color: '#EF4444',
103101
isExpanded: false,

‎apps/sim/app/api/v1/admin/folders/[id]/export/route.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -115,8 +115,7 @@ export const GET = withRouteHandler(
115115
id: folderTable.id,
116116
name: folderTable.name,
117117
workspaceId: folderTable.workspaceId,
118-
entityType: folderTable.entityType,
119-
entityId: folderTable.entityId,
118+
projectId: folderTable.projectId,
120119
})
121120
.from(folderTable)
122121
.where(and(eq(folderTable.id, folderId), eq(folderTable.resourceType, 'workflow')))

‎apps/sim/background/cleanup-soft-deletes.test.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -496,8 +496,7 @@ describe('folder cleanup target', () => {
496496
name: 'Reports',
497497
workspaceId: 'ws-1',
498498
resourceType: 'knowledge_base',
499-
entityType: null,
500-
entityId: null,
499+
projectId: null,
501500
},
502501
])
503502
mockDeduplicateFolderName.mockResolvedValueOnce('Reports (1)')

‎apps/sim/background/cleanup-soft-deletes.ts‎

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -660,8 +660,7 @@ async function reRootActiveFolderChildrenUnguarded(
660660
name: folderTable.name,
661661
workspaceId: folderTable.workspaceId,
662662
resourceType: folderTable.resourceType,
663-
entityType: folderTable.entityType,
664-
entityId: folderTable.entityId,
663+
projectId: folderTable.projectId,
665664
})
666665
.from(folderTable)
667666
.where(and(inArray(folderTable.parentId, expiredIds), isNull(folderTable.deletedAt)))

‎apps/sim/ee/workspace-forking/lib/mapping/resources.test.ts‎

Lines changed: 2 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -44,16 +44,14 @@ describe('listForkResourceCandidates', () => {
4444
name: 'Reports',
4545
parentId: null,
4646
workspaceId: 'ws-1',
47-
entityType: 'workspace',
48-
entityId: 'ws-1',
47+
projectId: null,
4948
},
5049
{
5150
id: 'folder-q3',
5251
name: 'Q3 Results',
5352
parentId: 'folder-reports',
5453
workspaceId: 'ws-1',
55-
entityType: 'workspace',
56-
entityId: 'ws-1',
54+
projectId: null,
5755
},
5856
])
5957

‎apps/sim/lib/billing/storage/payer-transfer.ts‎

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -869,19 +869,15 @@ async function getExactProjectStorageBytes(tx: DbTransaction, projectIds: string
869869
if (!projectIds.length) return bytesById
870870
const totals = await tx
871871
.select({
872-
projectId: workspaceFiles.entityId,
872+
projectId: workspaceFiles.projectId,
873873
bytes: sql<string>`coalesce(sum(${workspaceFiles.sizeBytes}), 0)::text`,
874874
invalidCount: sql<number>`count(*) FILTER (WHERE ${workspaceFiles.sizeBytes} IS NULL OR ${workspaceFiles.sizeBytes} < 0)::integer`,
875875
})
876876
.from(workspaceFiles)
877877
.where(
878-
and(
879-
eq(workspaceFiles.entityType, 'project'),
880-
eq(workspaceFiles.context, 'project'),
881-
inArray(workspaceFiles.entityId, projectIds)
882-
)
878+
and(eq(workspaceFiles.context, 'project'), inArray(workspaceFiles.projectId, projectIds))
883879
)
884-
.groupBy(workspaceFiles.entityId)
880+
.groupBy(workspaceFiles.projectId)
885881
for (const total of totals) {
886882
if (!total.projectId || total.invalidCount > 0)
887883
throw new Error('Project has invalid canonical size metadata')

‎apps/sim/lib/billing/storage/project-accounting.integration.ts‎

Lines changed: 23 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -109,7 +109,7 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
109109
entity_id text NOT NULL, permission_type text NOT NULL);
110110
CREATE TABLE permission_group (id text PRIMARY KEY, organization_id text,
111111
config jsonb, updated_at timestamp);
112-
CREATE TABLE workspace_files (id text PRIMARY KEY, entity_type text, entity_id text,
112+
CREATE TABLE workspace_files (id text PRIMARY KEY, project_id text,
113113
workspace_id text, context text NOT NULL, size_bytes bigint, deleted_at timestamp);
114114
CREATE TABLE workspace_file_version (id text PRIMARY KEY,
115115
file_id text REFERENCES workspace_files(id) ON DELETE CASCADE, size_bytes bigint NOT NULL);
@@ -195,9 +195,9 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
195195
await sql`UPDATE workspace SET organization_id = NULL, billed_account_user_id = 'user-b',
196196
storage_used_bytes = 100 WHERE id = 'workspace-a'`
197197
await sql`UPDATE user_stats SET storage_used_bytes = 50 WHERE user_id = 'user-b'`
198-
await sql`INSERT INTO workspace_files (id, entity_type, entity_id, workspace_id, context, size_bytes)
199-
VALUES ('project-file', 'project', 'project-a', NULL, 'project', 40),
200-
('workspace-file', 'workspace', 'workspace-a', 'workspace-a', 'workspace', 100)`
198+
await sql`INSERT INTO workspace_files (id, project_id, workspace_id, context, size_bytes)
199+
VALUES ('project-file', 'project-a', NULL, 'project', 40),
200+
('workspace-file', NULL, 'workspace-a', 'workspace', 100)`
201201
await database.transaction((tx) =>
202202
changeProjectAndWorkspaceStoragePayersInTx(tx, {
203203
projectChanges: [
@@ -242,9 +242,9 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
242242
storage_used_bytes = 20 WHERE id = 'workspace-a'`
243243
await sql`INSERT INTO project_workspace (project_id, workspace_id) VALUES ('project-a', 'workspace-a')`
244244
await sql`UPDATE user_stats SET storage_used_bytes = CASE user_id WHEN 'user-a' THEN 40 ELSE 20 END`
245-
await sql`INSERT INTO workspace_files (id, entity_type, entity_id, workspace_id, context, size_bytes)
246-
VALUES ('project-file', 'project', 'project-a', NULL, 'project', 40),
247-
('workspace-file', 'workspace', 'workspace-a', 'workspace-a', 'workspace', 20)`
245+
await sql`INSERT INTO workspace_files (id, project_id, workspace_id, context, size_bytes)
246+
VALUES ('project-file', 'project-a', NULL, 'project', 40),
247+
('workspace-file', NULL, 'workspace-a', 'workspace', 20)`
248248
await database.transaction((tx) =>
249249
transferWorkspaceProjects(tx, ['workspace-a'], 'organization-a', undefined, [
250250
{
@@ -283,8 +283,8 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
283283
await sql`INSERT INTO permissions (id, user_id, entity_type, entity_id, permission_type)
284284
VALUES ('admin', 'user-b', 'workspace', 'workspace-a', 'admin')`
285285
await sql`UPDATE user_stats SET storage_used_bytes = 20 WHERE user_id = 'user-a'`
286-
await sql`INSERT INTO workspace_files (id, entity_type, entity_id, workspace_id, context, size_bytes)
287-
VALUES ('workspace-file', 'workspace', 'workspace-a', 'workspace-a', 'workspace', 20)`
286+
await sql`INSERT INTO workspace_files (id, project_id, workspace_id, context, size_bytes)
287+
VALUES ('workspace-file', NULL, 'workspace-a', 'workspace', 20)`
288288
const written = createDeferred<void>()
289289
const releaseWrite = createDeferred<void>()
290290
const teardownPid = createDeferred<number>()
@@ -294,8 +294,8 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
294294
organizationId: null,
295295
billingEntity: { type: 'user', id: 'user-a' },
296296
})
297-
await tx.execute(query`INSERT INTO workspace_files (id, entity_type, entity_id, context, size_bytes)
298-
VALUES ('project-file', 'project', 'project-a', 'project', 40)`)
297+
await tx.execute(query`INSERT INTO workspace_files (id, project_id, context, size_bytes)
298+
VALUES ('project-file', 'project-a', 'project', 40)`)
299299
await prepared.applyDelta(40)
300300
written.resolve()
301301
await releaseWrite.promise
@@ -341,8 +341,8 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
341341
...['project-a', 'project-b'].map((projectId) =>
342342
database.transaction(async (tx) => {
343343
const prepared = await prepareProjectStorageMutationInTx(tx, context(projectId, 60))
344-
await tx.execute(query`INSERT INTO workspace_files (id, entity_type, entity_id, context, size_bytes)
345-
VALUES (${projectId}, 'project', ${projectId}, 'project', 40)`)
344+
await tx.execute(query`INSERT INTO workspace_files (id, project_id, context, size_bytes)
345+
VALUES (${projectId}, ${projectId}, 'project', 40)`)
346346
await prepared.applyDelta(40)
347347
})
348348
),
@@ -404,7 +404,7 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
404404
await database.transaction(async (tx) => {
405405
const prepared = await prepareProjectStorageMutationInTx(tx, context())
406406
await tx.execute(
407-
query`INSERT INTO workspace_files VALUES ('file', 'project', 'project-a', NULL, 'project', 100, NULL)`
407+
query`INSERT INTO workspace_files VALUES ('file', 'project-a', NULL, 'project', 100, NULL)`
408408
)
409409
await tx.execute(query`INSERT INTO workspace_file_version VALUES ('version', 'file', 500)`)
410410
await prepared.applyDelta(100)
@@ -442,9 +442,9 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
442442
'moves exact Project heads once across payers, including archived heads but excluding versions',
443443
async () => {
444444
await sql`INSERT INTO workspace_files VALUES
445-
('active', 'project', 'project-a', NULL, 'project', 40, NULL),
446-
('archived', 'project', 'project-a', NULL, 'project', 60, now()),
447-
('other', 'project', 'project-b', NULL, 'project', 25, NULL)`
445+
('active', 'project-a', NULL, 'project', 40, NULL),
446+
('archived', 'project-a', NULL, 'project', 60, now()),
447+
('other', 'project-b', NULL, 'project', 25, NULL)`
448448
await sql`INSERT INTO workspace_file_version VALUES ('version', 'active', 900)`
449449
await sql`UPDATE organization SET storage_used_bytes = 125 WHERE id = 'organization-a'`
450450
await database.transaction((tx) =>
@@ -485,10 +485,10 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
485485
'reconciliation adds Project heads once and refuses incomplete canonical size metadata',
486486
async () => {
487487
await sql`INSERT INTO workspace_files VALUES
488-
('workspace-file', 'workspace', 'workspace-a', 'workspace-a', 'workspace', 20, NULL),
489-
('project-file', 'project', 'project-a', NULL, 'project', 40, NULL),
490-
('archived', 'project', 'project-a', NULL, 'project', 60, now()),
491-
('chat', NULL, NULL, 'workspace-a', 'mothership', 999, NULL)`
488+
('workspace-file', NULL, 'workspace-a', 'workspace', 20, NULL),
489+
('project-file', 'project-a', NULL, 'project', 40, NULL),
490+
('archived', 'project-a', NULL, 'project', 60, now()),
491+
('chat', NULL, 'workspace-a', 'mothership', 999, NULL)`
492492
await sql`INSERT INTO workspace_file_version VALUES ('version', 'project-file', 900)`
493493
const store = createPostgresStorageReconciliationStore(sql)
494494
await store.reconcileWorkspaces(['workspace-a'])
@@ -512,7 +512,7 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
512512
const write = database.transaction(async (tx) => {
513513
const prepared = await prepareProjectStorageMutationInTx(tx, context())
514514
await tx.execute(
515-
query`INSERT INTO workspace_files VALUES ('file', 'project', 'project-a', NULL, 'project', 40, NULL)`
515+
query`INSERT INTO workspace_files VALUES ('file', 'project-a', NULL, 'project', 40, NULL)`
516516
)
517517
await prepared.applyDelta(40)
518518
changed.resolve()
@@ -549,7 +549,7 @@ describe('Project storage admission, transfer, and reconciliation in PostgreSQL'
549549
const write = database.transaction(async (tx) => {
550550
const prepared = await prepareProjectStorageMutationInTx(tx, context())
551551
await tx.execute(
552-
query`INSERT INTO workspace_files VALUES ('file', 'project', 'project-a', NULL, 'project', 40, NULL)`
552+
query`INSERT INTO workspace_files VALUES ('file', 'project-a', NULL, 'project', 40, NULL)`
553553
)
554554
await prepared.applyDelta(40)
555555
changed.resolve()

0 commit comments

Comments
 (0)