Highly ambitious ATProtocol AppView service and sdks

add blob field transformation in mutations from graphql to atproto format

Changed files
+65 -4
api
src
+65 -4
api/src/graphql/schema_builder.rs
··· 2504 2504 let type_name = nsid_to_type_name(nsid); 2505 2505 2506 2506 // Add create mutation 2507 - mutation = add_create_mutation(mutation, &type_name, nsid, database.clone(), slice_uri.clone()); 2507 + mutation = add_create_mutation(mutation, &type_name, nsid, &fields, database.clone(), slice_uri.clone()); 2508 2508 2509 2509 // Add update mutation 2510 - mutation = add_update_mutation(mutation, &type_name, nsid, database.clone(), slice_uri.clone()); 2510 + mutation = add_update_mutation(mutation, &type_name, nsid, &fields, database.clone(), slice_uri.clone()); 2511 2511 2512 2512 // Add delete mutation 2513 2513 mutation = add_delete_mutation(mutation, &type_name, nsid, database.clone(), slice_uri.clone()); ··· 3157 3157 input 3158 3158 } 3159 3159 3160 + /// Transforms blob fields in record data from GraphQL format to AT Protocol format 3161 + /// 3162 + /// GraphQL format: `{ref: "bafyrei...", mimeType: "...", size: 123}` 3163 + /// AT Protocol format: `{ref: {$link: "bafyrei..."}, mimeType: "...", size: 123}` 3164 + fn transform_blob_fields_for_atproto( 3165 + mut data: serde_json::Value, 3166 + fields: &[GraphQLField], 3167 + ) -> serde_json::Value { 3168 + if let serde_json::Value::Object(ref mut map) = data { 3169 + for field in fields { 3170 + if let Some(field_value) = map.get_mut(&field.name) { 3171 + match &field.field_type { 3172 + GraphQLType::Blob => { 3173 + // Transform single blob field 3174 + if let Some(blob_obj) = field_value.as_object_mut() { 3175 + // Check if ref is a string (GraphQL format) 3176 + if let Some(serde_json::Value::String(cid)) = blob_obj.get("ref") { 3177 + // Transform to {$link: "cid"} (AT Protocol format) 3178 + let link_obj = serde_json::json!({ 3179 + "$link": cid 3180 + }); 3181 + blob_obj.insert("ref".to_string(), link_obj); 3182 + } 3183 + } 3184 + } 3185 + GraphQLType::Array(inner) if matches!(inner.as_ref(), GraphQLType::Blob) => { 3186 + // Transform array of blobs 3187 + if let Some(arr) = field_value.as_array_mut() { 3188 + for blob_value in arr { 3189 + if let Some(blob_obj) = blob_value.as_object_mut() { 3190 + if let Some(serde_json::Value::String(cid)) = blob_obj.get("ref") { 3191 + let link_obj = serde_json::json!({ 3192 + "$link": cid 3193 + }); 3194 + blob_obj.insert("ref".to_string(), link_obj); 3195 + } 3196 + } 3197 + } 3198 + } 3199 + } 3200 + _ => {} // Other field types don't need transformation 3201 + } 3202 + } 3203 + } 3204 + } 3205 + 3206 + data 3207 + } 3208 + 3160 3209 /// Adds a create mutation for a collection 3161 3210 fn add_create_mutation( 3162 3211 mutation: Object, 3163 3212 type_name: &str, 3164 3213 nsid: &str, 3214 + fields: &[GraphQLField], 3165 3215 database: Database, 3166 3216 slice_uri: String, 3167 3217 ) -> Object { 3168 3218 let mutation_name = format!("create{}", type_name); 3169 3219 let nsid = nsid.to_string(); 3170 3220 let nsid_clone = nsid.clone(); 3221 + let fields = fields.to_vec(); 3171 3222 3172 3223 mutation.field( 3173 3224 Field::new( ··· 3177 3228 let db = database.clone(); 3178 3229 let slice = slice_uri.clone(); 3179 3230 let collection = nsid.clone(); 3231 + let fields = fields.clone(); 3180 3232 3181 3233 FieldFuture::new(async move { 3182 3234 // Get GraphQL context which contains auth info ··· 3192 3244 .ok_or_else(|| Error::new("Missing input argument"))?; 3193 3245 3194 3246 // Convert GraphQL value to JSON using deserialize 3195 - let record_data: serde_json::Value = input.deserialize() 3247 + let mut record_data: serde_json::Value = input.deserialize() 3196 3248 .map_err(|e| Error::new(format!("Failed to deserialize input: {:?}", e)))?; 3249 + 3250 + // Transform blob fields from GraphQL format (ref: "cid") to AT Protocol format (ref: {$link: "cid"}) 3251 + record_data = transform_blob_fields_for_atproto(record_data, &fields); 3197 3252 3198 3253 // Optional rkey argument 3199 3254 let rkey = ctx.args.get("rkey") ··· 3322 3377 mutation: Object, 3323 3378 type_name: &str, 3324 3379 nsid: &str, 3380 + fields: &[GraphQLField], 3325 3381 database: Database, 3326 3382 slice_uri: String, 3327 3383 ) -> Object { 3328 3384 let mutation_name = format!("update{}", type_name); 3329 3385 let nsid = nsid.to_string(); 3330 3386 let nsid_clone = nsid.clone(); 3387 + let fields = fields.to_vec(); 3331 3388 3332 3389 mutation.field( 3333 3390 Field::new( ··· 3337 3394 let db = database.clone(); 3338 3395 let slice = slice_uri.clone(); 3339 3396 let collection = nsid.clone(); 3397 + let fields = fields.clone(); 3340 3398 3341 3399 FieldFuture::new(async move { 3342 3400 // Get GraphQL context which contains auth info ··· 3358 3416 .ok_or_else(|| Error::new("Missing input argument"))?; 3359 3417 3360 3418 // Convert GraphQL value to JSON using deserialize 3361 - let record_data: serde_json::Value = input.deserialize() 3419 + let mut record_data: serde_json::Value = input.deserialize() 3362 3420 .map_err(|e| Error::new(format!("Failed to deserialize input: {:?}", e)))?; 3421 + 3422 + // Transform blob fields from GraphQL format (ref: "cid") to AT Protocol format (ref: {$link: "cid"}) 3423 + record_data = transform_blob_fields_for_atproto(record_data, &fields); 3363 3424 3364 3425 // Verify OAuth token and get user info 3365 3426 let user_info = crate::auth::verify_oauth_token_cached(