+5
-5
api/src/graphql/schema_builder.rs
+5
-5
api/src/graphql/schema_builder.rs
···
1064
1064
1065
1065
/// Container to hold blob data and DID for URL generation
1066
1066
#[derive(Clone)]
1067
-
struct BlobContainer {
1068
-
blob_ref: String, // CID reference
1069
-
mime_type: String, // MIME type
1070
-
size: i64, // Size in bytes
1071
-
did: String, // DID for CDN URL generation
1067
+
pub struct BlobContainer {
1068
+
pub blob_ref: String, // CID reference
1069
+
pub mime_type: String, // MIME type
1070
+
pub size: i64, // Size in bytes
1071
+
pub did: String, // DID for CDN URL generation
1072
1072
}
1073
1073
1074
1074
/// Creates a GraphQL Object type for a record collection
+29
-18
api/src/graphql/schema_ext/blob_upload.rs
+29
-18
api/src/graphql/schema_ext/blob_upload.rs
···
1
1
//! GraphQL schema extension for blob uploads
2
2
3
3
use async_graphql::dynamic::{Field, FieldFuture, FieldValue, InputValue, Object, TypeRef};
4
-
use async_graphql::{Error, Value as GraphQLValue};
4
+
use async_graphql::Error;
5
5
use base64::engine::general_purpose;
6
6
use base64::Engine;
7
7
8
8
use crate::atproto_extensions::upload_blob as atproto_upload_blob;
9
9
use crate::auth;
10
-
11
-
/// Container for blob upload response
12
-
#[derive(Clone)]
13
-
struct BlobUploadContainer {
14
-
blob: serde_json::Value,
15
-
}
10
+
use crate::graphql::schema_builder::BlobContainer;
16
11
17
12
/// Creates the BlobUploadResponse GraphQL type
18
13
pub fn create_blob_upload_response_type() -> Object {
19
14
let mut response = Object::new("BlobUploadResponse");
20
15
21
-
response = response.field(Field::new("blob", TypeRef::named_nn("JSON"), |ctx| {
16
+
// Return the Blob type instead of JSON to ensure consistent ref field handling
17
+
response = response.field(Field::new("blob", TypeRef::named_nn("Blob"), |ctx| {
22
18
FieldFuture::new(async move {
23
-
let container = ctx.parent_value.try_downcast_ref::<BlobUploadContainer>()?;
24
-
// Convert serde_json::Value to async_graphql::Value
25
-
let graphql_value: GraphQLValue = serde_json::from_value(container.blob.clone())
26
-
.map_err(|e| async_graphql::Error::new(format!("Failed to convert blob to GraphQL value: {}", e)))?;
27
-
Ok(Some(graphql_value))
19
+
// The BlobContainer is passed through from the mutation resolver
20
+
// The Blob type resolver will handle extracting the fields
21
+
let container = ctx.parent_value.try_downcast_ref::<BlobContainer>()?;
22
+
Ok(Some(FieldValue::owned_any(container.clone())))
28
23
})
29
24
}));
30
25
···
70
65
.decode(data_base64)
71
66
.map_err(|e| Error::new(format!("Invalid base64 data: {}", e)))?;
72
67
68
+
// Verify OAuth token to get user info (needed for DID)
69
+
let user_info = auth::verify_oauth_token_cached(
70
+
token,
71
+
&auth_base,
72
+
gql_ctx.auth_cache.clone(),
73
+
)
74
+
.await
75
+
.map_err(|e| Error::new(format!("Invalid token: {}", e)))?;
76
+
73
77
// Get ATProto DPoP auth and PDS URL for this user
74
78
let (dpop_auth, pds_url) = auth::get_atproto_auth_for_user_cached(
75
79
token,
···
91
95
.await
92
96
.map_err(|e| Error::new(format!("Failed to upload blob: {}", e)))?;
93
97
94
-
// Convert blob to JSON value
95
-
let blob_json = serde_json::to_value(&upload_result.blob)
96
-
.map_err(|e| Error::new(format!("Failed to serialize blob: {}", e)))?;
98
+
// Extract the DID from user info
99
+
let did = user_info.did.unwrap_or(user_info.sub);
100
+
101
+
// Create BlobContainer with flattened ref field (CID string)
102
+
// This ensures the GraphQL Blob type returns ref as a String, not an object
103
+
let blob_container = BlobContainer {
104
+
blob_ref: upload_result.blob.r#ref.link.clone(), // Extract CID from ref.$link
105
+
mime_type: upload_result.blob.mime_type.clone(),
106
+
size: upload_result.blob.size as i64,
107
+
did,
108
+
};
97
109
98
-
let container = BlobUploadContainer { blob: blob_json };
99
-
Ok(Some(FieldValue::owned_any(container)))
110
+
Ok(Some(FieldValue::owned_any(blob_container)))
100
111
})
101
112
},
102
113
)