tangled
alpha
login
or
join now
chadtmiller.com
/
conf-demo
2
fork
atom
Gleam Lustre Fullstack Atproto Demo App w/Slices.Network GraphQL API
2
fork
atom
overview
issues
pulls
pipelines
format
chadtmiller.com
4 months ago
3be03963
6cdb83bb
+397
-221
22 changed files
expand all
collapse all
unified
split
client
src
cache.gleam
client
effects.gleam
init.gleam
view.gleam
pages
attendees.gleam
login.gleam
profile_edit.gleam
ui
avatar.gleam
layout.gleam
location_input.gleam
textarea.gleam
utils
location.gleam
server
src
api
graphql
check_profile_exists.gleam
create_profile.gleam
get_bluesky_profile.gleam
get_profile.gleam
list_profiles.gleam
sync_user_collections.gleam
update_profile.gleam
upload_blob.gleam
graphql.gleam
shared
src
shared
profile.gleam
+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)
0
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"))
0
0
0
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"))
0
0
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)))
0
0
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))),
0
0
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
-
),
0
0
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,
0
0
0
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))))
0
0
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)
0
0
0
0
0
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)
0
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
-
}))
0
0
0
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)
0
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)
0
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)
0
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
-
]),
0
0
0
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...")]),
0
0
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"),
0
0
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"),
0
0
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
0
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
-
})),
0
0
0
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
}
0
+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) {
0
0
0
0
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"
0
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"),
0
0
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),
0
0
47
]),
48
]),
0
0
0
0
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"),
0
0
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"),
0
0
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"),
0
0
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
+
)
0
0
0
0
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())
0
0
0
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"),
0
0
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) {
0
0
0
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))
0
0
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))
0
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))
0
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))
0
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()))
0
0
0
0
0
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) {
0
0
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) {
0
0
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())
0
0
0
0
0
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) {
0
0
0
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))])
0
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
0
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
-
[{
0
0
0
24
case input.avatar {
25
Some(val) -> Some(#("avatar", val))
26
None -> None
27
}
28
-
}, {
0
29
case input.created_at {
30
Some(val) -> Some(#("createdAt", json.string(val)))
31
None -> None
32
}
33
-
}, {
0
34
case input.description {
35
Some(val) -> Some(#("description", json.string(val)))
36
None -> None
37
}
38
-
}, {
0
39
case input.display_name {
40
Some(val) -> Some(#("displayName", json.string(val)))
41
None -> None
42
}
43
-
}, {
0
44
case input.home_town {
45
Some(val) -> Some(#("homeTown", val))
46
None -> None
47
}
48
-
}, {
0
49
case input.interests {
50
-
Some(val) -> Some(#("interests", json.array(from: val, of: json.string)))
0
51
None -> None
52
}
53
-
}]
0
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) {
0
0
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())
0
0
0
0
0
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) {
0
0
0
0
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
0
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
+
])
0
0
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
0
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()))
0
0
0
0
0
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) {
0
0
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))
0
0
0
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())
0
0
0
0
0
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) {
0
0
0
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
0
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))])
0
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
0
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()))
0
0
0
0
0
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) {
0
0
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) {
0
0
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))
0
0
0
0
0
0
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)))
0
0
0
0
0
0
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) {
0
0
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())
0
0
0
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) {
0
0
0
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
0
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))])
0
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
0
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()))
0
0
0
0
0
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) {
0
0
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) {
0
0
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))
0
0
0
0
0
0
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)))
0
0
0
0
0
0
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) {
0
0
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())
0
0
0
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) {
0
0
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
0
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([])
0
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())
0
0
0
0
0
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) {
0
0
0
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))])
0
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
0
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
-
[{
0
0
0
24
case input.avatar {
25
Some(val) -> Some(#("avatar", val))
26
None -> None
27
}
28
-
}, {
0
29
case input.created_at {
30
Some(val) -> Some(#("createdAt", json.string(val)))
31
None -> None
32
}
33
-
}, {
0
34
case input.description {
35
Some(val) -> Some(#("description", json.string(val)))
36
None -> None
37
}
38
-
}, {
0
39
case input.display_name {
40
Some(val) -> Some(#("displayName", json.string(val)))
41
None -> None
42
}
43
-
}, {
0
44
case input.home_town {
45
Some(val) -> Some(#("homeTown", val))
46
None -> None
47
}
48
-
}, {
0
49
case input.interests {
50
-
Some(val) -> Some(#("interests", json.array(from: val, of: json.string)))
0
51
None -> None
52
}
53
-
}]
0
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) {
0
0
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))
0
0
0
0
0
0
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)))
0
0
0
0
0
0
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) {
0
0
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())
0
0
0
0
0
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) {
0
0
0
0
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
0
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
+
])
0
0
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) {
0
0
0
0
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
+
])
0
0
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))
0
0
0
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()))
0
0
0
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)),