Gleam Lustre Fullstack Atproto Demo App w/Slices.Network GraphQL API

format

+397 -221
+6 -5
client/src/cache.gleam
··· 99 99 pub fn set_profile(cache: Cache, profile: Profile) -> Cache { 100 100 case profile.handle { 101 101 Some(handle) -> { 102 - let updated_profiles = dict.insert(cache.profile_entities, handle, profile) 102 + let updated_profiles = 103 + dict.insert(cache.profile_entities, handle, profile) 103 104 Cache(..cache, profile_entities: updated_profiles) 104 105 } 105 106 None -> cache ··· 224 225 let updated_cache = set_profile(cache, optimistic_profile) 225 226 226 227 // Track the update for potential rollback 227 - Cache( 228 - ..updated_cache, 229 - optimistic_updates: [update, ..cache.optimistic_updates], 230 - ) 228 + Cache(..updated_cache, optimistic_updates: [ 229 + update, 230 + ..cache.optimistic_updates 231 + ]) 231 232 } 232 233 233 234 /// Commit an optimistic update (remove from rollback stack)
+23 -17
client/src/client/effects.gleam
··· 87 87 } 88 88 Error(err) -> { 89 89 io.println("JSON parse error: " <> string.inspect(err)) 90 - dispatch(model.ProfileQueryError(handle, "Failed to parse profile JSON")) 90 + dispatch(model.ProfileQueryError( 91 + handle, 92 + "Failed to parse profile JSON", 93 + )) 91 94 } 92 95 } 93 96 } ··· 132 135 } 133 136 Error(err) -> { 134 137 io.println("JSON parse error: " <> string.inspect(err)) 135 - dispatch(model.AttendeesQueryError("Failed to parse attendees JSON")) 138 + dispatch(model.AttendeesQueryError( 139 + "Failed to parse attendees JSON", 140 + )) 136 141 } 137 142 } 138 143 } ··· 161 166 case result { 162 167 Ok(file_data) -> { 163 168 io.println("File processed successfully") 164 - dispatch(model.ProfileEditMsg(profile_edit.AvatarFileProcessed(file_data))) 169 + dispatch( 170 + model.ProfileEditMsg(profile_edit.AvatarFileProcessed(file_data)), 171 + ) 165 172 } 166 173 Error(err) -> { 167 174 io.println("Failed to process file: " <> err) ··· 251 258 Ok(updated_profile) -> { 252 259 io.println("Profile parsed successfully") 253 260 dispatch( 254 - model.ProfileEditMsg(profile_edit.SaveCompleted(Ok(updated_profile))), 261 + model.ProfileEditMsg( 262 + profile_edit.SaveCompleted(Ok(updated_profile)), 263 + ), 255 264 ) 256 265 } 257 266 Error(_) -> { 258 267 io.println("Failed to parse profile response") 259 268 dispatch( 260 269 model.ProfileEditMsg( 261 - profile_edit.SaveCompleted( 262 - Error("Failed to parse updated profile"), 263 - ), 270 + profile_edit.SaveCompleted(Error( 271 + "Failed to parse updated profile", 272 + )), 264 273 ), 265 274 ) 266 275 } ··· 268 277 } 269 278 Ok(#(status, text)) -> { 270 279 io.println( 271 - "Save failed with status " 272 - <> int.to_string(status) 273 - <> ": " 274 - <> text, 280 + "Save failed with status " <> int.to_string(status) <> ": " <> text, 275 281 ) 276 282 dispatch( 277 283 model.ProfileEditMsg( 278 - profile_edit.SaveCompleted( 279 - Error("Failed to save profile (status " <> int.to_string(status) <> ")"), 280 - ), 284 + profile_edit.SaveCompleted(Error( 285 + "Failed to save profile (status " 286 + <> int.to_string(status) 287 + <> ")", 288 + )), 281 289 ), 282 290 ) 283 291 } 284 292 Error(err) -> { 285 293 io.println("Save request failed: " <> err) 286 - dispatch( 287 - model.ProfileEditMsg(profile_edit.SaveCompleted(Error(err))), 288 - ) 294 + dispatch(model.ProfileEditMsg(profile_edit.SaveCompleted(Error(err)))) 289 295 } 290 296 } 291 297 })
+15 -6
client/src/client/init.gleam
··· 39 39 Some(profile_data), _ -> { 40 40 // Seed cache with profile and mark as fresh 41 41 let stale_time = query.stale_time(query.ProfileQuery("")) 42 - cache.hydrate_profile(initial_cache, profile_data, current_time_ms, stale_time) 42 + cache.hydrate_profile( 43 + initial_cache, 44 + profile_data, 45 + current_time_ms, 46 + stale_time, 47 + ) 43 48 } 44 49 None, Some(attendees_data) -> { 45 50 // Seed cache with attendees and mark all profiles as fresh ··· 111 116 112 117 // Check if we need to fetch (cache might be stale or missing) 113 118 let query_key = query.to_string(query.ProfileQuery(handle)) 114 - let needs_fetch = cache.is_query_stale(hydrated_cache, query_key, current_time_ms) 119 + let needs_fetch = 120 + cache.is_query_stale(hydrated_cache, query_key, current_time_ms) 115 121 116 122 let fetch_effect = case needs_fetch { 117 123 True -> effects.fetch_profile_with_cache(handle) ··· 163 169 document.query_selector("#model") 164 170 |> result.map(plinth_element.inner_text) 165 171 |> result.try(fn(json_string) { 166 - json.parse(json_string, decode.at(["user"], { 167 - use handle <- decode.field("handle", decode.string) 168 - decode.success(layout.User(name: None, handle: handle)) 169 - })) 172 + json.parse( 173 + json_string, 174 + decode.at(["user"], { 175 + use handle <- decode.field("handle", decode.string) 176 + decode.success(layout.User(name: None, handle: handle)) 177 + }), 178 + ) 170 179 |> result.replace_error(Nil) 171 180 }) 172 181 |> option.from_result
+6 -3
client/src/client/view.gleam
··· 22 22 model.Attendees -> { 23 23 // Read from cache 24 24 let query_key_str = query.to_string(query.AttendeesQuery) 25 - let query_status = cache.get_query_status(current_model.cache, query_key_str) 25 + let query_status = 26 + cache.get_query_status(current_model.cache, query_key_str) 26 27 27 28 case query_status { 28 29 cache.Idle | cache.Fetching -> attendees.view_loading() ··· 36 37 model.Profile(handle: handle) -> { 37 38 // Read profile from cache 38 39 let query_key_str = query.to_string(query.ProfileQuery(handle)) 39 - let query_status = cache.get_query_status(current_model.cache, query_key_str) 40 + let query_status = 41 + cache.get_query_status(current_model.cache, query_key_str) 40 42 let profile_from_cache = cache.get_profile(current_model.cache, handle) 41 43 42 44 case profile_from_cache, query_status { ··· 73 75 // Read profile from cache 74 76 let profile_from_cache = cache.get_profile(current_model.cache, handle) 75 77 let query_key_str = query.to_string(query.ProfileQuery(handle)) 76 - let query_status = cache.get_query_status(current_model.cache, query_key_str) 78 + let query_status = 79 + cache.get_query_status(current_model.cache, query_key_str) 77 80 78 81 case profile_from_cache, query_status { 79 82 option.Some(p), _ ->
+9 -4
client/src/pages/attendees.gleam
··· 43 43 // Profile info 44 44 html.div([attribute.class("flex-1 min-w-0")], [ 45 45 // Display name 46 - html.h3([attribute.class("text-lg font-semibold text-white truncate")], [ 47 - html.text(display_name), 48 - ]), 46 + html.h3( 47 + [attribute.class("text-lg font-semibold text-white truncate")], 48 + [ 49 + html.text(display_name), 50 + ], 51 + ), 49 52 // Handle 50 53 case p.handle { 51 54 option.Some(h) -> ··· 78 81 79 82 pub fn view_loading() -> Element(msg) { 80 83 html.div([attribute.class("text-center py-12")], [ 81 - html.p([attribute.class("text-zinc-400")], [html.text("Loading attendees...")]), 84 + html.p([attribute.class("text-zinc-400")], [ 85 + html.text("Loading attendees..."), 86 + ]), 82 87 ]) 83 88 } 84 89
+2 -6
client/src/pages/login.gleam
··· 20 20 html.label( 21 21 [ 22 22 attribute.for("loginHint"), 23 - attribute.class( 24 - "block text-sm font-medium text-zinc-400 mb-2", 25 - ), 23 + attribute.class("block text-sm font-medium text-zinc-400 mb-2"), 26 24 ], 27 25 [html.text("Handle or PDS Host")], 28 26 ), ··· 56 54 attribute.href("https://bsky.app"), 57 55 attribute.target("_blank"), 58 56 attribute.attribute("rel", "noopener noreferrer"), 59 - attribute.class( 60 - "text-zinc-400 hover:text-zinc-300 underline", 61 - ), 57 + attribute.class("text-zinc-400 hover:text-zinc-300 underline"), 62 58 ], 63 59 [html.text("Create one on Bluesky")], 64 60 ),
+8 -5
client/src/pages/profile_edit.gleam
··· 52 52 DisplayNameUpdated(value) -> FormData(..form_data, display_name: value) 53 53 DescriptionUpdated(value) -> FormData(..form_data, description: value) 54 54 InterestsUpdated(value) -> FormData(..form_data, interests: value) 55 - AvatarFileChanged(_files) -> form_data // Handled in parent with effect 55 + AvatarFileChanged(_files) -> form_data 56 + // Handled in parent with effect 56 57 AvatarFileProcessed(file_data) -> 57 58 FormData( 58 59 ..form_data, ··· 204 205 attribute.id("avatar-upload"), 205 206 attribute.accept(["image/*"]), 206 207 attribute.class("hidden"), 207 - event.on("change", decode.map(decode.dynamic, fn(_) { 208 - on_msg(AvatarFileChanged([])) 209 - })), 208 + event.on( 209 + "change", 210 + decode.map(decode.dynamic, fn(_) { 211 + on_msg(AvatarFileChanged([])) 212 + }), 213 + ), 210 214 ]), 211 215 ]), 212 216 ]), ··· 295 299 ), 296 300 ]) 297 301 } 298 -
+2 -7
client/src/ui/avatar.gleam
··· 12 12 Xl 13 13 } 14 14 15 - pub fn avatar( 16 - src: Option(String), 17 - alt: String, 18 - size: Size, 19 - ) -> Element(msg) { 15 + pub fn avatar(src: Option(String), alt: String, size: Size) -> Element(msg) { 20 16 let size_classes = case size { 21 17 Sm -> "w-8 h-8" 22 18 Md -> "w-12 h-12" ··· 25 21 } 26 22 27 23 let base_classes = 28 - size_classes 29 - <> " rounded-full border-2 border-zinc-700 bg-zinc-800" 24 + size_classes <> " rounded-full border-2 border-zinc-700 bg-zinc-800" 30 25 31 26 case src { 32 27 option.Some(url) ->
+55 -45
client/src/ui/layout.gleam
··· 7 7 User(name: Option(String), handle: String) 8 8 } 9 9 10 - pub fn layout( 11 - user: Option(User), 12 - children: List(Element(msg)), 13 - ) -> Element(msg) { 14 - html.div([attribute.class("min-h-screen bg-zinc-950 text-zinc-300 font-mono")], [ 15 - html.div([attribute.class("max-w-4xl mx-auto px-6 py-12")], [ 16 - // Header 17 - html.div([attribute.class("border-b border-zinc-800 pb-4")], [ 18 - html.div([attribute.class("flex items-end justify-between")], [ 19 - // Logo/Title 20 - html.a( 21 - [ 22 - attribute.href("/"), 23 - attribute.class("flex items-center gap-3 hover:opacity-80 transition-opacity"), 24 - ], 25 - [ 26 - html.div([], [ 27 - html.h1([attribute.class("text-2xl font-bold text-white")], [ 28 - html.text("atmosphere conf"), 10 + pub fn layout(user: Option(User), children: List(Element(msg))) -> Element(msg) { 11 + html.div( 12 + [attribute.class("min-h-screen bg-zinc-950 text-zinc-300 font-mono")], 13 + [ 14 + html.div([attribute.class("max-w-4xl mx-auto px-6 py-12")], [ 15 + // Header 16 + html.div([attribute.class("border-b border-zinc-800 pb-4")], [ 17 + html.div([attribute.class("flex items-end justify-between")], [ 18 + // Logo/Title 19 + html.a( 20 + [ 21 + attribute.href("/"), 22 + attribute.class( 23 + "flex items-center gap-3 hover:opacity-80 transition-opacity", 24 + ), 25 + ], 26 + [ 27 + html.div([], [ 28 + html.h1([attribute.class("text-2xl font-bold text-white")], [ 29 + html.text("atmosphere conf"), 30 + ]), 29 31 ]), 30 - ]), 31 - ], 32 - ), 33 - // Navigation 34 - html.div([attribute.class("flex gap-4 text-xs items-center")], [ 35 - case user { 36 - option.Some(_) -> 37 - html.a( 38 - [ 39 - attribute.href("/attendees"), 40 - attribute.class("px-2 py-1 text-zinc-500 hover:text-zinc-300 transition-colors"), 41 - ], 42 - [html.text("Attendees")], 43 - ) 44 - option.None -> html.text("") 45 - }, 46 - view_nav(user), 32 + ], 33 + ), 34 + // Navigation 35 + html.div([attribute.class("flex gap-4 text-xs items-center")], [ 36 + case user { 37 + option.Some(_) -> 38 + html.a( 39 + [ 40 + attribute.href("/attendees"), 41 + attribute.class( 42 + "px-2 py-1 text-zinc-500 hover:text-zinc-300 transition-colors", 43 + ), 44 + ], 45 + [html.text("Attendees")], 46 + ) 47 + option.None -> html.text("") 48 + }, 49 + view_nav(user), 50 + ]), 47 51 ]), 48 52 ]), 53 + // Spacer 54 + html.div([attribute.class("mb-8")], []), 55 + // Content 56 + html.div([], children), 49 57 ]), 50 - // Spacer 51 - html.div([attribute.class("mb-8")], []), 52 - // Content 53 - html.div([], children), 54 - ]), 55 - ]) 58 + ], 59 + ) 56 60 } 57 61 58 62 fn view_nav(user: Option(User)) -> Element(msg) { ··· 67 71 html.a( 68 72 [ 69 73 attribute.href("/profile/" <> u.handle), 70 - attribute.class("px-2 py-1 text-zinc-400 hover:text-zinc-200 transition-colors"), 74 + attribute.class( 75 + "px-2 py-1 text-zinc-400 hover:text-zinc-200 transition-colors", 76 + ), 71 77 ], 72 78 [html.text(display_name)], 73 79 ), ··· 81 87 html.button( 82 88 [ 83 89 attribute.type_("submit"), 84 - attribute.class("px-2 py-1 text-zinc-500 hover:text-zinc-300 transition-colors cursor-pointer"), 90 + attribute.class( 91 + "px-2 py-1 text-zinc-500 hover:text-zinc-300 transition-colors cursor-pointer", 92 + ), 85 93 ], 86 94 [html.text("Sign Out")], 87 95 ), ··· 94 102 html.a( 95 103 [ 96 104 attribute.href("/login"), 97 - attribute.class("px-2 py-1 text-zinc-500 hover:text-zinc-300 transition-colors"), 105 + attribute.class( 106 + "px-2 py-1 text-zinc-500 hover:text-zinc-300 transition-colors", 107 + ), 98 108 ], 99 109 [html.text("Sign In")], 100 110 )
+2 -7
client/src/ui/location_input.gleam
··· 127 127 ) 128 128 } 129 129 Error(_err) -> { 130 - #( 131 - Model(..model, suggestions: [], is_loading: False), 132 - effect.none(), 133 - ) 130 + #(Model(..model, suggestions: [], is_loading: False), effect.none()) 134 131 } 135 132 } 136 133 } ··· 218 215 fn icon_element() -> Element(Msg) { 219 216 html.div( 220 217 [ 221 - attribute.class( 222 - "absolute right-3 top-1/2 -translate-y-1/2 text-zinc-500", 223 - ), 218 + attribute.class("absolute right-3 top-1/2 -translate-y-1/2 text-zinc-500"), 224 219 ], 225 220 [html.text("📍")], 226 221 )
+1 -4
client/src/ui/textarea.gleam
··· 2 2 import lustre/element.{type Element} 3 3 import lustre/element/html 4 4 5 - pub fn textarea( 6 - attributes: List(Attribute(msg)), 7 - value: String, 8 - ) -> Element(msg) { 5 + pub fn textarea(attributes: List(Attribute(msg)), value: String) -> Element(msg) { 9 6 let classes = 10 7 "w-full px-3 py-2 bg-zinc-900 border border-zinc-800 rounded text-sm text-zinc-300 focus:outline-none focus:border-zinc-700 disabled:opacity-50 disabled:cursor-not-allowed resize-y min-h-[100px]" 11 8
+1 -3
client/src/utils/location.gleam
··· 23 23 24 24 /// Search for locations using Nominatim API 25 25 @external(javascript, "../client_ffi.mjs", "searchLocations") 26 - pub fn search_locations( 27 - query: String, 28 - ) -> Promise(Result(List(Dynamic), String)) 26 + pub fn search_locations(query: String) -> Promise(Result(List(Dynamic), String)) 29 27 30 28 /// Convert lat/lon to H3 index 31 29 @external(javascript, "../client_ffi.mjs", "latLonToH3")
+6 -3
server/src/api/graphql.gleam
··· 86 86 ht: get_profile_gql.CommunityLexiconLocationHthree, 87 87 ) -> Option(profile.HomeTown) { 88 88 case ht.name, ht.value { 89 - Some(name), Some(value) -> Some(profile.HomeTown(name: name, h3_index: value)) 89 + Some(name), Some(value) -> 90 + Some(profile.HomeTown(name: name, h3_index: value)) 90 91 _, _ -> None 91 92 } 92 93 } ··· 96 97 ht: update_profile_gql.CommunityLexiconLocationHthree, 97 98 ) -> Option(profile.HomeTown) { 98 99 case ht.name, ht.value { 99 - Some(name), Some(value) -> Some(profile.HomeTown(name: name, h3_index: value)) 100 + Some(name), Some(value) -> 101 + Some(profile.HomeTown(name: name, h3_index: value)) 100 102 _, _ -> None 101 103 } 102 104 } ··· 323 325 ht: list_profiles_gql.CommunityLexiconLocationHthree, 324 326 ) -> Option(profile.HomeTown) { 325 327 case ht.name, ht.value { 326 - Some(name), Some(value) -> Some(profile.HomeTown(name: name, h3_index: value)) 328 + Some(name), Some(value) -> 329 + Some(profile.HomeTown(name: name, h3_index: value)) 327 330 _, _ -> None 328 331 } 329 332 }
+25 -9
server/src/api/graphql/check_profile_exists.gleam
··· 11 11 OrgAtmosphereconfProfileConnection(edges: List(OrgAtmosphereconfProfileEdge)) 12 12 } 13 13 14 - pub fn org_atmosphereconf_profile_connection_decoder() -> decode.Decoder(OrgAtmosphereconfProfileConnection) { 15 - use edges <- decode.field("edges", decode.list(org_atmosphereconf_profile_edge_decoder())) 14 + pub fn org_atmosphereconf_profile_connection_decoder() -> decode.Decoder( 15 + OrgAtmosphereconfProfileConnection, 16 + ) { 17 + use edges <- decode.field( 18 + "edges", 19 + decode.list(org_atmosphereconf_profile_edge_decoder()), 20 + ) 16 21 decode.success(OrgAtmosphereconfProfileConnection(edges: edges)) 17 22 } 18 23 ··· 20 25 OrgAtmosphereconfProfileEdge(node: OrgAtmosphereconfProfile) 21 26 } 22 27 23 - pub fn org_atmosphereconf_profile_edge_decoder() -> decode.Decoder(OrgAtmosphereconfProfileEdge) { 28 + pub fn org_atmosphereconf_profile_edge_decoder() -> decode.Decoder( 29 + OrgAtmosphereconfProfileEdge, 30 + ) { 24 31 use node <- decode.field("node", org_atmosphereconf_profile_decoder()) 25 32 decode.success(OrgAtmosphereconfProfileEdge(node: node)) 26 33 } ··· 29 36 OrgAtmosphereconfProfile(id: String) 30 37 } 31 38 32 - pub fn org_atmosphereconf_profile_decoder() -> decode.Decoder(OrgAtmosphereconfProfile) { 39 + pub fn org_atmosphereconf_profile_decoder() -> decode.Decoder( 40 + OrgAtmosphereconfProfile, 41 + ) { 33 42 use id <- decode.field("id", decode.string) 34 43 decode.success(OrgAtmosphereconfProfile(id: id)) 35 44 } ··· 40 49 ) 41 50 } 42 51 43 - pub fn check_profile_exists_response_decoder() -> decode.Decoder(CheckProfileExistsResponse) { 44 - use org_atmosphereconf_profiles <- decode.field("orgAtmosphereconfProfiles", org_atmosphereconf_profile_connection_decoder()) 52 + pub fn check_profile_exists_response_decoder() -> decode.Decoder( 53 + CheckProfileExistsResponse, 54 + ) { 55 + use org_atmosphereconf_profiles <- decode.field( 56 + "orgAtmosphereconfProfiles", 57 + org_atmosphereconf_profile_connection_decoder(), 58 + ) 45 59 decode.success(CheckProfileExistsResponse( 46 60 org_atmosphereconf_profiles: org_atmosphereconf_profiles, 47 61 )) 48 62 } 49 63 50 - pub fn check_profile_exists(client: squall.Client, did: String) -> Result(CheckProfileExistsResponse, String) { 64 + pub fn check_profile_exists( 65 + client: squall.Client, 66 + did: String, 67 + ) -> Result(CheckProfileExistsResponse, String) { 51 68 let query = 52 69 "query CheckProfile($did: String!) { orgAtmosphereconfProfiles(where: { did: { eq: $did } }, first: 1) { edges { node { id } } } }" 53 - let variables = 54 - json.object([#("did", json.string(did))]) 70 + let variables = json.object([#("did", json.string(did))]) 55 71 let body = 56 72 json.object([#("query", json.string(query)), #("variables", variables)]) 57 73 use req <- result.try(
+39 -20
server/src/api/graphql/create_profile.gleam
··· 4 4 import gleam/httpc 5 5 import gleam/json 6 6 import gleam/list 7 + import gleam/option.{type Option, None, Some} 7 8 import gleam/result 8 9 import squall 9 - import gleam/option.{type Option, Some, None} 10 10 11 11 pub type OrgAtmosphereconfProfileInput { 12 12 OrgAtmosphereconfProfileInput( ··· 19 19 ) 20 20 } 21 21 22 - fn org_atmosphereconf_profile_input_to_json(input: OrgAtmosphereconfProfileInput) -> json.Json { 23 - [{ 22 + fn org_atmosphereconf_profile_input_to_json( 23 + input: OrgAtmosphereconfProfileInput, 24 + ) -> json.Json { 25 + [ 26 + { 24 27 case input.avatar { 25 28 Some(val) -> Some(#("avatar", val)) 26 29 None -> None 27 30 } 28 - }, { 31 + }, 32 + { 29 33 case input.created_at { 30 34 Some(val) -> Some(#("createdAt", json.string(val))) 31 35 None -> None 32 36 } 33 - }, { 37 + }, 38 + { 34 39 case input.description { 35 40 Some(val) -> Some(#("description", json.string(val))) 36 41 None -> None 37 42 } 38 - }, { 43 + }, 44 + { 39 45 case input.display_name { 40 46 Some(val) -> Some(#("displayName", json.string(val))) 41 47 None -> None 42 48 } 43 - }, { 49 + }, 50 + { 44 51 case input.home_town { 45 52 Some(val) -> Some(#("homeTown", val)) 46 53 None -> None 47 54 } 48 - }, { 55 + }, 56 + { 49 57 case input.interests { 50 - Some(val) -> Some(#("interests", json.array(from: val, of: json.string))) 58 + Some(val) -> 59 + Some(#("interests", json.array(from: val, of: json.string))) 51 60 None -> None 52 61 } 53 - }] 62 + }, 63 + ] 54 64 |> list.filter_map(fn(x) { 55 65 case x { 56 66 Some(val) -> Ok(val) ··· 64 74 OrgAtmosphereconfProfile(id: String) 65 75 } 66 76 67 - pub fn org_atmosphereconf_profile_decoder() -> decode.Decoder(OrgAtmosphereconfProfile) { 77 + pub fn org_atmosphereconf_profile_decoder() -> decode.Decoder( 78 + OrgAtmosphereconfProfile, 79 + ) { 68 80 use id <- decode.field("id", decode.string) 69 81 decode.success(OrgAtmosphereconfProfile(id: id)) 70 82 } ··· 75 87 ) 76 88 } 77 89 78 - pub fn create_profile_response_decoder() -> decode.Decoder(CreateProfileResponse) { 79 - use create_org_atmosphereconf_profile <- decode.field("createOrgAtmosphereconfProfile", org_atmosphereconf_profile_decoder()) 90 + pub fn create_profile_response_decoder() -> decode.Decoder( 91 + CreateProfileResponse, 92 + ) { 93 + use create_org_atmosphereconf_profile <- decode.field( 94 + "createOrgAtmosphereconfProfile", 95 + org_atmosphereconf_profile_decoder(), 96 + ) 80 97 decode.success(CreateProfileResponse( 81 98 create_org_atmosphereconf_profile: create_org_atmosphereconf_profile, 82 99 )) 83 100 } 84 101 85 - pub fn create_profile(client: squall.Client, input: OrgAtmosphereconfProfileInput, rkey: String) -> Result(CreateProfileResponse, String) { 102 + pub fn create_profile( 103 + client: squall.Client, 104 + input: OrgAtmosphereconfProfileInput, 105 + rkey: String, 106 + ) -> Result(CreateProfileResponse, String) { 86 107 let query = 87 108 "mutation CreateProfile($input: OrgAtmosphereconfProfileInput!, $rkey: String) { createOrgAtmosphereconfProfile(input: $input, rkey: $rkey) { id } }" 88 109 let variables = 89 - json.object( 90 - [ 91 - #("input", org_atmosphereconf_profile_input_to_json(input)), 92 - #("rkey", json.string(rkey)), 93 - ], 94 - ) 110 + json.object([ 111 + #("input", org_atmosphereconf_profile_input_to_json(input)), 112 + #("rkey", json.string(rkey)), 113 + ]) 95 114 let body = 96 115 json.object([#("query", json.string(query)), #("variables", variables)]) 97 116 use req <- result.try(
+27 -10
server/src/api/graphql/get_bluesky_profile.gleam
··· 4 4 import gleam/httpc 5 5 import gleam/json 6 6 import gleam/list 7 + import gleam/option.{type Option} 7 8 import gleam/result 8 9 import squall 9 - import gleam/option.{type Option} 10 10 11 11 pub type AppBskyActorProfileConnection { 12 12 AppBskyActorProfileConnection(edges: List(AppBskyActorProfileEdge)) 13 13 } 14 14 15 - pub fn app_bsky_actor_profile_connection_decoder() -> decode.Decoder(AppBskyActorProfileConnection) { 16 - use edges <- decode.field("edges", decode.list(app_bsky_actor_profile_edge_decoder())) 15 + pub fn app_bsky_actor_profile_connection_decoder() -> decode.Decoder( 16 + AppBskyActorProfileConnection, 17 + ) { 18 + use edges <- decode.field( 19 + "edges", 20 + decode.list(app_bsky_actor_profile_edge_decoder()), 21 + ) 17 22 decode.success(AppBskyActorProfileConnection(edges: edges)) 18 23 } 19 24 ··· 21 26 AppBskyActorProfileEdge(node: AppBskyActorProfile) 22 27 } 23 28 24 - pub fn app_bsky_actor_profile_edge_decoder() -> decode.Decoder(AppBskyActorProfileEdge) { 29 + pub fn app_bsky_actor_profile_edge_decoder() -> decode.Decoder( 30 + AppBskyActorProfileEdge, 31 + ) { 25 32 use node <- decode.field("node", app_bsky_actor_profile_decoder()) 26 33 decode.success(AppBskyActorProfileEdge(node: node)) 27 34 } ··· 35 42 } 36 43 37 44 pub fn app_bsky_actor_profile_decoder() -> decode.Decoder(AppBskyActorProfile) { 38 - use display_name <- decode.field("displayName", decode.optional(decode.string)) 45 + use display_name <- decode.field( 46 + "displayName", 47 + decode.optional(decode.string), 48 + ) 39 49 use description <- decode.field("description", decode.optional(decode.string)) 40 50 use avatar <- decode.field("avatar", decode.optional(blob_decoder())) 41 51 decode.success(AppBskyActorProfile( ··· 62 72 ) 63 73 } 64 74 65 - pub fn get_bluesky_profile_response_decoder() -> decode.Decoder(GetBlueskyProfileResponse) { 66 - use app_bsky_actor_profiles <- decode.field("appBskyActorProfiles", app_bsky_actor_profile_connection_decoder()) 75 + pub fn get_bluesky_profile_response_decoder() -> decode.Decoder( 76 + GetBlueskyProfileResponse, 77 + ) { 78 + use app_bsky_actor_profiles <- decode.field( 79 + "appBskyActorProfiles", 80 + app_bsky_actor_profile_connection_decoder(), 81 + ) 67 82 decode.success(GetBlueskyProfileResponse( 68 83 app_bsky_actor_profiles: app_bsky_actor_profiles, 69 84 )) 70 85 } 71 86 72 - pub fn get_bluesky_profile(client: squall.Client, did: String) -> Result(GetBlueskyProfileResponse, String) { 87 + pub fn get_bluesky_profile( 88 + client: squall.Client, 89 + did: String, 90 + ) -> Result(GetBlueskyProfileResponse, String) { 73 91 let query = 74 92 "query GetBskyProfile($did: String!) { appBskyActorProfiles(where: { did: { eq: $did } }, first: 1) { edges { node { displayName description avatar { ref mimeType size } } } } }" 75 - let variables = 76 - json.object([#("did", json.string(did))]) 93 + let variables = json.object([#("did", json.string(did))]) 77 94 let body = 78 95 json.object([#("query", json.string(query)), #("variables", variables)]) 79 96 use req <- result.try(
+42 -14
server/src/api/graphql/get_profile.gleam
··· 4 4 import gleam/httpc 5 5 import gleam/json 6 6 import gleam/list 7 + import gleam/option.{type Option} 7 8 import gleam/result 8 9 import squall 9 - import gleam/option.{type Option} 10 10 11 11 pub type OrgAtmosphereconfProfileConnection { 12 12 OrgAtmosphereconfProfileConnection(edges: List(OrgAtmosphereconfProfileEdge)) 13 13 } 14 14 15 - pub fn org_atmosphereconf_profile_connection_decoder() -> decode.Decoder(OrgAtmosphereconfProfileConnection) { 16 - use edges <- decode.field("edges", decode.list(org_atmosphereconf_profile_edge_decoder())) 15 + pub fn org_atmosphereconf_profile_connection_decoder() -> decode.Decoder( 16 + OrgAtmosphereconfProfileConnection, 17 + ) { 18 + use edges <- decode.field( 19 + "edges", 20 + decode.list(org_atmosphereconf_profile_edge_decoder()), 21 + ) 17 22 decode.success(OrgAtmosphereconfProfileConnection(edges: edges)) 18 23 } 19 24 ··· 21 26 OrgAtmosphereconfProfileEdge(node: OrgAtmosphereconfProfile) 22 27 } 23 28 24 - pub fn org_atmosphereconf_profile_edge_decoder() -> decode.Decoder(OrgAtmosphereconfProfileEdge) { 29 + pub fn org_atmosphereconf_profile_edge_decoder() -> decode.Decoder( 30 + OrgAtmosphereconfProfileEdge, 31 + ) { 25 32 use node <- decode.field("node", org_atmosphereconf_profile_decoder()) 26 33 decode.success(OrgAtmosphereconfProfileEdge(node: node)) 27 34 } ··· 43 50 ) 44 51 } 45 52 46 - pub fn org_atmosphereconf_profile_decoder() -> decode.Decoder(OrgAtmosphereconfProfile) { 53 + pub fn org_atmosphereconf_profile_decoder() -> decode.Decoder( 54 + OrgAtmosphereconfProfile, 55 + ) { 47 56 use id <- decode.field("id", decode.string) 48 57 use uri <- decode.field("uri", decode.string) 49 58 use cid <- decode.field("cid", decode.string) 50 59 use did <- decode.field("did", decode.string) 51 - use actor_handle <- decode.field("actorHandle", decode.optional(decode.string)) 52 - use display_name <- decode.field("displayName", decode.optional(decode.string)) 60 + use actor_handle <- decode.field( 61 + "actorHandle", 62 + decode.optional(decode.string), 63 + ) 64 + use display_name <- decode.field( 65 + "displayName", 66 + decode.optional(decode.string), 67 + ) 53 68 use description <- decode.field("description", decode.optional(decode.string)) 54 69 use avatar <- decode.field("avatar", decode.optional(blob_decoder())) 55 - use home_town <- decode.field("homeTown", decode.optional(community_lexicon_location_hthree_decoder())) 56 - use interests <- decode.field("interests", decode.optional(decode.list(decode.string))) 70 + use home_town <- decode.field( 71 + "homeTown", 72 + decode.optional(community_lexicon_location_hthree_decoder()), 73 + ) 74 + use interests <- decode.field( 75 + "interests", 76 + decode.optional(decode.list(decode.string)), 77 + ) 57 78 use created_at <- decode.field("createdAt", decode.optional(decode.string)) 58 79 use indexed_at <- decode.field("indexedAt", decode.string) 59 80 decode.success(OrgAtmosphereconfProfile( ··· 88 109 CommunityLexiconLocationHthree(name: Option(String), value: Option(String)) 89 110 } 90 111 91 - pub fn community_lexicon_location_hthree_decoder() -> decode.Decoder(CommunityLexiconLocationHthree) { 112 + pub fn community_lexicon_location_hthree_decoder() -> decode.Decoder( 113 + CommunityLexiconLocationHthree, 114 + ) { 92 115 use name <- decode.field("name", decode.optional(decode.string)) 93 116 use value <- decode.field("value", decode.optional(decode.string)) 94 117 decode.success(CommunityLexiconLocationHthree(name: name, value: value)) ··· 101 124 } 102 125 103 126 pub fn get_profile_response_decoder() -> decode.Decoder(GetProfileResponse) { 104 - use org_atmosphereconf_profiles <- decode.field("orgAtmosphereconfProfiles", org_atmosphereconf_profile_connection_decoder()) 127 + use org_atmosphereconf_profiles <- decode.field( 128 + "orgAtmosphereconfProfiles", 129 + org_atmosphereconf_profile_connection_decoder(), 130 + ) 105 131 decode.success(GetProfileResponse( 106 132 org_atmosphereconf_profiles: org_atmosphereconf_profiles, 107 133 )) 108 134 } 109 135 110 - pub fn get_profile(client: squall.Client, handle: String) -> Result(GetProfileResponse, String) { 136 + pub fn get_profile( 137 + client: squall.Client, 138 + handle: String, 139 + ) -> Result(GetProfileResponse, String) { 111 140 let query = 112 141 "query GetProfile($handle: String!) { orgAtmosphereconfProfiles(where: { actorHandle: { eq: $handle } }, first: 1) { edges { node { id uri cid did actorHandle displayName description avatar { ref mimeType size url(preset: \"avatar\") } homeTown { name value } interests createdAt indexedAt } } } }" 113 - let variables = 114 - json.object([#("handle", json.string(handle))]) 142 + let variables = json.object([#("handle", json.string(handle))]) 115 143 let body = 116 144 json.object([#("query", json.string(query)), #("variables", variables)]) 117 145 use req <- result.try(
+41 -14
server/src/api/graphql/list_profiles.gleam
··· 4 4 import gleam/httpc 5 5 import gleam/json 6 6 import gleam/list 7 + import gleam/option.{type Option} 7 8 import gleam/result 8 9 import squall 9 - import gleam/option.{type Option} 10 10 11 11 pub type OrgAtmosphereconfProfileConnection { 12 12 OrgAtmosphereconfProfileConnection(edges: List(OrgAtmosphereconfProfileEdge)) 13 13 } 14 14 15 - pub fn org_atmosphereconf_profile_connection_decoder() -> decode.Decoder(OrgAtmosphereconfProfileConnection) { 16 - use edges <- decode.field("edges", decode.list(org_atmosphereconf_profile_edge_decoder())) 15 + pub fn org_atmosphereconf_profile_connection_decoder() -> decode.Decoder( 16 + OrgAtmosphereconfProfileConnection, 17 + ) { 18 + use edges <- decode.field( 19 + "edges", 20 + decode.list(org_atmosphereconf_profile_edge_decoder()), 21 + ) 17 22 decode.success(OrgAtmosphereconfProfileConnection(edges: edges)) 18 23 } 19 24 ··· 21 26 OrgAtmosphereconfProfileEdge(node: OrgAtmosphereconfProfile) 22 27 } 23 28 24 - pub fn org_atmosphereconf_profile_edge_decoder() -> decode.Decoder(OrgAtmosphereconfProfileEdge) { 29 + pub fn org_atmosphereconf_profile_edge_decoder() -> decode.Decoder( 30 + OrgAtmosphereconfProfileEdge, 31 + ) { 25 32 use node <- decode.field("node", org_atmosphereconf_profile_decoder()) 26 33 decode.success(OrgAtmosphereconfProfileEdge(node: node)) 27 34 } ··· 43 50 ) 44 51 } 45 52 46 - pub fn org_atmosphereconf_profile_decoder() -> decode.Decoder(OrgAtmosphereconfProfile) { 53 + pub fn org_atmosphereconf_profile_decoder() -> decode.Decoder( 54 + OrgAtmosphereconfProfile, 55 + ) { 47 56 use id <- decode.field("id", decode.string) 48 57 use uri <- decode.field("uri", decode.string) 49 58 use cid <- decode.field("cid", decode.string) 50 59 use did <- decode.field("did", decode.string) 51 - use actor_handle <- decode.field("actorHandle", decode.optional(decode.string)) 52 - use display_name <- decode.field("displayName", decode.optional(decode.string)) 60 + use actor_handle <- decode.field( 61 + "actorHandle", 62 + decode.optional(decode.string), 63 + ) 64 + use display_name <- decode.field( 65 + "displayName", 66 + decode.optional(decode.string), 67 + ) 53 68 use description <- decode.field("description", decode.optional(decode.string)) 54 69 use avatar <- decode.field("avatar", decode.optional(blob_decoder())) 55 - use home_town <- decode.field("homeTown", decode.optional(community_lexicon_location_hthree_decoder())) 56 - use interests <- decode.field("interests", decode.optional(decode.list(decode.string))) 70 + use home_town <- decode.field( 71 + "homeTown", 72 + decode.optional(community_lexicon_location_hthree_decoder()), 73 + ) 74 + use interests <- decode.field( 75 + "interests", 76 + decode.optional(decode.list(decode.string)), 77 + ) 57 78 use created_at <- decode.field("createdAt", decode.optional(decode.string)) 58 79 use indexed_at <- decode.field("indexedAt", decode.string) 59 80 decode.success(OrgAtmosphereconfProfile( ··· 88 109 CommunityLexiconLocationHthree(name: Option(String), value: Option(String)) 89 110 } 90 111 91 - pub fn community_lexicon_location_hthree_decoder() -> decode.Decoder(CommunityLexiconLocationHthree) { 112 + pub fn community_lexicon_location_hthree_decoder() -> decode.Decoder( 113 + CommunityLexiconLocationHthree, 114 + ) { 92 115 use name <- decode.field("name", decode.optional(decode.string)) 93 116 use value <- decode.field("value", decode.optional(decode.string)) 94 117 decode.success(CommunityLexiconLocationHthree(name: name, value: value)) ··· 101 124 } 102 125 103 126 pub fn list_profiles_response_decoder() -> decode.Decoder(ListProfilesResponse) { 104 - use org_atmosphereconf_profiles <- decode.field("orgAtmosphereconfProfiles", org_atmosphereconf_profile_connection_decoder()) 127 + use org_atmosphereconf_profiles <- decode.field( 128 + "orgAtmosphereconfProfiles", 129 + org_atmosphereconf_profile_connection_decoder(), 130 + ) 105 131 decode.success(ListProfilesResponse( 106 132 org_atmosphereconf_profiles: org_atmosphereconf_profiles, 107 133 )) 108 134 } 109 135 110 - pub fn list_profiles(client: squall.Client) -> Result(ListProfilesResponse, String) { 136 + pub fn list_profiles( 137 + client: squall.Client, 138 + ) -> Result(ListProfilesResponse, String) { 111 139 let query = 112 140 "query ListProfiles { orgAtmosphereconfProfiles { edges { node { id uri cid did actorHandle displayName description avatar { ref mimeType size url(preset: \"avatar\") } homeTown { name value } interests createdAt indexedAt } } } }" 113 - let variables = 114 - json.object([]) 141 + let variables = json.object([]) 115 142 let body = 116 143 json.object([#("query", json.string(query)), #("variables", variables)]) 117 144 use req <- result.try(
+12 -5
server/src/api/graphql/sync_user_collections.gleam
··· 21 21 SyncUserCollectionsResponse(sync_user_collections: SyncResult) 22 22 } 23 23 24 - pub fn sync_user_collections_response_decoder() -> decode.Decoder(SyncUserCollectionsResponse) { 25 - use sync_user_collections <- decode.field("syncUserCollections", sync_result_decoder()) 24 + pub fn sync_user_collections_response_decoder() -> decode.Decoder( 25 + SyncUserCollectionsResponse, 26 + ) { 27 + use sync_user_collections <- decode.field( 28 + "syncUserCollections", 29 + sync_result_decoder(), 30 + ) 26 31 decode.success(SyncUserCollectionsResponse( 27 32 sync_user_collections: sync_user_collections, 28 33 )) 29 34 } 30 35 31 - pub fn sync_user_collections(client: squall.Client, did: String) -> Result(SyncUserCollectionsResponse, String) { 36 + pub fn sync_user_collections( 37 + client: squall.Client, 38 + did: String, 39 + ) -> Result(SyncUserCollectionsResponse, String) { 32 40 let query = 33 41 "mutation SyncUserCollections($did: String!) { syncUserCollections(did: $did) { success message } }" 34 - let variables = 35 - json.object([#("did", json.string(did))]) 42 + let variables = json.object([#("did", json.string(did))]) 36 43 let body = 37 44 json.object([#("query", json.string(query)), #("variables", variables)]) 38 45 use req <- result.try(
+58 -25
server/src/api/graphql/update_profile.gleam
··· 4 4 import gleam/httpc 5 5 import gleam/json 6 6 import gleam/list 7 + import gleam/option.{type Option, None, Some} 7 8 import gleam/result 8 9 import squall 9 - import gleam/option.{type Option, Some, None} 10 10 11 11 pub type OrgAtmosphereconfProfileInput { 12 12 OrgAtmosphereconfProfileInput( ··· 19 19 ) 20 20 } 21 21 22 - fn org_atmosphereconf_profile_input_to_json(input: OrgAtmosphereconfProfileInput) -> json.Json { 23 - [{ 22 + fn org_atmosphereconf_profile_input_to_json( 23 + input: OrgAtmosphereconfProfileInput, 24 + ) -> json.Json { 25 + [ 26 + { 24 27 case input.avatar { 25 28 Some(val) -> Some(#("avatar", val)) 26 29 None -> None 27 30 } 28 - }, { 31 + }, 32 + { 29 33 case input.created_at { 30 34 Some(val) -> Some(#("createdAt", json.string(val))) 31 35 None -> None 32 36 } 33 - }, { 37 + }, 38 + { 34 39 case input.description { 35 40 Some(val) -> Some(#("description", json.string(val))) 36 41 None -> None 37 42 } 38 - }, { 43 + }, 44 + { 39 45 case input.display_name { 40 46 Some(val) -> Some(#("displayName", json.string(val))) 41 47 None -> None 42 48 } 43 - }, { 49 + }, 50 + { 44 51 case input.home_town { 45 52 Some(val) -> Some(#("homeTown", val)) 46 53 None -> None 47 54 } 48 - }, { 55 + }, 56 + { 49 57 case input.interests { 50 - Some(val) -> Some(#("interests", json.array(from: val, of: json.string))) 58 + Some(val) -> 59 + Some(#("interests", json.array(from: val, of: json.string))) 51 60 None -> None 52 61 } 53 - }] 62 + }, 63 + ] 54 64 |> list.filter_map(fn(x) { 55 65 case x { 56 66 Some(val) -> Ok(val) ··· 77 87 ) 78 88 } 79 89 80 - pub fn org_atmosphereconf_profile_decoder() -> decode.Decoder(OrgAtmosphereconfProfile) { 90 + pub fn org_atmosphereconf_profile_decoder() -> decode.Decoder( 91 + OrgAtmosphereconfProfile, 92 + ) { 81 93 use id <- decode.field("id", decode.string) 82 94 use uri <- decode.field("uri", decode.string) 83 95 use cid <- decode.field("cid", decode.string) 84 96 use did <- decode.field("did", decode.string) 85 - use actor_handle <- decode.field("actorHandle", decode.optional(decode.string)) 86 - use display_name <- decode.field("displayName", decode.optional(decode.string)) 97 + use actor_handle <- decode.field( 98 + "actorHandle", 99 + decode.optional(decode.string), 100 + ) 101 + use display_name <- decode.field( 102 + "displayName", 103 + decode.optional(decode.string), 104 + ) 87 105 use description <- decode.field("description", decode.optional(decode.string)) 88 106 use avatar <- decode.field("avatar", decode.optional(blob_decoder())) 89 - use home_town <- decode.field("homeTown", decode.optional(community_lexicon_location_hthree_decoder())) 90 - use interests <- decode.field("interests", decode.optional(decode.list(decode.string))) 107 + use home_town <- decode.field( 108 + "homeTown", 109 + decode.optional(community_lexicon_location_hthree_decoder()), 110 + ) 111 + use interests <- decode.field( 112 + "interests", 113 + decode.optional(decode.list(decode.string)), 114 + ) 91 115 use created_at <- decode.field("createdAt", decode.optional(decode.string)) 92 116 use indexed_at <- decode.field("indexedAt", decode.string) 93 117 decode.success(OrgAtmosphereconfProfile( ··· 122 146 CommunityLexiconLocationHthree(name: Option(String), value: Option(String)) 123 147 } 124 148 125 - pub fn community_lexicon_location_hthree_decoder() -> decode.Decoder(CommunityLexiconLocationHthree) { 149 + pub fn community_lexicon_location_hthree_decoder() -> decode.Decoder( 150 + CommunityLexiconLocationHthree, 151 + ) { 126 152 use name <- decode.field("name", decode.optional(decode.string)) 127 153 use value <- decode.field("value", decode.optional(decode.string)) 128 154 decode.success(CommunityLexiconLocationHthree(name: name, value: value)) ··· 134 160 ) 135 161 } 136 162 137 - pub fn update_profile_response_decoder() -> decode.Decoder(UpdateProfileResponse) { 138 - use update_org_atmosphereconf_profile <- decode.field("updateOrgAtmosphereconfProfile", org_atmosphereconf_profile_decoder()) 163 + pub fn update_profile_response_decoder() -> decode.Decoder( 164 + UpdateProfileResponse, 165 + ) { 166 + use update_org_atmosphereconf_profile <- decode.field( 167 + "updateOrgAtmosphereconfProfile", 168 + org_atmosphereconf_profile_decoder(), 169 + ) 139 170 decode.success(UpdateProfileResponse( 140 171 update_org_atmosphereconf_profile: update_org_atmosphereconf_profile, 141 172 )) 142 173 } 143 174 144 - pub fn update_profile(client: squall.Client, rkey: String, input: OrgAtmosphereconfProfileInput) -> Result(UpdateProfileResponse, String) { 175 + pub fn update_profile( 176 + client: squall.Client, 177 + rkey: String, 178 + input: OrgAtmosphereconfProfileInput, 179 + ) -> Result(UpdateProfileResponse, String) { 145 180 let query = 146 181 "mutation UpdateProfile($rkey: String!, $input: OrgAtmosphereconfProfileInput!) { updateOrgAtmosphereconfProfile(rkey: $rkey, input: $input) { id uri cid did actorHandle displayName description avatar { ref mimeType size url } homeTown { name value } interests createdAt indexedAt } }" 147 182 let variables = 148 - json.object( 149 - [ 150 - #("rkey", json.string(rkey)), 151 - #("input", org_atmosphereconf_profile_input_to_json(input)), 152 - ], 153 - ) 183 + json.object([ 184 + #("rkey", json.string(rkey)), 185 + #("input", org_atmosphereconf_profile_input_to_json(input)), 186 + ]) 154 187 let body = 155 188 json.object([#("query", json.string(query)), #("variables", variables)]) 156 189 use req <- result.try(
+9 -7
server/src/api/graphql/upload_blob.gleam
··· 36 36 decode.success(UploadBlobResponse(upload_blob: upload_blob)) 37 37 } 38 38 39 - pub fn upload_blob(client: squall.Client, data: String, mime_type: String) -> Result(UploadBlobResponse, String) { 39 + pub fn upload_blob( 40 + client: squall.Client, 41 + data: String, 42 + mime_type: String, 43 + ) -> Result(UploadBlobResponse, String) { 40 44 let query = 41 45 "mutation UploadBlob($data: String!, $mimeType: String!) { uploadBlob(data: $data, mimeType: $mimeType) { blob { ref mimeType size } } }" 42 46 let variables = 43 - json.object( 44 - [ 45 - #("data", json.string(data)), 46 - #("mimeType", json.string(mime_type)), 47 - ], 48 - ) 47 + json.object([ 48 + #("data", json.string(data)), 49 + #("mimeType", json.string(mime_type)), 50 + ]) 49 51 let body = 50 52 json.object([#("query", json.string(query)), #("variables", variables)]) 51 53 use req <- result.try(
+8 -2
shared/src/shared/profile.gleam
··· 50 50 use cid <- decode.field("cid", decode.string) 51 51 use did <- decode.field("did", decode.string) 52 52 use handle <- decode.field("handle", decode.optional(decode.string)) 53 - use display_name <- decode.field("display_name", decode.optional(decode.string)) 53 + use display_name <- decode.field( 54 + "display_name", 55 + decode.optional(decode.string), 56 + ) 54 57 use description <- decode.field("description", decode.optional(decode.string)) 55 58 use avatar_url <- decode.field("avatar_url", decode.optional(decode.string)) 56 59 use avatar_blob <- decode.field( 57 60 "avatar_blob", 58 61 decode.optional(avatar_blob_decoder()), 59 62 ) 60 - use home_town <- decode.field("home_town", decode.optional(home_town_decoder())) 63 + use home_town <- decode.field( 64 + "home_town", 65 + decode.optional(home_town_decoder()), 66 + ) 61 67 use interests <- decode.field( 62 68 "interests", 63 69 decode.optional(decode.list(decode.string)),