this repo has no description

:sparkles: (Gleam) initial web server

+12
Gleam/web_server/.editorconfig
··· 1 + # EditorConfig is awesome: https://editorconfig.org 2 + 3 + # top-most EditorConfig file 4 + root = true 5 + 6 + # Unix-style newlines with a newline ending every file 7 + [*] 8 + end_of_line = lf 9 + indent_size = 2 10 + indent_style = space 11 + insert_final_newline = true 12 + charset = utf-8
+4
Gleam/web_server/.gitignore
··· 1 + *.beam 2 + *.ez 3 + /build 4 + erl_crash.dump
+25
Gleam/web_server/README.md
··· 1 + # web_server 2 + 3 + [![Package Version](https://img.shields.io/hexpm/v/web_server)](https://hex.pm/packages/web_server) 4 + [![Hex Docs](https://img.shields.io/badge/hex-docs-ffaff3)](https://hexdocs.pm/web_server/) 5 + 6 + ```sh 7 + gleam add web_server 8 + ``` 9 + ```gleam 10 + import web_server 11 + 12 + pub fn main() { 13 + // TODO: An example of the project in use 14 + } 15 + ``` 16 + 17 + Further documentation can be found at <https://hexdocs.pm/web_server>. 18 + 19 + ## Development 20 + 21 + ```sh 22 + gleam run # Run the project 23 + gleam test # Run the tests 24 + gleam shell # Run an Erlang shell 25 + ```
+25
Gleam/web_server/gleam.toml
··· 1 + name = "web_server" 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 = "username", repo = "project" } 10 + # links = [{ title = "Website", href = "https://gleam.run" }] 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.34.0 and < 2.0.0" 17 + mist = ">= 1.2.0 and < 2.0.0" 18 + gleam_http = ">= 3.6.0 and < 4.0.0" 19 + gleam_erlang = ">= 0.25.0 and < 1.0.0" 20 + wisp = ">= 1.5.3 and < 2.0.0" 21 + gleam_json = ">= 2.3.0 and < 3.0.0" 22 + sqlight = ">= 1.0.1 and < 2.0.0" 23 + 24 + [dev-dependencies] 25 + gleeunit = ">= 1.0.0 and < 2.0.0"
+41
Gleam/web_server/manifest.toml
··· 1 + # This file was generated by Gleam 2 + # You typically do not need to edit this file 3 + 4 + packages = [ 5 + { name = "birl", version = "1.8.0", build_tools = ["gleam"], requirements = ["gleam_regexp", "gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "2AC7BA26F998E3DFADDB657148BD5DDFE966958AD4D6D6957DD0D22E5B56C400" }, 6 + { name = "directories", version = "1.1.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "BDA521A4EB9EE3A7894F0DC863797878E91FF5C7826F7084B2E731E208BDB076" }, 7 + { name = "envoy", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" }, 8 + { name = "esqlite", version = "0.8.9", build_tools = ["rebar3"], requirements = [], otp_app = "esqlite", source = "hex", outer_checksum = "465AE9AE28AE4192EA54C829FDC90C320447D439A9B2E10946621672FC6A6F8C" }, 9 + { name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" }, 10 + { name = "filepath", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "65F51013BCF78A603AFFD7992EF1CC6ECA96C74038EB48887F656DE44DBC1902" }, 11 + { name = "gleam_crypto", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "8AE56026B3E05EBB1F076778478A762E9EB62B31AEEB4285755452F397029D22" }, 12 + { name = "gleam_erlang", version = "0.34.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "0C38F2A128BAA0CEF17C3000BD2097EB80634E239CE31A86400C4416A5D0FDCC" }, 13 + { name = "gleam_http", version = "3.7.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "8A70D2F70BB7CFEB5DF048A2183FFBA91AF6D4CF5798504841744A16999E33D2" }, 14 + { name = "gleam_json", version = "2.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "C55C5C2B318533A8072D221C5E06E5A75711C129E420DD1CE463342106012E5D" }, 15 + { name = "gleam_otp", version = "0.16.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "50DA1539FC8E8FA09924EB36A67A2BBB0AD6B27BCDED5A7EF627057CF69D035E" }, 16 + { name = "gleam_regexp", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_regexp", source = "hex", outer_checksum = "7F5E0C0BBEB3C58E57C9CB05FA9002F970C85AD4A63BA1E55CBCB35C15809179" }, 17 + { name = "gleam_stdlib", version = "0.57.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86EFACDF6460B8681E82752C5490F9630EC0F138F88A037DDCB241799AA8811F" }, 18 + { name = "gleam_yielder", version = "1.1.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_yielder", source = "hex", outer_checksum = "8E4E4ECFA7982859F430C57F549200C7749823C106759F4A19A78AEA6687717A" }, 19 + { name = "gleeunit", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "0E6C83834BA65EDCAAF4FE4FB94AC697D9262D83E6F58A750D63C9F6C8A9D9FF" }, 20 + { name = "glisten", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "glisten", source = "hex", outer_checksum = "CF3A9383E9BA4A8CBAF2F7B799716290D02F2AC34E7A77556B49376B662B9314" }, 21 + { name = "gramps", version = "2.0.3", build_tools = ["gleam"], requirements = ["gleam_crypto", "gleam_erlang", "gleam_http", "gleam_stdlib"], otp_app = "gramps", source = "hex", outer_checksum = "3CCAA6E081225180D95C79679D383BBF51C8D1FDC1B84DA1DA444F628C373793" }, 22 + { name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" }, 23 + { name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" }, 24 + { name = "marceau", version = "1.3.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "2D1C27504BEF45005F5DFB18591F8610FB4BFA91744878210BDC464412EC44E9" }, 25 + { name = "mist", version = "1.2.0", build_tools = ["gleam"], requirements = ["birl", "gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten", "gramps", "hpack_erl", "logging"], otp_app = "mist", source = "hex", outer_checksum = "109B4D64E68C104CC23BB3CC5441ECD479DD7444889DA01113B75C6AF0F0E17B" }, 26 + { name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" }, 27 + { name = "ranger", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "gleam_yielder"], otp_app = "ranger", source = "hex", outer_checksum = "C8988E8F8CDBD3E7F4D8F2E663EF76490390899C2B2885A6432E942495B3E854" }, 28 + { name = "simplifile", version = "2.2.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0DFABEF7DC7A9E2FF4BB27B108034E60C81BEBFCB7AB816B9E7E18ED4503ACD8" }, 29 + { name = "sqlight", version = "1.0.1", build_tools = ["gleam"], requirements = ["esqlite", "gleam_stdlib"], otp_app = "sqlight", source = "hex", outer_checksum = "5435662352757841F9395C933404A223F39F73C73C4EC5E4418A7CB8AE85CDE6" }, 30 + { name = "wisp", version = "1.5.3", build_tools = ["gleam"], requirements = ["directories", "exception", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "DCD083E0ED3D30EA8CF2EECCA01A8108F7987D7BD908F527862E3D2725436444" }, 31 + ] 32 + 33 + [requirements] 34 + gleam_erlang = { version = ">= 0.25.0 and < 1.0.0" } 35 + gleam_http = { version = ">= 3.6.0 and < 4.0.0" } 36 + gleam_json = { version = ">= 2.3.0 and < 3.0.0" } 37 + gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" } 38 + gleeunit = { version = ">= 1.0.0 and < 2.0.0" } 39 + mist = { version = ">= 1.2.0 and < 2.0.0" } 40 + sqlight = { version = ">= 1.0.1 and < 2.0.0" } 41 + wisp = { version = ">= 1.5.3 and < 2.0.0" }
+138
Gleam/web_server/src/app/database.gleam
··· 1 + import app/error.{type AppError} 2 + import app/transaction 3 + import gleam/dynamic/decode 4 + import gleam/result 5 + import sqlight 6 + 7 + pub type Connection = 8 + sqlight.Connection 9 + 10 + pub fn with_connection(name: String, f: fn(sqlight.Connection) -> a) -> a { 11 + use db <- sqlight.with_connection(name) 12 + let assert Ok(_) = sqlight.exec("pragma foreign_keys = on;", db) 13 + f(db) 14 + } 15 + 16 + pub fn migrate_schema(db: Connection) -> Result(Nil, AppError) { 17 + sqlight.exec( 18 + " 19 + CREATE TABLE IF NOT EXISTS users ( 20 + id INTEGER PRIMARY KEY, 21 + limit_in_cents INTEGER NOT NULL, 22 + initial_balance INTEGER NOT NULL DEFAULT 0 23 + ); 24 + 25 + INSERT INTO users (limit_in_cents, initial_balance) 26 + VALUES (1000 * 100, 0), 27 + (800 * 100, 0), 28 + (10000 * 100, 0), 29 + (100000 * 100, 0), 30 + (5000 * 100, 0); 31 + 32 + CREATE TABLE IF NOT EXISTS history ( 33 + id INTEGER PRIMARY KEY, 34 + user_id INTEGER NOT NULL, 35 + value INTEGER NOT NULL, 36 + type CHAR(1) NOT NULL, 37 + description VARCHAR(10) NOT NULL, 38 + do_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP 39 + ); 40 + 41 + CREATE INDEX IF NOT EXISTS idx_history ON history (user_id); 42 + ", 43 + db, 44 + ) 45 + |> result.map_error(error.SqlightError) 46 + } 47 + 48 + pub fn get_user( 49 + db: Connection, 50 + user_id: String, 51 + ) -> Result(List(transaction.UserBalance), sqlight.Error) { 52 + let decoder = { 53 + use limit <- decode.field(0, decode.string) 54 + use balance <- decode.field(1, decode.int) 55 + 56 + transaction.UserBalance(limite: limit, saldo: balance) 57 + |> decode.success 58 + } 59 + 60 + sqlight.query( 61 + "SELECT limit_in_cents, initial_balance FROM users WHERE id = ?", 62 + on: db, 63 + with: [sqlight.text(user_id)], 64 + expecting: decoder, 65 + ) 66 + } 67 + 68 + pub fn save_in_history( 69 + db: Connection, 70 + user_id: String, 71 + history: transaction.Transaction, 72 + ) -> Result(List(Int), sqlight.Error) { 73 + let decoder = { 74 + use id <- dynamic.element(0) 75 + dynamic.int(id) 76 + } 77 + 78 + sqlight.query( 79 + "INSERT INTO history (user_id, value, type, description) 80 + VALUES (?, ?, ?, ?)", 81 + on: db, 82 + with: [ 83 + sqlight.text(user_id), 84 + sqlight.int(history.valor), 85 + sqlight.text(history.tipo), 86 + sqlight.text(history.descricao), 87 + ], 88 + expecting: decoder, 89 + ) 90 + } 91 + 92 + pub fn add_credit( 93 + db: Connection, 94 + user_id: String, 95 + history: transaction.Transaction, 96 + ) -> Result(List(Int), sqlight.Error) { 97 + let decoder = { 98 + use id <- dynamic.element(0) 99 + dynamic.int(id) 100 + } 101 + 102 + sqlight.query( 103 + "INSERT INTO history (user_id, value, type, description) 104 + VALUES (?, ?, ?, ?)", 105 + on: db, 106 + with: [ 107 + sqlight.text(user_id), 108 + sqlight.int(history.valor), 109 + sqlight.text(history.tipo), 110 + sqlight.text(history.descricao), 111 + ], 112 + expecting: decoder, 113 + ) 114 + } 115 + 116 + pub fn add_debit( 117 + db: Connection, 118 + user_id: String, 119 + history: transaction.Transaction, 120 + ) -> Result(List(Int), sqlight.Error) { 121 + let decoder = { 122 + use id <- dynamic.element(0) 123 + dynamic.int(id) 124 + } 125 + 126 + sqlight.query( 127 + "INSERT INTO history (user_id, value, type, description) 128 + VALUES (?, ?, ?, ?)", 129 + on: db, 130 + with: [ 131 + sqlight.text(user_id), 132 + sqlight.int(history.valor), 133 + sqlight.text(history.tipo), 134 + sqlight.text(history.descricao), 135 + ], 136 + expecting: decoder, 137 + ) 138 + }
+5
Gleam/web_server/src/app/error.gleam
··· 1 + import sqlight 2 + 3 + pub type AppError { 4 + SqlightError(sqlight.Error) 5 + }
+9
Gleam/web_server/src/app/history.gleam
··· 1 + pub type History { 2 + History( 3 + user_id: Int, 4 + value: Int, 5 + type_: String, 6 + description: String, 7 + do_at: String, 8 + ) 9 + }
+18
Gleam/web_server/src/app/person.gleam
··· 1 + import gleam/dynamic.{type Dynamic} 2 + 3 + pub type Person { 4 + Person(name: String, nickname: String, birthdate: String, stack: List(String)) 5 + } 6 + 7 + pub fn decode_person(json: Dynamic) -> Result(Person, dynamic.DecodeErrors) { 8 + let decoder = 9 + dynamic.decode4( 10 + Person, 11 + dynamic.field("name", dynamic.string), 12 + dynamic.field("nickname", dynamic.string), 13 + dynamic.field("birthdate", dynamic.string), 14 + dynamic.field("stack", dynamic.list(dynamic.string)), 15 + ) 16 + 17 + decoder(json) 18 + }
+90
Gleam/web_server/src/app/router.gleam
··· 1 + import app/database 2 + import app/person 3 + import app/transaction 4 + import app/web.{type Context} 5 + import gleam/http.{Get, Post} 6 + import gleam/io 7 + import gleam/json 8 + import gleam/result 9 + import wisp.{type Request, type Response} 10 + 11 + pub fn handle_request(req: Request, ctx: Context) -> Response { 12 + use req <- web.middleware(req) 13 + 14 + case wisp.path_segments(req) { 15 + ["clientes", id, "transacoes"] -> transactions(req, ctx, id) 16 + // ["clientes", id, "extrato"] -> transactions(req, ctx, id) 17 + [] -> home(req) 18 + ["pessoas"] -> people(req) 19 + 20 + //["person", id] -> show_person(req) 21 + _ -> wisp.not_found() 22 + } 23 + } 24 + 25 + fn transactions(req: Request, ctx: Context, user_id: String) -> Response { 26 + use <- wisp.require_method(req, Post) 27 + use json <- wisp.require_json(req) 28 + 29 + let result = { 30 + use transaction_data <- result.try(transaction.decode_transaction(json)) 31 + 32 + let assert Ok(user_balance) = database.get_user(ctx.db, user_id) 33 + 34 + io.debug(user_balance) 35 + 36 + // let assert Ok(_) = 37 + // database.save_in_history(ctx.db, user_id, transaction_data) 38 + 39 + // let assert Ok(_) = case transaction_data { 40 + // transaction.Transaction(tipo: "c", _, _) -> 41 + // database.add_credit(ctx.db, user_id, transaction_data) 42 + // transaction.Transaction(tipo: "d", _, _) -> 43 + // database.add_debit(ctx.db, user_id, transaction_data) 44 + // _ -> Error("") 45 + // } 46 + 47 + transaction.TransactionResponse(limite: 0, saldo: transaction_data.valor) 48 + |> transaction.transaction_response 49 + |> json.to_string_builder 50 + |> Ok 51 + } 52 + 53 + case result { 54 + Ok(json) -> wisp.json_response(json, 201) 55 + _ -> wisp.unprocessable_entity() 56 + } 57 + } 58 + 59 + fn home(req: Request) -> Response { 60 + use <- wisp.require_method(req, Get) 61 + 62 + json.object([#("hello", json.string("world"))]) 63 + |> json.to_string_builder 64 + |> wisp.json_response(200) 65 + } 66 + 67 + fn people(req: Request) -> Response { 68 + case req.method { 69 + // Get -> list_people() 70 + Post -> save_person(req) 71 + _ -> wisp.method_not_allowed([Get, Post]) 72 + } 73 + } 74 + 75 + fn save_person(req: Request) -> Response { 76 + use json <- wisp.require_json(req) 77 + 78 + let result = { 79 + use person <- result.try(person.decode_person(json)) 80 + 81 + json.object([#("id", json.string(person.name))]) 82 + |> json.to_string_builder 83 + |> Ok 84 + } 85 + 86 + case result { 87 + Ok(json) -> wisp.json_response(json, 201) 88 + _ -> wisp.unprocessable_entity() 89 + } 90 + }
+35
Gleam/web_server/src/app/transaction.gleam
··· 1 + import gleam/dynamic.{type Dynamic} 2 + import gleam/json 3 + 4 + pub type Transaction { 5 + Transaction(valor: Int, tipo: String, descricao: String) 6 + } 7 + 8 + pub fn decode_transaction( 9 + json: Dynamic, 10 + ) -> Result(Transaction, dynamic.DecodeErrors) { 11 + let decoder = 12 + dynamic.decode3( 13 + Transaction, 14 + dynamic.field("valor", dynamic.int), 15 + dynamic.field("tipo", dynamic.string), 16 + dynamic.field("descricao", dynamic.string), 17 + ) 18 + 19 + decoder(json) 20 + } 21 + 22 + pub type TransactionResponse { 23 + TransactionResponse(limite: Int, saldo: Int) 24 + } 25 + 26 + pub fn transaction_response(transaction: TransactionResponse) -> json.Json { 27 + json.object([ 28 + #("limite", json.int(transaction.limite)), 29 + #("saldo", json.int(transaction.saldo)), 30 + ]) 31 + } 32 + 33 + pub type UserBalance { 34 + UserBalance(limite: Int, saldo: Int) 35 + }
+50
Gleam/web_server/src/app/web.gleam
··· 1 + import app/database 2 + import gleam/bool 3 + import gleam/json 4 + import wisp 5 + 6 + pub type Context { 7 + Context(db: database.Connection) 8 + } 9 + 10 + pub fn middleware( 11 + req: wisp.Request, 12 + handle_request: fn(wisp.Request) -> wisp.Response, 13 + ) { 14 + let req = wisp.method_override(req) 15 + use <- wisp.log_request(req) 16 + use <- wisp.rescue_crashes 17 + use req <- wisp.handle_head(req) 18 + 19 + use <- default_responses 20 + 21 + handle_request(req) 22 + } 23 + 24 + fn default_responses(handle_request: fn() -> wisp.Response) -> wisp.Response { 25 + let response = handle_request() 26 + 27 + use <- bool.guard(when: response.body != wisp.Empty, return: response) 28 + 29 + case response.status { 30 + 404 | 405 -> 31 + json.object([#("msg", json.string("Not Found"))]) 32 + |> json.to_string_builder 33 + |> wisp.json_response(response.status) 34 + 35 + 400 | 422 -> 36 + json.object([#("msg", json.string("Bad request"))]) 37 + |> json.to_string_builder 38 + |> wisp.json_response(response.status) 39 + 40 + 500 -> 41 + json.object([#("msg", json.string("Internal server error"))]) 42 + |> json.to_string_builder 43 + |> wisp.json_response(response.status) 44 + 45 + _ -> 46 + json.object([#("msg", json.string("Something"))]) 47 + |> json.to_string_builder 48 + |> wisp.json_response(response.status) 49 + } 50 + }
+29
Gleam/web_server/src/web_server.gleam
··· 1 + import app/database 2 + import app/router 3 + import app/web.{Context} 4 + import gleam/erlang/process 5 + import mist 6 + import wisp 7 + 8 + const db_name = "transactions.sqlite3" 9 + 10 + pub fn main() { 11 + wisp.configure_logger() 12 + let secret_key_base = wisp.random_string(64) 13 + 14 + let assert Ok(_) = database.with_connection(db_name, database.migrate_schema) 15 + 16 + let handle_request = fn(req) { 17 + use db <- database.with_connection(db_name) 18 + let ctx = Context(db: db) 19 + router.handle_request(req, ctx) 20 + } 21 + 22 + let assert Ok(_) = 23 + wisp.mist_handler(handle_request, secret_key_base) 24 + |> mist.new 25 + |> mist.port(8000) 26 + |> mist.start_http 27 + 28 + process.sleep_forever() 29 + }
+12
Gleam/web_server/test/web_server_test.gleam
··· 1 + import gleeunit 2 + import gleeunit/should 3 + 4 + pub fn main() { 5 + gleeunit.main() 6 + } 7 + 8 + // gleeunit test functions end in `_test` 9 + pub fn hello_world_test() { 10 + 1 11 + |> should.equal(1) 12 + }
Gleam/web_server/transactions.sqlite3

This is a binary file and will not be displayed.