+12
Gleam/web_server/.editorconfig
+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
+25
Gleam/web_server/README.md
+25
Gleam/web_server/README.md
···
1
+
# web_server
2
+
3
+
[](https://hex.pm/packages/web_server)
4
+
[](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
+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
+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
+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
+5
Gleam/web_server/src/app/error.gleam
+9
Gleam/web_server/src/app/history.gleam
+9
Gleam/web_server/src/app/history.gleam
+18
Gleam/web_server/src/app/person.gleam
+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
+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
+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
+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
+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
+12
Gleam/web_server/test/web_server_test.gleam
Gleam/web_server/transactions.sqlite3
Gleam/web_server/transactions.sqlite3
This is a binary file and will not be displayed.