Highly ambitious ATProtocol AppView service and sdks

update frontend-v2 graphql schema

Changed files
+62 -27
frontend-v2
+1 -1
frontend-v2/schema.graphql
··· 931 } 932 933 type BlobUploadResponse { 934 - blob: JSON! 935 } 936 937 type CollectionStats {
··· 931 } 932 933 type BlobUploadResponse { 934 + blob: Blob! 935 } 936 937 type CollectionStats {
+8 -7
frontend-v2/server/profile-init.ts
··· 18 export async function initializeUserProfile( 19 userDid: string, 20 userHandle: string, 21 - tokens: TokenInfo 22 ): Promise<void> { 23 if (!API_URL || !SLICE_URI) { 24 console.error("Missing API_URL or VITE_SLICE_URI environment variables"); ··· 26 } 27 28 try { 29 - const graphqlUrl = `${API_URL}/graphql?slice=${encodeURIComponent(SLICE_URI)}`; 30 const authHeader = `${tokens.tokenType} ${tokens.accessToken}`; 31 32 // 1. Check if profile already exists ··· 132 }); 133 134 if (!bskyResponse.ok) { 135 - throw new Error(`Fetch Bluesky profile failed: ${bskyResponse.statusText}`); 136 } 137 138 const bskyData = await bskyResponse.json(); ··· 160 ) { 161 // Reconstruct blob format for AT Protocol 162 profileInput.avatar = { 163 - $type: "blob", 164 - ref: { 165 - $link: bskyProfile.avatar.ref, 166 - }, 167 mimeType: bskyProfile.avatar.mimeType, 168 size: bskyProfile.avatar.size, 169 };
··· 18 export async function initializeUserProfile( 19 userDid: string, 20 userHandle: string, 21 + tokens: TokenInfo, 22 ): Promise<void> { 23 if (!API_URL || !SLICE_URI) { 24 console.error("Missing API_URL or VITE_SLICE_URI environment variables"); ··· 26 } 27 28 try { 29 + const graphqlUrl = `${API_URL}/graphql?slice=${ 30 + encodeURIComponent(SLICE_URI) 31 + }`; 32 const authHeader = `${tokens.tokenType} ${tokens.accessToken}`; 33 34 // 1. Check if profile already exists ··· 134 }); 135 136 if (!bskyResponse.ok) { 137 + throw new Error( 138 + `Fetch Bluesky profile failed: ${bskyResponse.statusText}`, 139 + ); 140 } 141 142 const bskyData = await bskyResponse.json(); ··· 164 ) { 165 // Reconstruct blob format for AT Protocol 166 profileInput.avatar = { 167 + ref: bskyProfile.avatar.ref, 168 mimeType: bskyProfile.avatar.mimeType, 169 size: bskyProfile.avatar.size, 170 };
+35 -6
frontend-v2/src/__generated__/ProfileSettingsUploadBlobMutation.graphql.ts
··· 1 /** 2 - * @generated SignedSource<<a2334c7e93bb6d5b4748df1211a418ae>> 3 * @lightSyntaxTransform 4 * @nogrep 5 */ ··· 15 }; 16 export type ProfileSettingsUploadBlobMutation$data = { 17 readonly uploadBlob: { 18 - readonly blob: any; 19 }; 20 }; 21 export type ProfileSettingsUploadBlobMutation = { ··· 59 { 60 "alias": null, 61 "args": null, 62 - "kind": "ScalarField", 63 "name": "blob", 64 "storageKey": null 65 } 66 ], ··· 85 "selections": (v1/*: any*/) 86 }, 87 "params": { 88 - "cacheID": "3a4a6b19d2898f14635b098941614cab", 89 "id": null, 90 "metadata": {}, 91 "name": "ProfileSettingsUploadBlobMutation", 92 "operationKind": "mutation", 93 - "text": "mutation ProfileSettingsUploadBlobMutation(\n $data: String!\n $mimeType: String!\n) {\n uploadBlob(data: $data, mimeType: $mimeType) {\n blob\n }\n}\n" 94 } 95 }; 96 })(); 97 98 - (node as any).hash = "76da65b07a282ed7f2dee12b4cac82d6"; 99 100 export default node;
··· 1 /** 2 + * @generated SignedSource<<728b9a3525f975b6c58a5cdcd323f89e>> 3 * @lightSyntaxTransform 4 * @nogrep 5 */ ··· 15 }; 16 export type ProfileSettingsUploadBlobMutation$data = { 17 readonly uploadBlob: { 18 + readonly blob: { 19 + readonly mimeType: string; 20 + readonly ref: string; 21 + readonly size: number; 22 + }; 23 }; 24 }; 25 export type ProfileSettingsUploadBlobMutation = { ··· 63 { 64 "alias": null, 65 "args": null, 66 + "concreteType": "Blob", 67 + "kind": "LinkedField", 68 "name": "blob", 69 + "plural": false, 70 + "selections": [ 71 + { 72 + "alias": null, 73 + "args": null, 74 + "kind": "ScalarField", 75 + "name": "ref", 76 + "storageKey": null 77 + }, 78 + { 79 + "alias": null, 80 + "args": null, 81 + "kind": "ScalarField", 82 + "name": "mimeType", 83 + "storageKey": null 84 + }, 85 + { 86 + "alias": null, 87 + "args": null, 88 + "kind": "ScalarField", 89 + "name": "size", 90 + "storageKey": null 91 + } 92 + ], 93 "storageKey": null 94 } 95 ], ··· 114 "selections": (v1/*: any*/) 115 }, 116 "params": { 117 + "cacheID": "afd8db2ee7590308e81afc0b0e5c86dd", 118 "id": null, 119 "metadata": {}, 120 "name": "ProfileSettingsUploadBlobMutation", 121 "operationKind": "mutation", 122 + "text": "mutation ProfileSettingsUploadBlobMutation(\n $data: String!\n $mimeType: String!\n) {\n uploadBlob(data: $data, mimeType: $mimeType) {\n blob {\n ref\n mimeType\n size\n }\n }\n}\n" 123 } 124 }; 125 })(); 126 127 + (node as any).hash = "74a3a8bf43181cd62d2e81c45be384e5"; 128 129 export default node;
+18 -13
frontend-v2/src/pages/ProfileSettings.tsx
··· 1 - import { useParams, Link } from "react-router-dom"; 2 import { useState } from "react"; 3 import { graphql, useLazyLoadQuery, useMutation } from "react-relay"; 4 import type { ProfileSettingsQuery } from "../__generated__/ProfileSettingsQuery.graphql.ts"; ··· 44 where: { 45 actorHandle: { eq: handle }, 46 }, 47 - } 48 ); 49 50 const profile = data.networkSlicesActorProfiles.edges[0]?.node; ··· 59 graphql` 60 mutation ProfileSettingsUploadBlobMutation($data: String!, $mimeType: String!) { 61 uploadBlob(data: $data, mimeType: $mimeType) { 62 - blob 63 } 64 } 65 - ` 66 ); 67 68 const [commitUpdateProfile, isUpdatingProfile] = useMutation( ··· 80 } 81 } 82 } 83 - ` 84 ); 85 86 const [commitCreateProfile, isCreatingProfile] = useMutation( ··· 98 } 99 } 100 } 101 - ` 102 ); 103 104 // Helper to convert File to base64 ··· 108 reader.onload = () => { 109 const arrayBuffer = reader.result as ArrayBuffer; 110 const bytes = new Uint8Array(arrayBuffer); 111 - const binary = Array.from(bytes).map(b => String.fromCharCode(b)).join(''); 112 resolve(btoa(binary)); 113 }; 114 reader.onerror = reject; ··· 129 // Upload new avatar 130 const base64Data = await fileToBase64(avatarFile); 131 132 - const uploadResult = await new Promise<{ uploadBlob: { blob: unknown } }>((resolve, reject) => { 133 commitUploadBlob({ 134 variables: { 135 data: base64Data, 136 mimeType: avatarFile.type, 137 }, 138 - onCompleted: (data) => resolve(data as { uploadBlob: { blob: unknown } }), 139 onError: (error) => reject(error), 140 }); 141 }); ··· 144 } else if (profile?.avatar) { 145 // Keep existing avatar - reconstruct blob with $type field for AT Protocol 146 avatarBlob = { 147 - $type: "blob", 148 - ref: { 149 - $link: profile.avatar.ref, 150 - }, 151 mimeType: profile.avatar.mimeType, 152 size: profile.avatar.size, 153 };
··· 1 + import { Link, useParams } from "react-router-dom"; 2 import { useState } from "react"; 3 import { graphql, useLazyLoadQuery, useMutation } from "react-relay"; 4 import type { ProfileSettingsQuery } from "../__generated__/ProfileSettingsQuery.graphql.ts"; ··· 44 where: { 45 actorHandle: { eq: handle }, 46 }, 47 + }, 48 ); 49 50 const profile = data.networkSlicesActorProfiles.edges[0]?.node; ··· 59 graphql` 60 mutation ProfileSettingsUploadBlobMutation($data: String!, $mimeType: String!) { 61 uploadBlob(data: $data, mimeType: $mimeType) { 62 + blob { 63 + ref 64 + mimeType 65 + size 66 + } 67 } 68 } 69 + `, 70 ); 71 72 const [commitUpdateProfile, isUpdatingProfile] = useMutation( ··· 84 } 85 } 86 } 87 + `, 88 ); 89 90 const [commitCreateProfile, isCreatingProfile] = useMutation( ··· 102 } 103 } 104 } 105 + `, 106 ); 107 108 // Helper to convert File to base64 ··· 112 reader.onload = () => { 113 const arrayBuffer = reader.result as ArrayBuffer; 114 const bytes = new Uint8Array(arrayBuffer); 115 + const binary = Array.from(bytes).map((b) => String.fromCharCode(b)) 116 + .join(""); 117 resolve(btoa(binary)); 118 }; 119 reader.onerror = reject; ··· 134 // Upload new avatar 135 const base64Data = await fileToBase64(avatarFile); 136 137 + const uploadResult = await new Promise< 138 + { uploadBlob: { blob: unknown } } 139 + >((resolve, reject) => { 140 commitUploadBlob({ 141 variables: { 142 data: base64Data, 143 mimeType: avatarFile.type, 144 }, 145 + onCompleted: (data) => 146 + resolve(data as { uploadBlob: { blob: unknown } }), 147 onError: (error) => reject(error), 148 }); 149 }); ··· 152 } else if (profile?.avatar) { 153 // Keep existing avatar - reconstruct blob with $type field for AT Protocol 154 avatarBlob = { 155 + ref: profile.avatar.ref, 156 mimeType: profile.avatar.mimeType, 157 size: profile.avatar.size, 158 };