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