* Select every soft-deleted file row that's eligible for permanent removal. * Returned once and reused for both S3 deletion and DB deletion so the external * cleanup cannot drift from the row-level cleanup.
( workspaceIds: string[], retentionDate: Date )
| 52 | * cleanup cannot drift from the row-level cleanup. |
| 53 | */ |
| 54 | async function selectExpiredWorkspaceFiles( |
| 55 | workspaceIds: string[], |
| 56 | retentionDate: Date |
| 57 | ): Promise<WorkspaceFileScope> { |
| 58 | const [legacyRows, multiContextRows] = await Promise.all([ |
| 59 | selectRowsByIdChunks(workspaceIds, (chunkIds, chunkLimit) => |
| 60 | db |
| 61 | .select({ id: workspaceFile.id, key: workspaceFile.key }) |
| 62 | .from(workspaceFile) |
| 63 | .where( |
| 64 | and( |
| 65 | inArray(workspaceFile.workspaceId, chunkIds), |
| 66 | isNotNull(workspaceFile.deletedAt), |
| 67 | lt(workspaceFile.deletedAt, retentionDate) |
| 68 | ) |
| 69 | ) |
| 70 | .limit(chunkLimit) |
| 71 | ), |
| 72 | selectRowsByIdChunks(workspaceIds, (chunkIds, chunkLimit) => |
| 73 | db |
| 74 | .select({ |
| 75 | id: workspaceFiles.id, |
| 76 | key: workspaceFiles.key, |
| 77 | context: workspaceFiles.context, |
| 78 | }) |
| 79 | .from(workspaceFiles) |
| 80 | .where( |
| 81 | and( |
| 82 | inArray(workspaceFiles.workspaceId, chunkIds), |
| 83 | isNotNull(workspaceFiles.deletedAt), |
| 84 | lt(workspaceFiles.deletedAt, retentionDate) |
| 85 | ) |
| 86 | ) |
| 87 | .limit(chunkLimit) |
| 88 | ), |
| 89 | ]) |
| 90 | |
| 91 | return { |
| 92 | legacyRows, |
| 93 | multiContextRows: multiContextRows.map((r) => ({ |
| 94 | id: r.id, |
| 95 | key: r.key, |
| 96 | context: r.context as StorageContext, |
| 97 | })), |
| 98 | } |
| 99 | } |
| 100 | |
| 101 | async function cleanupWorkspaceFileStorage( |
| 102 | scope: WorkspaceFileScope |
no test coverage detected