Highly ambitious ATProtocol AppView service and sdks

fix graphql blob ref field to return string instead of object

Changed files
+34 -23
api
src
graphql
+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
··· 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 )