Highly ambitious ATProtocol AppView service and sdks
at main 214 lines 5.6 kB view raw
1/// <reference lib="deno.ns" /> 2 3const API_URL = Deno.env.get("API_URL"); 4const SLICE_URI = Deno.env.get("VITE_SLICE_URI"); 5 6interface TokenInfo { 7 accessToken: string; 8 tokenType: string; 9} 10 11/** 12 * Initialize user profile by: 13 * 1. Checking if profile already exists 14 * 2. Syncing user collections (Bluesky data) 15 * 3. Fetching Bluesky profile 16 * 4. Creating network.slices.actor.profile with Bluesky data 17 */ 18export 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"); 25 return; 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 35 const checkQuery = ` 36 query CheckProfile($did: String!) { 37 networkSlicesActorProfiles(where: { did: { eq: $did } }, first: 1) { 38 edges { 39 node { 40 id 41 } 42 } 43 } 44 } 45 `; 46 47 const checkResponse = await fetch(graphqlUrl, { 48 method: "POST", 49 headers: { 50 "Content-Type": "application/json", 51 "Authorization": authHeader, 52 }, 53 body: JSON.stringify({ 54 query: checkQuery, 55 variables: { did: userDid }, 56 }), 57 }); 58 59 if (!checkResponse.ok) { 60 throw new Error(`Profile check failed: ${checkResponse.statusText}`); 61 } 62 63 const checkData = await checkResponse.json(); 64 const hasProfile = 65 (checkData?.data?.networkSlicesActorProfiles?.edges?.length ?? 0) > 0; 66 67 if (hasProfile) { 68 // User already has a profile, nothing to do 69 console.log("Profile already exists for", userDid); 70 return; 71 } 72 73 // 2. Sync user collections (to get Bluesky data) 74 const syncMutation = ` 75 mutation SyncUserCollections($did: String!) { 76 syncUserCollections(did: $did) { 77 success 78 message 79 } 80 } 81 `; 82 83 const syncResponse = await fetch(graphqlUrl, { 84 method: "POST", 85 headers: { 86 "Content-Type": "application/json", 87 "Authorization": authHeader, 88 }, 89 body: JSON.stringify({ 90 query: syncMutation, 91 variables: { did: userDid }, 92 }), 93 }); 94 95 if (!syncResponse.ok) { 96 throw new Error(`Sync collections failed: ${syncResponse.statusText}`); 97 } 98 99 const syncResult = await syncResponse.json(); 100 if (syncResult.errors) { 101 console.error("Sync collections errors:", syncResult.errors); 102 throw new Error("Failed to sync user collections"); 103 } 104 105 // 3. Fetch Bluesky profile data 106 const bskyQuery = ` 107 query GetBskyProfile($did: String!) { 108 appBskyActorProfiles(where: { did: { eq: $did } }, first: 1) { 109 edges { 110 node { 111 displayName 112 description 113 avatar { 114 ref 115 mimeType 116 size 117 } 118 } 119 } 120 } 121 } 122 `; 123 124 const bskyResponse = await fetch(graphqlUrl, { 125 method: "POST", 126 headers: { 127 "Content-Type": "application/json", 128 "Authorization": authHeader, 129 }, 130 body: JSON.stringify({ 131 query: bskyQuery, 132 variables: { did: userDid }, 133 }), 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(); 143 const bskyProfile = bskyData?.data?.appBskyActorProfiles?.edges?.[0]?.node; 144 145 // 4. Create network.slices.actor.profile with Bluesky data 146 const profileInput: { 147 displayName: string; 148 description?: string; 149 avatar?: unknown; 150 createdAt: string; 151 } = { 152 displayName: bskyProfile?.displayName || userHandle || "User", 153 createdAt: new Date().toISOString(), 154 }; 155 156 if (bskyProfile?.description) { 157 profileInput.description = bskyProfile.description; 158 } 159 160 if ( 161 bskyProfile?.avatar?.ref && 162 bskyProfile?.avatar?.mimeType && 163 bskyProfile?.avatar?.size 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 }; 171 } 172 173 const createMutation = ` 174 mutation CreateProfile( 175 $input: NetworkSlicesActorProfileInput! 176 $rkey: String 177 ) { 178 createNetworkSlicesActorProfile(input: $input, rkey: $rkey) { 179 id 180 } 181 } 182 `; 183 184 const createResponse = await fetch(graphqlUrl, { 185 method: "POST", 186 headers: { 187 "Content-Type": "application/json", 188 "Authorization": authHeader, 189 }, 190 body: JSON.stringify({ 191 query: createMutation, 192 variables: { 193 input: profileInput, 194 rkey: "self", 195 }, 196 }), 197 }); 198 199 if (!createResponse.ok) { 200 throw new Error(`Create profile failed: ${createResponse.statusText}`); 201 } 202 203 const createResult = await createResponse.json(); 204 if (createResult.errors) { 205 console.error("Create profile errors:", createResult.errors); 206 throw new Error("Failed to create profile"); 207 } 208 209 console.log("Successfully initialized profile for", userDid); 210 } catch (error) { 211 // Silent failure - user can set up profile manually later 212 console.error("Profile initialization failed:", error); 213 } 214}