feat: multipart blob sync support (#14138)

<!-- This is an auto-generated comment: release notes by coderabbit.ai
-->
## Summary by CodeRabbit

* **New Features**
* Flexible blob uploads: GRAPHQL, presigned, and multipart flows with
per‑part URLs, abort/complete operations, presigned proxy endpoints, and
nightly cleanup of expired pending uploads.

* **API / Schema**
* GraphQL additions: new types, mutations, enum and error to manage
upload lifecycle (create, complete, abort, get part URL).

* **Database**
* New blob status enum and columns (status, upload_id); listing now
defaults to completed blobs.

* **Localization**
  * Added user-facing message: "Blob is invalid."

* **Tests**
* Expanded unit and end‑to‑end coverage for upload flows, proxy
behavior, multipart and provider integrations.

<sub>✏️ Tip: You can customize this high-level summary in your review
settings.</sub>
<!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
DarkSky
2025-12-23 22:09:21 +08:00
committed by GitHub
parent a9937e18b6
commit 76524084d1
36 changed files with 2880 additions and 33 deletions
@@ -21,12 +21,16 @@ export class BlobModel extends BaseModel {
update: {
mime: blob.mime,
size: blob.size,
status: blob.status,
uploadId: blob.uploadId,
},
create: {
workspaceId: blob.workspaceId,
key: blob.key,
mime: blob.mime,
size: blob.size,
status: blob.status,
uploadId: blob.uploadId,
},
});
}
@@ -76,11 +80,39 @@ export class BlobModel extends BaseModel {
...options?.where,
workspaceId,
deletedAt: null,
status: 'completed',
},
select: options?.select,
});
}
async hasAny(workspaceId: string) {
const count = await this.db.blob.count({
where: {
workspaceId,
deletedAt: null,
},
});
return count > 0;
}
async listPendingExpired(before: Date) {
return await this.db.blob.findMany({
where: {
status: 'pending',
deletedAt: null,
createdAt: {
lt: before,
},
},
select: {
workspaceId: true,
key: true,
uploadId: true,
},
});
}
async listDeleted(workspaceId: string) {
return await this.db.blob.findMany({
where: {