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

format

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