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 1065 /// Container to hold blob data and DID for URL generation 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 1072 } 1073 1074 /// Creates a GraphQL Object type for a record collection
··· 1064 1065 /// Container to hold blob data and DID for URL generation 1066 #[derive(Clone)] 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 } 1073 1074 /// Creates a GraphQL Object type for a record collection
+29 -18
api/src/graphql/schema_ext/blob_upload.rs
··· 1 //! GraphQL schema extension for blob uploads 2 3 use async_graphql::dynamic::{Field, FieldFuture, FieldValue, InputValue, Object, TypeRef}; 4 - use async_graphql::{Error, Value as GraphQLValue}; 5 use base64::engine::general_purpose; 6 use base64::Engine; 7 8 use crate::atproto_extensions::upload_blob as atproto_upload_blob; 9 use crate::auth; 10 - 11 - /// Container for blob upload response 12 - #[derive(Clone)] 13 - struct BlobUploadContainer { 14 - blob: serde_json::Value, 15 - } 16 17 /// Creates the BlobUploadResponse GraphQL type 18 pub fn create_blob_upload_response_type() -> Object { 19 let mut response = Object::new("BlobUploadResponse"); 20 21 - response = response.field(Field::new("blob", TypeRef::named_nn("JSON"), |ctx| { 22 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)) 28 }) 29 })); 30 ··· 70 .decode(data_base64) 71 .map_err(|e| Error::new(format!("Invalid base64 data: {}", e)))?; 72 73 // Get ATProto DPoP auth and PDS URL for this user 74 let (dpop_auth, pds_url) = auth::get_atproto_auth_for_user_cached( 75 token, ··· 91 .await 92 .map_err(|e| Error::new(format!("Failed to upload blob: {}", e)))?; 93 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)))?; 97 98 - let container = BlobUploadContainer { blob: blob_json }; 99 - Ok(Some(FieldValue::owned_any(container))) 100 }) 101 }, 102 )
··· 1 //! GraphQL schema extension for blob uploads 2 3 use async_graphql::dynamic::{Field, FieldFuture, FieldValue, InputValue, Object, TypeRef}; 4 + use async_graphql::Error; 5 use base64::engine::general_purpose; 6 use base64::Engine; 7 8 use crate::atproto_extensions::upload_blob as atproto_upload_blob; 9 use crate::auth; 10 + use crate::graphql::schema_builder::BlobContainer; 11 12 /// Creates the BlobUploadResponse GraphQL type 13 pub fn create_blob_upload_response_type() -> Object { 14 let mut response = Object::new("BlobUploadResponse"); 15 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| { 18 FieldFuture::new(async move { 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()))) 23 }) 24 })); 25 ··· 65 .decode(data_base64) 66 .map_err(|e| Error::new(format!("Invalid base64 data: {}", e)))?; 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 + 77 // Get ATProto DPoP auth and PDS URL for this user 78 let (dpop_auth, pds_url) = auth::get_atproto_auth_for_user_cached( 79 token, ··· 95 .await 96 .map_err(|e| Error::new(format!("Failed to upload blob: {}", e)))?; 97 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 + }; 109 110 + Ok(Some(FieldValue::owned_any(blob_container))) 111 }) 112 }, 113 )