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
+40
View File
@@ -96,6 +96,41 @@ type BlobNotFoundDataType {
spaceId: String!
}
type BlobUploadInit {
alreadyUploaded: Boolean
blobKey: String!
expiresAt: DateTime
headers: JSONObject
method: BlobUploadMethod!
partSize: Int
uploadId: String
uploadUrl: String
uploadedParts: [BlobUploadedPart!]
}
"""Blob upload method"""
enum BlobUploadMethod {
GRAPHQL
MULTIPART
PRESIGNED
}
type BlobUploadPart {
expiresAt: DateTime
headers: JSONObject
uploadUrl: String!
}
input BlobUploadPartInput {
etag: String!
partNumber: Int!
}
type BlobUploadedPart {
etag: String!
partNumber: Int!
}
enum ChatHistoryOrder {
asc
desc
@@ -659,6 +694,7 @@ enum ErrorNames {
ALREADY_IN_SPACE
AUTHENTICATION_REQUIRED
BAD_REQUEST
BLOB_INVALID
BLOB_NOT_FOUND
BLOB_QUOTA_EXCEEDED
CANNOT_DELETE_ACCOUNT_WITH_OWNED_TEAM_WORKSPACE
@@ -1194,6 +1230,7 @@ type MissingOauthQueryParameterDataType {
}
type Mutation {
abortBlobUpload(key: String!, uploadId: String!, workspaceId: String!): Boolean!
acceptInviteById(inviteId: String!, sendAcceptMail: Boolean @deprecated(reason: "never used"), workspaceId: String @deprecated(reason: "never used")): Boolean!
activateLicense(license: String!, workspaceId: String!): License!
@@ -1223,6 +1260,8 @@ type Mutation {
"""Cleanup sessions"""
cleanupCopilotSession(options: DeleteSessionInput!): [String!]!
completeBlobUpload(key: String!, parts: [BlobUploadPartInput!], uploadId: String, workspaceId: String!): String!
createBlobUpload(key: String!, mime: String!, size: Int!, workspaceId: String!): BlobUploadInit!
"""Create change password url"""
createChangePasswordUrl(callbackUrl: String!, userId: String!): String!
@@ -1275,6 +1314,7 @@ type Mutation {
forkCopilotSession(options: ForkChatSessionInput!): String!
generateLicenseKey(sessionId: String!): String!
generateUserAccessToken(input: GenerateAccessTokenInput!): RevealedAccessToken!
getBlobUploadPartUrl(key: String!, partNumber: Int!, uploadId: String!, workspaceId: String!): BlobUploadPart!
grantDocUserRoles(input: GrantDocUserRolesInput!): Boolean!
grantMember(permission: Permission!, userId: String!, workspaceId: String!): Boolean!