Web-based stream overlay manager
at main 3.4 kB view raw
1import clover/counter 2import clover/data 3import clover/pages/index 4import clover/pages/room 5import ewe 6import formal/form 7import gleam/erlang/application 8import gleam/erlang/process 9import gleam/http 10import gleam/http/cookie 11import gleam/http/request 12import gleam/http/response 13import gleam/int 14import gleam/list 15import gleam/option 16import gleam/string 17import gleam/uri 18import logging 19import lustre 20 21pub fn main() { 22 logging.configure() 23 logging.set_level(logging.Info) 24 25 let counter = counter.component() 26 let assert Ok(component) = lustre.start_server_component(counter, Nil) 27 28 let assert Ok(_) = 29 ewe.new(handler(_, component)) 30 |> ewe.bind_all() 31 |> ewe.listening(1234) 32 |> ewe.start() 33 34 process.sleep_forever() 35} 36 37fn log_req(req: ewe.Request, f: fn(ewe.Request) -> ewe.Response) -> ewe.Response { 38 let resp = f(req) 39 logging.log(logging.Info, int.to_string(resp.status) <> " " <> req.path) 40 resp 41} 42 43fn handler( 44 req: ewe.Request, 45 counter: lustre.Runtime(counter.Msg), 46) -> ewe.Response { 47 use req <- log_req(req) 48 49 let pass = "1234" 50 let authed = authenticated(req, pass) 51 52 case request.path_segments(req) { 53 [] -> data.page(index.view()) 54 ["join"] -> 55 case list.key_find(req.headers, "content-type") { 56 Ok("application/x-www-form-urlencoded") 57 | Ok("application/x-www-form-urlencoded; " <> _) -> { 58 use body <- data.require_string_body(req) 59 case uri.parse_query(body) { 60 Error(_) -> data.bad_request("Invalid form encoding") 61 Ok(pairs) -> { 62 let pairs = sort_keys(pairs) 63 let form = index.room_form() |> form.add_values(pairs) 64 case form.run(form) { 65 Ok(data) if data.password == pass -> 66 data.redirect("/room/") 67 |> response.set_cookie( 68 "password", 69 data.password, 70 cookie.defaults(http.Https), 71 ) 72 _ -> data.redirect("/") 73 } 74 } 75 } 76 } 77 _ -> data.redirect("/") 78 } 79 ["room"] if authed -> data.page(room.view()) 80 ["room", "html"] -> data.page(room.view_html()) 81 ["lustre", "runtime.mjs"] -> serve_runtime() 82 ["ws"] if authed -> serve_counter(req, counter) 83 _ -> data.not_found() 84 } 85} 86 87fn authenticated(req: ewe.Request, password: String) -> Bool { 88 let cookies = request.get_cookies(req) 89 list.contains(cookies, #("password", password)) 90} 91 92fn sort_keys(pairs: List(#(String, t))) -> List(#(String, t)) { 93 list.sort(pairs, fn(a, b) { string.compare(a.0, b.0) }) 94} 95 96fn serve_runtime() -> response.Response(ewe.ResponseBody) { 97 let assert Ok(lustre_priv) = application.priv_directory("lustre") 98 let file_path = lustre_priv <> "/static/lustre-server-component.mjs" 99 100 case ewe.file(file_path, offset: option.None, limit: option.None) { 101 Ok(f) -> 102 response.new(200) 103 |> response.prepend_header( 104 "content-type", 105 "application/javascript; charset=utf-8", 106 ) 107 |> response.set_body(f) 108 Error(_) -> data.not_found() 109 } 110} 111 112fn serve_counter( 113 req: request.Request(ewe.Connection), 114 counter: lustre.Runtime(counter.Msg), 115) -> response.Response(ewe.ResponseBody) { 116 let on_init = fn(a, b) { counter.init_socket(a, b, counter) } 117 118 req 119 |> ewe.upgrade_websocket( 120 on_init:, 121 handler: counter.loop_socket, 122 on_close: counter.close_socket, 123 ) 124}