Skip to content

Commit 0712cdf

Browse files
committed
fix(project-files): retire a restored document only when this restore revived the file
1 parent 0fc3141 commit 0712cdf

3 files changed

Lines changed: 12 additions & 9 deletions

File tree

‎apps/sim/lib/projects/files/application/documents.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,9 +28,10 @@ const args = {
2828
}
2929

3030
describe('persistProjectFileDoc conflict retries', () => {
31-
it('reports a document conflict after a revision keeps moving', async () => {
31+
it('retries a moving revision before reporting a document conflict', async () => {
3232
mockReadContent.mockRejectedValue(new ProjectFileRevisionConflictError('File changed'))
3333
await expect(persistProjectFileDoc.execute(args)).resolves.toEqual({ status: 'conflict' })
34+
expect(mockReadContent).toHaveBeenCalledTimes(3)
3435
})
3536

3637
it('surfaces a Project lock conflict instead of reporting a document conflict', async () => {

‎apps/sim/lib/projects/files/application/lifecycle.ts‎

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -166,10 +166,9 @@ export const restoreProjectFile = defineAuthorizedProjectFileUseCase({
166166
operation: projectFileOperations.restore,
167167
invalidatesFileList: ({ context }) => Boolean(context.file?.deletedAt),
168168
async execute({ input, context, tx }: LifecycleArgs<ProjectFileTarget & { fileId: string }>) {
169-
const restored = await restoreFileInTx(tx, context.owner, input.fileId)
170-
const result = { restored: true as const, file: await projectFileRecord(tx, context, restored) }
171-
if (context.file?.deletedAt)
172-
await retireDocuments(tx, context.projectId, [input.fileId], result)
169+
const { file, restored } = await restoreFileInTx(tx, context.owner, input.fileId)
170+
const result = { restored: true as const, file: await projectFileRecord(tx, context, file) }
171+
if (restored) await retireDocuments(tx, context.projectId, [input.fileId], result)
173172
return result
174173
},
175174
projectAudit: ({ context, result }) => ({

‎apps/sim/lib/uploads/contexts/workspace/file-manager.ts‎

Lines changed: 7 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -3048,12 +3048,15 @@ export async function restoreWorkspaceFile(workspaceId: string, fileId: string):
30483048
}
30493049
}
30503050

3051-
/** Restores a retained head, re-rooting and deduplicating within its canonical owner. */
3051+
/**
3052+
* Restores a retained head, re-rooting and deduplicating within its canonical owner. `restored`
3053+
* reports whether this call restored it, read under the file's row lock.
3054+
*/
30523055
export async function restoreFileInTx(
30533056
tx: DbTransaction,
30543057
owner: EditableFileOwner,
30553058
fileId: string
3056-
): Promise<WorkspaceFileRow> {
3059+
): Promise<{ file: WorkspaceFileRow; restored: boolean }> {
30573060
if (owner.entityType === 'workspace') await lockWorkspaceProject(tx, owner.entityId)
30583061
await lockFileDirectories(tx, [owner])
30593062
const [file] = await tx
@@ -3063,7 +3066,7 @@ export async function restoreFileInTx(
30633066
.for('update')
30643067
.limit(1)
30653068
if (!file) throw new OrchestrationError('not_found', 'File not found')
3066-
if (!file.deletedAt) return file
3069+
if (!file.deletedAt) return { file, restored: false }
30673070
let folderId = file.folderId
30683071
if (folderId) {
30693072
const [parent] = await tx
@@ -3119,5 +3122,5 @@ export async function restoreFileInTx(
31193122
throw error
31203123
})
31213124
if (!restored) throw new OrchestrationError('conflict', 'File changed during restoration')
3122-
return restored
3125+
return { file: restored, restored: true }
31233126
}

0 commit comments

Comments
 (0)