For now? I'm experimenting on an old concept.
1
fork

Configure Feed

Select the types of activity you want to include in your feed.

Make the api reachable for the gleam server impl!


Signed-off-by: MLC Bloeiman <mar@strawmelonjuice.com>

+458 -321
-1
.envrc
··· 1 1 if nix flake show &>/dev/null; then 2 2 use flake 3 3 fi 4 - export DATABASE_URL="sqlite3://$(pwd)/data/instance.db"
+3
.gitignore
··· 1 1 backend/impl-gleam/client 2 + backend/impl-gleam/webapi 3 + backend/impl-rs/client 4 + backend/impl-rs/webapi 2 5 3 6 4 7 # -------------------- older ignores
+8 -2
backend/impl-gleam/Containerfile
··· 7 7 FROM ghcr.io/gleam-lang/gleam:v1.15.2-erlang-alpine AS build-client 8 8 WORKDIR /app 9 9 COPY ./client/gleam.toml ./client/manifest.toml ./ 10 + COPY ./webapi/ ../webapi/ 10 11 RUN gleam deps download 11 12 COPY ./client/ ./ 12 13 COPY --from=style-client /app/prepped/ /app/prepped/ ··· 26 27 RUN apk add build-base 27 28 WORKDIR /app 28 29 COPY ./server/gleam.toml ./server/manifest.toml ./ 30 + COPY ./webapi/ ../webapi/ 29 31 RUN gleam deps download 30 32 COPY ./server/ /app/ 31 - RUN cd /app/ && gleam export erlang-shipment 33 + RUN --mount=type=cache,target=/app/build \ 34 + gleam export erlang-shipment && \ 35 + # Move the result out of the cache volume so the next stage can see it 36 + cp -r /app/build/erlang-shipment /app/shipment_final 37 + # RUN cd /app/ && gleam export erlang-shipment # <-- THIS IS THE SLOW STEP. 32 38 33 39 FROM docker.io/library/erlang:28-alpine 34 40 RUN mkdir -p /data && chown 1000 /data ··· 36 42 COPY --from=ghcr.io/amacneil/dbmate:latest /usr/local/bin/dbmate /usr/local/bin/dbmate 37 43 COPY ./db/migrations /app/migrations 38 44 COPY --from=package-client --chown=1000 /build/dist /app/lumina_server/priv/static 39 - COPY --from=package-server --chown=1000 /app/build/erlang-shipment /app 45 + COPY --from=package-server --chown=1000 /app/shipment_final /app 40 46 41 47 WORKDIR /app 42 48
+5 -4
backend/impl-gleam/Justfile
··· 6 6 copy-over-client: 7 7 mkdir -p ./client 8 8 cp -fru $(git rev-parse --show-toplevel)/web/* ./client 9 - 9 + mkdir -p ./webapi 10 + cp -fru $(git rev-parse --show-toplevel)/webapi/* ./webapi 10 11 [doc("Build the styles for Lumina client")] 11 12 [group('building')] 12 13 build-styles: 13 - 14 + 14 15 15 16 [doc("Build the server-side of Lumina into a Podman image, from the Flake! This builds most of Lumina inside your worktree, albeit not tracked.")] 16 17 [group('building')] ··· 58 59 [group('prepare')] 59 60 create-data-dirs: 60 61 mkdir -p ./data/configvars/ 61 - chmod 777 data 62 + chmod 777 data -fR || true 62 63 63 64 [doc("Clean all build artifacts")] 64 65 clean-all: ··· 86 87 [doc("Run the server in development mode with file watching")] 87 88 [group("local-devel")] 88 89 local-devel-watch: 89 - watchexec --restart --debounce 10 --stop-timeout=0 --shell=sh -e rs,gleam,toml,css,ts,json --print-events -- just local-devel 90 + watchexec --restart -I -c --debounce=10s --stop-timeout=0 --shell=sh -e rs,gleam,toml,css,ts,json --print-events -- just local-devel 90 91 91 92 [doc("Runs the commands from local-devel automatically, watches")] 92 93 [group("local-devel")]
+3 -1
backend/impl-gleam/server/gleam.toml
··· 6 6 # your project to the Hex package manager. 7 7 # 8 8 # description = "" 9 - # licences = ["Apache-2.0"] 9 + licences = ["EUPL-1.2"] 10 10 # repository = { type = "github", user = "", repo = "" } 11 11 # links = [{ title = "Website", href = "" }] 12 12 # ··· 34 34 simplifile = ">= 2.4.0 and < 3.0.0" 35 35 booklet = ">= 1.1.0 and < 2.0.0" 36 36 humanise = ">= 1.1.0 and < 2.0.0" 37 + webapi = { path = "../webapi" } 38 + 37 39 38 40 [dev_dependencies] 39 41 gleeunit = ">= 1.0.0 and < 2.0.0"
+2
backend/impl-gleam/server/manifest.toml
··· 43 43 { name = "simplifile", version = "2.4.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "7C18AFA4FED0B4CE1FA5B0B4BAC1FA1744427054EA993565F6F3F82E5453170D" }, 44 44 { name = "sqlight", version = "1.0.3", build_tools = ["gleam"], requirements = ["esqlite", "gleam_stdlib"], otp_app = "sqlight", source = "hex", outer_checksum = "CADD79663C9B61D4BAC960A47CC2D42CA8F48EAF5804DBEB79977287750F4B16" }, 45 45 { name = "tom", version = "2.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_time"], otp_app = "tom", source = "hex", outer_checksum = "234A842F3D087D35737483F5DFB6DE9839E3366EF0CAF8726D2D094210227670" }, 46 + { name = "webapi", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], source = "local", path = "../webapi" }, 46 47 { name = "websocks", version = "3.0.1", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_stdlib"], otp_app = "websocks", source = "hex", outer_checksum = "C70340E5B6C3390383ADA17029DCA6F8903863A7AD8CD8E1520EDCC4FE70D6FD" }, 47 48 { name = "wisp", version = "2.2.2", build_tools = ["gleam"], requirements = ["directories", "exception", "filepath", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "houdini", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "5FF5F1E288C3437252ABB93D8F9CF42FF652CE7AD54480CFE736038DC09C4F22" }, 48 49 { name = "woof", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "woof", source = "hex", outer_checksum = "A5DC2FCB04F23B0F440978885A167A91450B88F7760B969127187C57E05D489C" }, ··· 68 69 shellout = { version = ">= 1.8.0 and < 2.0.0" } 69 70 simplifile = { version = ">= 2.4.0 and < 3.0.0" } 70 71 sqlight = { version = ">= 1.0.3 and < 2.0.0" } 72 + webapi = { path = "../webapi" } 71 73 wisp = { version = ">= 2.2.2 and < 3.0.0" } 72 74 woof = { version = ">= 1.2.0 and < 2.0.0" } 73 75 youid = { version = ">= 1.6.0 and < 2.0.0" }
+43 -9
backend/impl-gleam/server/src/lumina_server.gleam
··· 16 16 // This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND. [cite: 5] 17 17 // See the Licence for the specific language governing permissions and limitations. [cite: 6] 18 18 19 - import booklet.{type Booklet} 19 + import webapi 20 + import gleam/json 21 + import booklet 20 22 import envoy 21 23 import ewe.{type Request, type Response} 22 24 import gleam/bit_array ··· 120 122 True -> 121 123 case events.log_to_db(entry, fields_formatted, db) { 122 124 Ok(_) -> Nil 123 - _ -> { 124 - // echo e 125 - log_to_db |> booklet.set(False) 126 - woof.error("Could not log to database! No longer trying.", []) 125 + Error(e) -> { 126 + woof.error("Could not log to database!\n\n"<>e.message, []) 127 + woof.append_global_context([woof.field("db_logging", "failed: " <> e.message)]) 128 + booklet.set(in: log_to_db, to: False) 127 129 } 128 130 } 129 131 False -> Nil ··· 317 319 } 318 320 319 321 fn client_communication_handler( 320 - _conn: ewe.WebsocketConnection, 322 + conn: ewe.WebsocketConnection, 321 323 state: WebsocketState, 322 324 // That Nil is the internal message, again if we'd follow the example. But 323 325 // Lumina mostly communicates with the database and stores more global variables in Booklets (which is ETS)... So no need. ··· 335 337 case message { 336 338 ewe.Text(json_str) -> { 337 339 connection_logger(woof.Debug, "Received: " <> json_str, []) 338 - // Todo 339 - ewe.websocket_continue(state) 340 - } 340 + case json.parse(json_str, webapi.ws_msg_from_client_decoder()) { 341 + 342 + Error(_) -> { 343 + woof.tap_debug(woof.Warning, "Received malformed message from client.", [ 344 + woof.field("message", json_str), 345 + ]) 346 + ewe.send_close_frame(conn, ewe.CustomCloseCode(code: 4000, data: "Malformed message received.")) 347 + Some(ewe.websocket_stop_abnormal("Malformed message received.")) 348 + } 349 + Ok(message) -> 350 + case message { 351 + webapi.Introduction(client_kind:, try_revive:) -> { 352 + case try_revive { 353 + Some(_) -> todo as "Revive is not implemented yet." 354 + None -> Nil 355 + } 356 + let client_type = case client_kind { 357 + "web" -> { 358 + connection_logger(woof.Debug, "A web client greeds us!", []) 359 + WebClient} 360 + _ -> todo 361 + } 362 + Some(ewe.websocket_continue(WebsocketState(..state, conn_data: ClientConnectionData(..connection_data, client_type: Some(client_type))))) 363 + } 364 + webapi.PostContentRequest(post_id:) -> todo 365 + webapi.RegisterPrecheck(email:, username:, password:) -> todo 366 + webapi.TimeLineRequest(timeline_name:, page:) -> todo 367 + webapi.RegisterRequest(email:, username:, password:) -> todo 368 + webapi.LoginAuthenticationRequest(email_username:, password:) -> todo 369 + webapi.OwnUserInformationRequest -> todo 370 + } 371 + } 372 + |> option.unwrap(ewe.websocket_continue(state)) 373 + 374 + } 341 375 ewe.Binary(_) -> ewe.websocket_continue(state) 342 376 ewe.User(Nil) -> ewe.websocket_continue(state) 343 377 }
+2
backend/impl-rs/Justfile
··· 7 7 copy-over-client: 8 8 mkdir -p ./client 9 9 cp -fru $(git rev-parse --show-toplevel)/web/* ./client 10 + mkdir -p ./webapi 11 + cp -fru $(git rev-parse --show-toplevel)/webapi/* ./webapi 10 12 11 13 12 14 [doc("Build the server-side of Lumina")]
+3 -6
web/gleam.toml
··· 4 4 licences=["EUPL-1.2"] 5 5 6 6 [dependencies] 7 - gleam_json = "2.3.0" 8 - gleam_stdlib = "0.59.0" 7 + gleam_json = ">= 3.1.0 and < 4.0.0" 8 + gleam_stdlib = ">= 0.59.0 and < 2.0.0" 9 9 lustre = ">= 5.0.3 and < 6.0.0" 10 10 lustre_websocket = ">= 0.9.0 and < 1.0.0" 11 11 gleamy_lights = ">= 2.3.0 and < 3.0.0" 12 12 plinth = ">= 0.5.9 and < 1.0.0" 13 13 gleam_time = ">= 1.4.0 and < 2.0.0" 14 - 14 + webapi = { path = "../webapi" } 15 15 [dev-dependencies] 16 16 gleeunit = "~> 1.0" 17 - 18 - [metadata] 19 - tdm_reservation = "true"
+18 -95
web/manifest.toml
··· 2 2 # You typically do not need to edit this file 3 3 4 4 packages = [ 5 - { name = "conversation", version = "2.0.1", build_tools = [ 6 - "gleam", 7 - ], requirements = [ 8 - "gleam_http", 9 - "gleam_javascript", 10 - "gleam_stdlib", 11 - ], otp_app = "conversation", source = "hex", outer_checksum = "103DF47463B8432AB713D6643DC17244B9C82E2B172A343150805129FE584A2F" }, 12 - { name = "envoy", version = "1.0.2", build_tools = [ 13 - "gleam", 14 - ], requirements = [ 15 - "gleam_stdlib", 16 - ], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" }, 17 - { name = "gleam_community_colour", version = "2.0.1", build_tools = [ 18 - "gleam", 19 - ], requirements = [ 20 - "gleam_json", 21 - "gleam_stdlib", 22 - ], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "F0ACE69E3A47E913B03D3D0BB23A5563A91A4A7D20956916286068F4A9F817FE" }, 23 - { name = "gleam_erlang", version = "0.34.0", build_tools = [ 24 - "gleam", 25 - ], requirements = [ 26 - "gleam_stdlib", 27 - ], otp_app = "gleam_erlang", source = "hex", outer_checksum = "0C38F2A128BAA0CEF17C3000BD2097EB80634E239CE31A86400C4416A5D0FDCC" }, 28 - { name = "gleam_http", version = "3.7.2", build_tools = [ 29 - "gleam", 30 - ], requirements = [ 31 - "gleam_stdlib", 32 - ], otp_app = "gleam_http", source = "hex", outer_checksum = "8A70D2F70BB7CFEB5DF048A2183FFBA91AF6D4CF5798504841744A16999E33D2" }, 33 - { name = "gleam_javascript", version = "1.0.0", build_tools = [ 34 - "gleam", 35 - ], requirements = [ 36 - "gleam_stdlib", 37 - ], otp_app = "gleam_javascript", source = "hex", outer_checksum = "EF6C77A506F026C6FB37941889477CD5E4234FCD4337FF0E9384E297CB8F97EB" }, 38 - { name = "gleam_json", version = "2.3.0", build_tools = [ 39 - "gleam", 40 - ], requirements = [ 41 - "gleam_stdlib", 42 - ], otp_app = "gleam_json", source = "hex", outer_checksum = "C55C5C2B318533A8072D221C5E06E5A75711C129E420DD1CE463342106012E5D" }, 43 - { name = "gleam_otp", version = "0.16.1", build_tools = [ 44 - "gleam", 45 - ], requirements = [ 46 - "gleam_erlang", 47 - "gleam_stdlib", 48 - ], otp_app = "gleam_otp", source = "hex", outer_checksum = "50DA1539FC8E8FA09924EB36A67A2BBB0AD6B27BCDED5A7EF627057CF69D035E" }, 49 - { name = "gleam_stdlib", version = "0.59.0", build_tools = [ 50 - "gleam", 51 - ], requirements = [ 52 - ], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "F8FEE9B35797301994B81AF75508CF87C328FE1585558B0FFD188DC2B32EAA95" }, 53 - { name = "gleam_time", version = "1.4.0", build_tools = [ 54 - "gleam", 55 - ], requirements = [ 56 - "gleam_stdlib", 57 - ], otp_app = "gleam_time", source = "hex", outer_checksum = "DCDDC040CE97DA3D2A925CDBBA08D8A78681139745754A83998641C8A3F6587E" }, 58 - { name = "gleamy_lights", version = "2.3.1", build_tools = [ 59 - "gleam", 60 - ], requirements = [ 61 - "envoy", 62 - "gleam_community_colour", 63 - "gleam_stdlib", 64 - ], otp_app = "gleamy_lights", source = "hex", outer_checksum = "CD89DD48BBCD8FBB6B8CB84101C70221CBFB901F711C3C7F81F47288EC8074FD" }, 65 - { name = "gleeunit", version = "1.3.1", build_tools = [ 66 - "gleam", 67 - ], requirements = [ 68 - "gleam_stdlib", 69 - ], otp_app = "gleeunit", source = "hex", outer_checksum = "A7DD6C07B7DA49A6E28796058AA89E651D233B357D5607006D70619CD89DAAAB" }, 70 - { name = "houdini", version = "1.1.0", build_tools = [ 71 - "gleam", 72 - ], requirements = [ 73 - "gleam_stdlib", 74 - ], otp_app = "houdini", source = "hex", outer_checksum = "5BA517E5179F132F0471CB314F27FE210A10407387DA1EA4F6FD084F74469FC2" }, 75 - { name = "lustre", version = "5.0.3", build_tools = [ 76 - "gleam", 77 - ], requirements = [ 78 - "gleam_erlang", 79 - "gleam_json", 80 - "gleam_otp", 81 - "gleam_stdlib", 82 - "houdini", 83 - ], otp_app = "lustre", source = "hex", outer_checksum = "0BB69D69A9E75E675AA2C32A4A0E5086041D037829FC8AD385BA6A59E45A60A2" }, 84 - { name = "lustre_websocket", version = "0.9.0", build_tools = [ 85 - "gleam", 86 - ], requirements = [ 87 - "gleam_stdlib", 88 - "lustre", 89 - ], otp_app = "lustre_websocket", source = "hex", outer_checksum = "7C986F711ACCF7F4EF4C24BDE0BE1D25D805A92ED3BFFE10BE61EBE1E92065D6" }, 90 - { name = "plinth", version = "0.5.9", build_tools = [ 91 - "gleam", 92 - ], requirements = [ 93 - "conversation", 94 - "gleam_javascript", 95 - "gleam_json", 96 - "gleam_stdlib", 97 - ], otp_app = "plinth", source = "hex", outer_checksum = "9684C5D768F99B34537B48B100509389C45D2E7C045426E93ACB250993611724" }, 5 + { name = "envoy", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "850DA9D29D2E5987735872A2B5C81035146D7FE19EFC486129E44440D03FD832" }, 6 + { name = "gleam_community_colour", version = "2.0.4", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], otp_app = "gleam_community_colour", source = "hex", outer_checksum = "6DB4665555D7D2B27F0EA32EF47E8BEBC4303821765F9C73D483F38EE24894F0" }, 7 + { name = "gleam_erlang", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "1124AD3AA21143E5AF0FC5CF3D9529F6DB8CA03E43A55711B60B6B7B3874375C" }, 8 + { name = "gleam_javascript", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_javascript", source = "hex", outer_checksum = "EF6C77A506F026C6FB37941889477CD5E4234FCD4337FF0E9384E297CB8F97EB" }, 9 + { name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" }, 10 + { name = "gleam_otp", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BA6A294E295E428EC1562DC1C11EA7530DCB981E8359134BEABC8493B7B2258E" }, 11 + { name = "gleam_stdlib", version = "0.71.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "702F3BC2A14793906880B1078B19A6165F87323AEE8D0C4A34085846336FCAAE" }, 12 + { name = "gleam_time", version = "1.8.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_time", source = "hex", outer_checksum = "533D8723774D61AD4998324F5DD1DABDCDBFABAFB9E87CB5D03C6955448FC97D" }, 13 + { name = "gleamy_lights", version = "2.3.1", build_tools = ["gleam"], requirements = ["envoy", "gleam_community_colour", "gleam_stdlib"], otp_app = "gleamy_lights", source = "hex", outer_checksum = "CD89DD48BBCD8FBB6B8CB84101C70221CBFB901F711C3C7F81F47288EC8074FD" }, 14 + { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, 15 + { name = "houdini", version = "1.2.0", build_tools = ["gleam"], requirements = [], otp_app = "houdini", source = "hex", outer_checksum = "5DB1053F1AF828049C2B206D4403C18970ABEF5C18671CA3C2D2ED0DD64F6385" }, 16 + { name = "lustre", version = "5.6.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_json", "gleam_otp", "gleam_stdlib", "houdini"], otp_app = "lustre", source = "hex", outer_checksum = "EE558CD4DB9F09FCC16417ADF0183A3C2DAC3E4B21ED3AC0CAE859792AB810CA" }, 17 + { name = "lustre_websocket", version = "0.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "lustre"], otp_app = "lustre_websocket", source = "hex", outer_checksum = "7C986F711ACCF7F4EF4C24BDE0BE1D25D805A92ED3BFFE10BE61EBE1E92065D6" }, 18 + { name = "plinth", version = "0.10.2", build_tools = ["gleam"], requirements = ["gleam_javascript", "gleam_json", "gleam_stdlib"], otp_app = "plinth", source = "hex", outer_checksum = "3FE77CED3F19D70918EE32CE8BFB12BE1C28CA004D997F874C2D8DAD2DB73D87" }, 19 + { name = "webapi", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_json", "gleam_stdlib"], source = "local", path = "../webapi" }, 98 20 ] 99 21 100 22 [requirements] 101 - gleam_json = { version = "2.3.0" } 102 - gleam_stdlib = { version = "0.59.0" } 23 + gleam_json = { version = ">= 3.1.0 and < 4.0.0" } 24 + gleam_stdlib = { version = ">= 0.59.0 and < 2.0.0" } 103 25 gleam_time = { version = ">= 1.4.0 and < 2.0.0" } 104 26 gleamy_lights = { version = ">= 2.3.0 and < 3.0.0" } 105 27 gleeunit = { version = "~> 1.0" } 106 28 lustre = { version = ">= 5.0.3 and < 6.0.0" } 107 29 lustre_websocket = { version = ">= 0.9.0 and < 1.0.0" } 108 30 plinth = { version = ">= 0.5.9 and < 1.0.0" } 31 + webapi = { path = "../webapi" }
+27 -203
web/src/lumina_client.gleam
··· 3 3 4 4 // Lumina/Peonies 5 5 // Copyright (C) 2018-2026 MLC 'Strawmelonjuice' Bloeiman and contributors. [cite: 4] 6 - // 6 + // 7 7 // This software is licensed under the European Union Public Licence (EUPL) v1.2. 8 8 // You may not use this work except in compliance with the Licence. 9 9 // You may obtain a copy of the Licence at: https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 10 - // 11 - // AI TRAINING NOTICE: Rights for TDM and AI training are EXPRESSLY RESERVED 10 + // 11 + // AI TRAINING NOTICE: Rights for TDM and AI training are EXPRESSLY RESERVED 12 12 // under Art 4(3) Dir 2019/790. AI training constitutes a Derivative Work. 13 13 // See LICENSE file in the repository root for full details. 14 - // 15 - // 14 + // 15 + // 16 16 // This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND. [cite: 5] 17 17 // See the Licence for the specific language governing permissions and limitations. [cite: 6] 18 18 19 19 import gleam/bool 20 20 import gleam/dict 21 - import gleam/dynamic/decode 22 21 import gleam/float 23 22 import gleam/int 24 23 import gleam/json ··· 28 27 import gleam/string 29 28 import gleam/time/timestamp 30 29 import gleamy_lights/console 30 + import webapi.{encode_ws_msg_from_client as encode_ws_msg, LoginAuthenticationRequest, RegisterPrecheck, RegisterRequest, } 31 31 import gleamy_lights/premixed 32 32 import lumina_client/dom 33 33 import lumina_client/helpers.{login_view_checker, model_local_storage_key} ··· 87 87 Ok(timeline) -> { 88 88 case homepage.get_next_page_to_load(timeline) { 89 89 Some(next_page) -> 90 - TimeLineRequest(timeline_name, next_page) 90 + webapi.TimeLineRequest(timeline_name, next_page) 91 91 |> encode_ws_msg 92 92 |> json.to_string 93 93 |> lustre_websocket.send(socket, _) ··· 95 95 } 96 96 } 97 97 Error(_) -> 98 - TimeLineRequest(timeline_name, 0) 98 + webapi.TimeLineRequest(timeline_name, 0) 99 99 |> encode_ws_msg 100 100 |> json.to_string 101 101 |> lustre_websocket.send(socket, _) ··· 510 510 // Request unless cached or load next page if needed. 511 511 let requ = case model.cache.cached_timelines |> dict.get(tid) { 512 512 Error(..) -> 513 - TimeLineRequest(tid, 0) 513 + webapi.TimeLineRequest(tid, 0) 514 514 |> encode_ws_msg 515 515 |> json.to_string 516 516 |> lustre_websocket.send(socket, _) ··· 520 520 True -> { 521 521 case homepage.get_next_page_to_load(timeline) { 522 522 Some(next_page) -> 523 - TimeLineRequest(tid, next_page) 523 + webapi.TimeLineRequest(tid, next_page) 524 524 |> encode_ws_msg 525 525 |> json.to_string 526 526 |> lustre_websocket.send(socket, _) ··· 592 592 lustre_websocket.InvalidUrl -> panic 593 593 lustre_websocket.OnTextMessage(notice) -> 594 594 case 595 - json.parse(notice, { 596 - ws_msg_decoder( 597 - json.parse(notice, ws_msg_typedefiner()) 598 - |> result.unwrap("Unparsable message"), 599 - ) 600 - }) 595 + json.parse(notice, webapi.ws_msg_from_server_decoder()) 601 596 { 602 - Ok(Greeting(m)) -> { 597 + Ok(webapi.Greeting(m)) -> { 603 598 console.log("The server says hi! '" <> m <> "'") 604 599 #(model, effect.none()) 605 600 } 606 - Ok(RegisterPrecheckResponse(ok, why)) -> { 601 + Ok(webapi.RegisterPrecheckResponse(ok, why)) -> { 607 602 console.log("Register precheck response: " <> string.inspect(ok)) 608 603 let ready = 609 604 case ok { ··· 620 615 _ -> #(model, effect.none()) 621 616 } 622 617 } 623 - Ok(OwnUserInformationResponse( 618 + Ok(webapi.OwnUserInformationResponse( 624 619 username:, 625 620 email:, 626 621 avatar:, ··· 663 658 effect.none(), 664 659 ) 665 660 } 666 - Ok(AuthenticationSuccess(_username, token:)) -> { 661 + Ok(webapi.AuthenticationSuccess(_username, token:)) -> { 667 662 let assert model_type.WsConnectionConnected(socket) = model.ws 668 663 as "Socket not connected" 669 664 #( ··· 674 669 token: Some(token), 675 670 ), 676 671 effect.batch([ 677 - OwnUserInformationRequest 672 + webapi.OwnUserInformationRequest 678 673 |> encode_ws_msg 679 674 |> json.to_string 680 675 |> lustre_websocket.send(socket, _), 681 676 // Even though 'officially' we don't show the global timeline, this should be the one requested firstly. 682 - TimeLineRequest("global", 0) 677 + webapi.TimeLineRequest("global", 0) 683 678 |> encode_ws_msg 684 679 |> json.to_string 685 680 |> lustre_websocket.send(socket, _), 686 681 ]), 687 682 ) 688 683 } 689 - Ok(AuthenticationFailure) -> { 684 + Ok(webapi.AuthenticationFailure) -> { 690 685 case model.page { 691 686 model_type.Landing | HomeTimeline(..) | NotFound(..) | Licence -> 692 687 session_destroy() ··· 698 693 Register(..) -> #(model, effect.none()) 699 694 } 700 695 } 701 - Ok(TimeLineResponse( 696 + Ok(webapi.TimeLineResponse( 702 697 timeline_name:, 703 698 timeline_id:, 704 699 items:, ··· 727 722 let posts_fetches = 728 723 effect.batch( 729 724 list.map(items, fn(post_id) { 730 - PostContentRequest(post_id:) 725 + webapi.PostContentRequest(post_id:) 731 726 |> encode_ws_msg 732 727 |> json.to_string 733 728 |> lustre_websocket.send(socket, _) ··· 786 781 ) 787 782 #(model, effect.none()) 788 783 } 789 - Ok(Undecodable) -> 784 + Ok(webapi.Undecodable) -> 790 785 panic as "Received message that was explicitly marked as undecodable, this should not happen 791 786 as the decoder should have returned an error instead of Undecodable. Check the decoder implementation and the logs 792 787 for the raw message." ··· 838 833 Model(..model, ws: model_type.WsConnectionConnected(socket)), 839 834 lustre_websocket.send( 840 835 socket, 841 - { 842 - let x = [ 843 - #("type", json.string("introduction")), 844 - #("client_kind", json.string("web")), 845 - ] 846 - json.object(case model.user, model.token { 847 - None, Some(token) -> { 848 - // traversing x is okay. 849 - list.append(x, [#("try_revive", json.string(token))]) 850 - } 851 - _, _ -> x 852 - }) 853 - } 854 - |> json.to_string(), 855 - ), 836 + webapi.Introduction("web", case model.user, model.token { 837 + None, Some(token) -> Some(token) 838 + _, _ -> None 839 + })|>encode_ws_msg|>json.to_string, 840 + 841 + ), 856 842 ) 857 843 } 858 844 } 859 845 860 - // WS Message decoding --------------------------------------------------------- 861 - 862 - type WsMsgFromServer { 863 - Greeting(greeting: String) 864 - RegisterPrecheckResponse(ok: Bool, why: String) 865 - AuthenticationSuccess(username: String, token: String) 866 - AuthenticationFailure 867 - TimeLineResponse( 868 - timeline_name: String, 869 - timeline_id: String, 870 - /// List of post ids as string. 871 - items: List(String), 872 - /// Total number of posts in timeline 873 - total_count: Int, 874 - /// Current page number 875 - page: Int, 876 - /// Whether there are more pages available 877 - has_more: Bool, 878 - ) 879 - OwnUserInformationResponse( 880 - username: String, 881 - email: String, 882 - // Optional field populated with mime type and base64 of a profile picture. 883 - avatar: option.Option(#(String, String)), 884 - uuid: String, 885 - /// Number of unread notifications, a timeline request for "notifications" can be used to get the actual notifications and fill the cache. 886 - unread_notifications: Int, 887 - ) 888 - Undecodable 889 - } 890 - 891 - type WsMsgFromClient { 892 - OwnUserInformationRequest 893 - LoginAuthenticationRequest(email_username: String, password: String) 894 - RegisterRequest(email: String, username: String, password: String) 895 - TimeLineRequest(timeline_name: String, page: Int) 896 - RegisterPrecheck( 897 - email: String, 898 - username: String, 899 - // Password only once? Yes, the equal password check is done in the view/update themselves. 900 - password: String, 901 - ) 902 - PostContentRequest(post_id: String) 903 - } 904 - 905 - fn encode_ws_msg(message: WsMsgFromClient) -> json.Json { 906 - case message { 907 - OwnUserInformationRequest -> 908 - json.object([#("type", json.string("own_user_information_request"))]) 909 - LoginAuthenticationRequest(email_username, password) -> 910 - json.object([ 911 - #("type", json.string("login_authentication_request")), 912 - #("email_username", json.string(email_username)), 913 - #("password", json.string(password)), 914 - ]) 915 - 916 - RegisterRequest(email, username, password) -> 917 - json.object([ 918 - #("type", json.string("register_request")), 919 - #("email", json.string(email)), 920 - #("username", json.string(username)), 921 - #("password", json.string(password)), 922 - ]) 923 - RegisterPrecheck(email, username, password) -> 924 - json.object([ 925 - #("type", json.string("register_precheck")), 926 - #("email", json.string(email)), 927 - #("username", json.string(username)), 928 - #("password", json.string(password)), 929 - ]) 930 - TimeLineRequest(timeline_name:, page:) -> 931 - json.object([ 932 - #("type", json.string("timeline_request")), 933 - #("by_name", json.string(timeline_name)), 934 - #("page", json.int(page)), 935 - ]) 936 - PostContentRequest(post_id:) -> { 937 - json.object([ 938 - #("type", json.string("post_view_request")), 939 - #("post_id", json.string(post_id)), 940 - ]) 941 - } 942 - } 943 - } 944 846 945 847 fn send_refresh_request(model: model_type.Model) -> Effect(Msg) { 946 848 let current_time = ··· 964 866 ) 965 867 } 966 868 } 967 - } 968 - 969 - fn ws_msg_decoder(variant: String) -> decode.Decoder(WsMsgFromServer) { 970 - case variant { 971 - "auth_success" -> { 972 - use username <- decode.field("username", decode.string) 973 - use token <- decode.field("token", decode.string) 974 - decode.success(AuthenticationSuccess(username:, token:)) 975 - } 976 - "auth_failure" -> { 977 - decode.success(AuthenticationFailure) 978 - } 979 - "unknown" -> decode.success(Undecodable) 980 - "register_precheck_response" -> { 981 - use ok <- decode.field("ok", decode.bool) 982 - use why <- decode.field("why", decode.string) 983 - decode.success(RegisterPrecheckResponse(ok, why)) 984 - } 985 - "greeting" -> { 986 - use greeting <- decode.field("greeting", decode.string) 987 - decode.success(Greeting(greeting:)) 988 - } 989 - "timeline_response" -> { 990 - console.log("Decoding timeline response: " <> variant) 991 - use timeline_name <- decode.field("timeline_name", decode.string) 992 - use timeline_id <- decode.field("timeline_id", decode.string) 993 - use items <- decode.field("post_ids", decode.list(decode.string)) 994 - use total_count <- decode.field("total_count", decode.int) 995 - use page <- decode.field("page", decode.int) 996 - use has_more <- decode.field("has_more", decode.bool) 997 - decode.success(TimeLineResponse( 998 - timeline_name:, 999 - timeline_id:, 1000 - items:, 1001 - total_count:, 1002 - page:, 1003 - has_more:, 1004 - )) 1005 - } 1006 - "own_user_information_response" -> { 1007 - use username <- decode.field("username", decode.string) 1008 - use email <- decode.field("email", decode.string) 1009 - use unread_notifications <- decode.field( 1010 - "unread_notifications", 1011 - decode.int, 1012 - ) 1013 - // avatar may be null or an array [mime, base64] 1014 - use avatar_list_opt <- decode.field( 1015 - "avatar", 1016 - decode.optional(decode.list(decode.string)), 1017 - ) 1018 - let avatar = case avatar_list_opt { 1019 - Some(list) -> 1020 - case list { 1021 - [mime, b64] -> Some(#(mime, b64)) 1022 - _ -> None 1023 - } 1024 - None -> None 1025 - } 1026 - use uuid <- decode.field("uuid", decode.string) 1027 - decode.success(OwnUserInformationResponse( 1028 - username:, 1029 - email:, 1030 - avatar:, 1031 - uuid:, 1032 - unread_notifications:, 1033 - )) 1034 - } 1035 - g -> { 1036 - console.error("Unknown message type: " <> g) 1037 - decode.failure(Undecodable, g) 1038 - } 1039 - } 1040 - } 1041 - 1042 - fn ws_msg_typedefiner() -> decode.Decoder(String) { 1043 - use variant <- decode.field("type", decode.string) 1044 - decode.success(variant) 1045 869 } 1046 870 1047 871 fn session_destroy() -> #(Model, Effect(Msg)) {
+4
webapi/.gitignore
··· 1 + *.beam 2 + *.ez 3 + /build 4 + erl_crash.dump
+25
webapi/README.md
··· 1 + # webapi 2 + 3 + [![Package Version](https://img.shields.io/hexpm/v/webapi)](https://hex.pm/packages/webapi) 4 + [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/webapi/) 5 + 6 + ```sh 7 + gleam add webapi@1 8 + ``` 9 + 10 + ```gleam 11 + import webapi 12 + 13 + pub fn main() -> Nil { 14 + // TODO: An example of the project in use 15 + } 16 + ``` 17 + 18 + Further documentation can be found at <https://hexdocs.pm/webapi>. 19 + 20 + ## Development 21 + 22 + ```sh 23 + gleam run # Run the project 24 + gleam test # Run the tests 25 + ```
+20
webapi/gleam.toml
··· 1 + name = "webapi" 2 + version = "1.0.0" 3 + 4 + # Fill out these fields if you intend to generate HTML documentation or publish 5 + # your project to the Hex package manager. 6 + # 7 + # description = "" 8 + # licences = ["Apache-2.0"] 9 + # repository = { type = "github", user = "", repo = "" } 10 + # links = [{ title = "Website", href = "" }] 11 + # 12 + # For a full reference of all the available options, you can have a look at 13 + # https://gleam.run/writing-gleam/gleam-toml/. 14 + 15 + [dependencies] 16 + gleam_stdlib = ">= 0.44.0 and < 2.0.0" 17 + gleam_json = ">= 3.1.0 and < 4.0.0" 18 + 19 + [dev_dependencies] 20 + gleeunit = ">= 1.0.0 and < 2.0.0"
+13
webapi/manifest.toml
··· 1 + # This file was generated by Gleam 2 + # You typically do not need to edit this file 3 + 4 + packages = [ 5 + { name = "gleam_json", version = "3.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "44FDAA8847BE8FC48CA7A1C089706BD54BADCC4C45B237A992EDDF9F2CDB2836" }, 6 + { name = "gleam_stdlib", version = "0.71.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "702F3BC2A14793906880B1078B19A6165F87323AEE8D0C4A34085846336FCAAE" }, 7 + { name = "gleeunit", version = "1.9.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "DA9553CE58B67924B3C631F96FE3370C49EB6D6DC6B384EC4862CC4AAA718F3C" }, 8 + ] 9 + 10 + [requirements] 11 + gleam_json = { version = ">= 3.1.0 and < 4.0.0" } 12 + gleam_stdlib = { version = ">= 0.44.0 and < 2.0.0" } 13 + gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
+282
webapi/src/webapi.gleam
··· 1 + //// Lumina > Web API types and decoders/encoders for server-client communication. 2 + 3 + // Lumina/Peonies 4 + // Copyright (C) 2018-2026 MLC 'Strawmelonjuice' Bloeiman and contributors. [cite: 4] 5 + // 6 + // This software is licensed under the European Union Public Licence (EUPL) v1.2. 7 + // You may not use this work except in compliance with the Licence. 8 + // You may obtain a copy of the Licence at: https://joinup.ec.europa.eu/collection/eupl/eupl-text-eupl-12 9 + // 10 + // AI TRAINING NOTICE: Rights for TDM and AI training are EXPRESSLY RESERVED 11 + // under Art 4(3) Dir 2019/790. AI training constitutes a Derivative Work. 12 + // See LICENSE file in the repository root for full details. 13 + // 14 + // 15 + // This software is provided "AS IS", WITHOUT WARRANTY OF ANY KIND. [cite: 5] 16 + // See the Licence for the specific language governing permissions and limitations. [cite: 6] 17 + 18 + 19 + import gleam/dynamic/decode 20 + import gleam/json 21 + import gleam/option.{Some, None} 22 + 23 + /// Message types for websocket communication from server to client. 24 + pub type WsMsgFromServer { 25 + Greeting(greeting: String) 26 + RegisterPrecheckResponse(ok: Bool, why: String) 27 + AuthenticationSuccess(username: String, token: String) 28 + AuthenticationFailure 29 + TimeLineResponse( 30 + timeline_name: String, 31 + timeline_id: String, 32 + /// List of post ids as string. 33 + items: List(String), 34 + /// Total number of posts in timeline 35 + total_count: Int, 36 + /// Current page number 37 + page: Int, 38 + /// Whether there are more pages available 39 + has_more: Bool, 40 + ) 41 + OwnUserInformationResponse( 42 + username: String, 43 + email: String, 44 + // Optional field populated with mime type and base64 of a profile picture. 45 + avatar: option.Option(#(String, String)), 46 + uuid: String, 47 + /// Number of unread notifications, a timeline request for "notifications" can be used to get the actual notifications and fill the cache. 48 + unread_notifications: Int, 49 + ) 50 + Undecodable 51 + } 52 + 53 + pub fn ws_msg_from_server_decoder() -> decode.Decoder(WsMsgFromServer) { 54 + use variant <- decode.field("type", decode.string) 55 + 56 + case variant { 57 + "auth_success" -> { 58 + use username <- decode.field("username", decode.string) 59 + use token <- decode.field("token", decode.string) 60 + decode.success(AuthenticationSuccess(username:, token:)) 61 + } 62 + "auth_failure" -> { 63 + decode.success(AuthenticationFailure) 64 + } 65 + "unknown" -> decode.success(Undecodable) 66 + "register_precheck_response" -> { 67 + use ok <- decode.field("ok", decode.bool) 68 + use why <- decode.field("why", decode.string) 69 + decode.success(RegisterPrecheckResponse(ok, why)) 70 + } 71 + "greeting" -> { 72 + use greeting <- decode.field("greeting", decode.string) 73 + decode.success(Greeting(greeting:)) 74 + } 75 + "timeline_response" -> { 76 + echo "Decoding timeline response: " <> variant 77 + use timeline_name <- decode.field("timeline_name", decode.string) 78 + use timeline_id <- decode.field("timeline_id", decode.string) 79 + use items <- decode.field("post_ids", decode.list(decode.string)) 80 + use total_count <- decode.field("total_count", decode.int) 81 + use page <- decode.field("page", decode.int) 82 + use has_more <- decode.field("has_more", decode.bool) 83 + decode.success(TimeLineResponse( 84 + timeline_name:, 85 + timeline_id:, 86 + items:, 87 + total_count:, 88 + page:, 89 + has_more:, 90 + )) 91 + } 92 + "own_user_information_response" -> { 93 + use username <- decode.field("username", decode.string) 94 + use email <- decode.field("email", decode.string) 95 + use unread_notifications <- decode.field( 96 + "unread_notifications", 97 + decode.int, 98 + ) 99 + // avatar may be null or an array [mime, base64] 100 + use avatar_list_opt <- decode.field( 101 + "avatar", 102 + decode.optional(decode.list(decode.string)), 103 + ) 104 + let avatar = case avatar_list_opt { 105 + Some(list) -> 106 + case list { 107 + [mime, b64] -> Some(#(mime, b64)) 108 + _ -> None 109 + } 110 + None -> None 111 + } 112 + use uuid <- decode.field("uuid", decode.string) 113 + decode.success(OwnUserInformationResponse( 114 + username:, 115 + email:, 116 + avatar:, 117 + uuid:, 118 + unread_notifications:, 119 + )) 120 + } 121 + g -> { 122 + decode.failure(Undecodable, g) 123 + } 124 + } 125 + } 126 + 127 + 128 + pub fn encode_ws_msg_from_server(ws_msg_from_server: WsMsgFromServer) -> json.Json { 129 + case ws_msg_from_server { 130 + Greeting(greeting:) -> json.object([ 131 + #("type", json.string("greeting")), 132 + #("greeting", json.string(greeting)), 133 + ]) 134 + RegisterPrecheckResponse(ok:, why:) -> json.object([ 135 + #("type", json.string("register_precheck_response")), 136 + #("ok", json.bool(ok)), 137 + #("why", json.string(why)), 138 + ]) 139 + AuthenticationSuccess(username:, token:) -> json.object([ 140 + #("type", json.string("authentication_success")), 141 + #("username", json.string(username)), 142 + #("token", json.string(token)), 143 + ]) 144 + AuthenticationFailure -> json.object([ 145 + #("type", json.string("authentication_failure")), 146 + ]) 147 + TimeLineResponse(timeline_name:, timeline_id:, items:, total_count:, page:, has_more:) -> json.object([ 148 + #("type", json.string("time_line_response")), 149 + #("timeline_name", json.string(timeline_name)), 150 + #("timeline_id", json.string(timeline_id)), 151 + #("items", json.array(items, json.string)), 152 + #("total_count", json.int(total_count)), 153 + #("page", json.int(page)), 154 + #("has_more", json.bool(has_more)), 155 + ]) 156 + OwnUserInformationResponse(username:, email:, avatar:, uuid:, unread_notifications:) -> json.object([ 157 + #("type", json.string("own_user_information_response")), 158 + #("username", json.string(username)), 159 + #("email", json.string(email)), 160 + #("avatar", case avatar { 161 + None -> json.null() 162 + Some(value) -> json.preprocessed_array([ 163 + json.string(value.0), 164 + json.string(value.1), 165 + ]) 166 + }), 167 + #("uuid", json.string(uuid)), 168 + #("unread_notifications", json.int(unread_notifications)), 169 + ]) 170 + Undecodable -> json.object([ 171 + #("type", json.string("undecodable")), 172 + ]) 173 + } 174 + } 175 + 176 + /// Message types for websocket communication from client to server. 177 + pub type WsMsgFromClient { 178 + Introduction(client_kind: String, try_revive: option.Option(String)) 179 + OwnUserInformationRequest 180 + LoginAuthenticationRequest(email_username: String, password: String) 181 + RegisterRequest(email: String, username: String, password: String) 182 + TimeLineRequest(timeline_name: String, page: Int) 183 + RegisterPrecheck( 184 + email: String, 185 + username: String, 186 + // Password only once? Yes, the equal password check is done in the view/update themselves. 187 + password: String, 188 + ) 189 + PostContentRequest(post_id: String) 190 + } 191 + 192 + 193 + pub fn encode_ws_msg_from_client(message: WsMsgFromClient) -> json.Json { 194 + case message { 195 + Introduction(client_kind:, try_revive:) -> json.object([ 196 + #("type", json.string("introduction")), 197 + #("client_kind", json.string(client_kind)), 198 + #("try_revive", case try_revive { 199 + None -> json.null() 200 + Some(token) -> json.string(token) 201 + }), 202 + ]) 203 + OwnUserInformationRequest -> 204 + json.object([#("type", json.string("own_user_information_request"))]) 205 + LoginAuthenticationRequest(email_username, password) -> 206 + json.object([ 207 + #("type", json.string("login_authentication_request")), 208 + #("email_username", json.string(email_username)), 209 + #("password", json.string(password)), 210 + ]) 211 + 212 + RegisterRequest(email, username, password) -> 213 + json.object([ 214 + #("type", json.string("register_request")), 215 + #("email", json.string(email)), 216 + #("username", json.string(username)), 217 + #("password", json.string(password)), 218 + ]) 219 + RegisterPrecheck(email, username, password) -> 220 + json.object([ 221 + #("type", json.string("register_precheck")), 222 + #("email", json.string(email)), 223 + #("username", json.string(username)), 224 + #("password", json.string(password)), 225 + ]) 226 + TimeLineRequest(timeline_name:, page:) -> 227 + json.object([ 228 + #("type", json.string("timeline_request")), 229 + #("by_name", json.string(timeline_name)), 230 + #("page", json.int(page)), 231 + ]) 232 + PostContentRequest(post_id:) -> { 233 + json.object([ 234 + #("type", json.string("post_view_request")), 235 + #("post_id", json.string(post_id)), 236 + ]) 237 + } 238 + } 239 + } 240 + 241 + pub fn ws_msg_from_client_decoder() -> decode.Decoder(WsMsgFromClient) { 242 + use variant <- decode.field("type", decode.string) 243 + case variant { 244 + "introduction" -> { 245 + use client_kind <- decode.field("client_kind", decode.string) 246 + use try_revive <- decode.optional_field( 247 + "try_revive", 248 + None, 249 + decode.optional(decode.string), 250 + ) 251 + decode.success(Introduction(client_kind:, try_revive:)) 252 + } 253 + "own_user_information_request" -> decode.success(OwnUserInformationRequest) 254 + "login_authentication_request" -> { 255 + use email_username <- decode.field("email_username", decode.string) 256 + use password <- decode.field("password", decode.string) 257 + decode.success(LoginAuthenticationRequest(email_username:, password:)) 258 + } 259 + "register_request" -> { 260 + use email <- decode.field("email", decode.string) 261 + use username <- decode.field("username", decode.string) 262 + use password <- decode.field("password", decode.string) 263 + decode.success(RegisterRequest(email:, username:, password:)) 264 + } 265 + "time_line_request" -> { 266 + use timeline_name <- decode.field("timeline_name", decode.string) 267 + use page <- decode.field("page", decode.int) 268 + decode.success(TimeLineRequest(timeline_name:, page:)) 269 + } 270 + "register_precheck" -> { 271 + use email <- decode.field("email", decode.string) 272 + use username <- decode.field("username", decode.string) 273 + use password <- decode.field("password", decode.string) 274 + decode.success(RegisterPrecheck(email:, username:, password:)) 275 + } 276 + "post_content_request" -> { 277 + use post_id <- decode.field("post_id", decode.string) 278 + decode.success(PostContentRequest(post_id:)) 279 + } 280 + _ -> decode.failure(OwnUserInformationRequest, "WsMsgFromClient") 281 + } 282 + }