Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft #2

Merged
merged 24 commits into from
Oct 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -2,3 +2,4 @@
*.ez
/build
erl_crash.dump
.env
9 changes: 7 additions & 2 deletions gleam.toml
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
name = "my_web_app"
name = "app"
version = "1.0.0"

# Fill out these fields if you intend to generate HTML documentation or publish
Expand All @@ -14,9 +14,14 @@ version = "1.0.0"

[dependencies]
gleam_stdlib = ">= 0.34.0 and < 2.0.0"
mist = ">= 1.2.0 and < 2.0.0"
mist = ">= 3.0.0 and < 4.0.0"
gleam_http = ">= 3.6.0 and < 4.0.0"
gleam_erlang = ">= 0.25.0 and < 1.0.0"
glenvy = ">= 1.0.1 and < 2.0.0"
gluid = ">= 1.0.0 and < 2.0.0"
gleam_json = ">= 2.0.0 and < 3.0.0"
gleam_pgo = ">= 0.14.0 and < 1.0.0"
wisp = ">= 1.2.0 and < 2.0.0"

[dev-dependencies]
gleeunit = ">= 1.0.0 and < 2.0.0"
39 changes: 31 additions & 8 deletions manifest.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,24 +2,47 @@
# You typically do not need to edit this file

packages = [
{ name = "backoff", version = "1.1.6", build_tools = ["rebar3"], requirements = [], otp_app = "backoff", source = "hex", outer_checksum = "CF0CFFF8995FB20562F822E5CC47D8CCF664C5ECDC26A684CBE85C225F9D7C39" },
{ name = "birl", version = "1.7.1", build_tools = ["gleam"], requirements = ["gleam_stdlib", "ranger"], otp_app = "birl", source = "hex", outer_checksum = "5C66647D62BCB11FE327E7A6024907C4A17954EF22865FE0940B54A852446D01" },
{ name = "gleam_crypto", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "ADD058DEDE8F0341F1ADE3AAC492A224F15700829D9A3A3F9ADF370F875C51B7" },
{ name = "gleam_erlang", version = "0.25.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "054D571A7092D2A9727B3E5D183B7507DAB0DA41556EC9133606F09C15497373" },
{ name = "gleam_http", version = "3.6.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "8C07DF9DF8CC7F054C650839A51C30A7D3C26482AC241C899C1CEA86B22DBE51" },
{ name = "gleam_otp", version = "0.10.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "0B04FE915ACECE539B317F9652CAADBBC0F000184D586AAAF2D94C100945D72B" },
{ name = "gleam_stdlib", version = "0.39.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "2D7DE885A6EA7F1D5015D1698920C9BAF7241102836CE0C3837A4F160128A9C4" },
{ name = "directories", version = "1.1.0", build_tools = ["gleam"], requirements = ["envoy", "gleam_stdlib", "platform", "simplifile"], otp_app = "directories", source = "hex", outer_checksum = "BDA521A4EB9EE3A7894F0DC863797878E91FF5C7826F7084B2E731E208BDB076" },
{ name = "envoy", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "envoy", source = "hex", outer_checksum = "95FD059345AA982E89A0B6E2A3BF1CF43E17A7048DCD85B5B65D3B9E4E39D359" },
{ name = "exception", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "exception", source = "hex", outer_checksum = "F5580D584F16A20B7FCDCABF9E9BE9A2C1F6AC4F9176FA6DD0B63E3B20D450AA" },
{ name = "filepath", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "filepath", source = "hex", outer_checksum = "EFB6FF65C98B2A16378ABC3EE2B14124168C0CE5201553DE652E2644DCFDB594" },
{ name = "gleam_crypto", version = "1.4.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_crypto", source = "hex", outer_checksum = "8AE56026B3E05EBB1F076778478A762E9EB62B31AEEB4285755452F397029D22" },
{ name = "gleam_erlang", version = "0.27.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_erlang", source = "hex", outer_checksum = "DE468F676D71B313C6C8C5334425CFCF827837333F8AB47B64D8A6D7AA40185D" },
{ name = "gleam_http", version = "3.7.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_http", source = "hex", outer_checksum = "EA66440C2269F7CED0F6845E5BD0DB68095775D627FA709A841CA78A398D6D56" },
{ name = "gleam_json", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleam_json", source = "hex", outer_checksum = "CB10B0E7BF44282FB25162F1A24C1A025F6B93E777CCF238C4017E4EEF2CDE97" },
{ name = "gleam_otp", version = "0.12.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib"], otp_app = "gleam_otp", source = "hex", outer_checksum = "BFACC1513410DF5A1617169A9CD7EA334973AC71D860A17574BA7B2EADD89A6F" },
{ name = "gleam_pgo", version = "0.14.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "pgo"], otp_app = "gleam_pgo", source = "hex", outer_checksum = "12256A7F351E994A6E43AB67276DD8AACE752991C013F48A288C4A848F3A9758" },
{ name = "gleam_stdlib", version = "0.40.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "86606B75A600BBD05E539EB59FABC6E307EEEA7B1E5865AFB6D980A93BCB2181" },
{ name = "gleeunit", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "F7A7228925D3EE7D0813C922E062BFD6D7E9310F0BEE585D3A42F3307E3CFD13" },
{ name = "glisten", version = "2.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "glisten", source = "hex", outer_checksum = "CF3A9383E9BA4A8CBAF2F7B799716290D02F2AC34E7A77556B49376B662B9314" },
{ name = "glenvy", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_stdlib", "nibble", "simplifile"], otp_app = "glenvy", source = "hex", outer_checksum = "0F95149BF3052724BC2C76DF0235ED12C681683936294C84E2FB7853254FFB00" },
{ name = "glisten", version = "6.0.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib", "logging", "telemetry"], otp_app = "glisten", source = "hex", outer_checksum = "912132751031473CB38F454120124FFC96AF6B0EA33D92C9C90DB16327A2A972" },
{ name = "gluid", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gluid", source = "hex", outer_checksum = "9C0D64DC9A345B7DCC76A2EFDC1F3479E3C6A08A1FB81156E9CCED959F8DEB78" },
{ 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" },
{ name = "hpack_erl", version = "0.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "hpack", source = "hex", outer_checksum = "D6137D7079169D8C485C6962DFE261AF5B9EF60FBC557344511C1E65E3D95FB0" },
{ name = "logging", version = "1.3.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "1098FBF10B54B44C2C7FDF0B01C1253CAFACDACABEFB4B0D027803246753E06D" },
{ 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" },
{ name = "marceau", version = "1.3.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "2D1C27504BEF45005F5DFB18591F8610FB4BFA91744878210BDC464412EC44E9" },
{ name = "mist", version = "3.0.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 = "CDA1A74E768419235E16886463EC4722EFF4AB3F8D820A76EAD45D7C167D7282" },
{ name = "nibble", version = "1.1.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "nibble", source = "hex", outer_checksum = "67C6BEBC1AB6D771AB893B4A7B3E66C92668C6E7774C335FEFCD545B06435FE5" },
{ name = "opentelemetry_api", version = "1.4.0", build_tools = ["rebar3", "mix"], requirements = [], otp_app = "opentelemetry_api", source = "hex", outer_checksum = "3DFBBFAA2C2ED3121C5C483162836C4F9027DEF469C41578AF5EF32589FCFC58" },
{ name = "pg_types", version = "0.4.0", build_tools = ["rebar3"], requirements = [], otp_app = "pg_types", source = "hex", outer_checksum = "B02EFA785CAECECF9702C681C80A9CA12A39F9161A846CE17B01FB20AEEED7EB" },
{ name = "pgo", version = "0.14.0", build_tools = ["rebar3"], requirements = ["backoff", "opentelemetry_api", "pg_types"], otp_app = "pgo", source = "hex", outer_checksum = "71016C22599936E042DC0012EE4589D24C71427D266292F775EBF201D97DF9C9" },
{ name = "platform", version = "1.0.0", build_tools = ["gleam"], requirements = [], otp_app = "platform", source = "hex", outer_checksum = "8339420A95AD89AAC0F82F4C3DB8DD401041742D6C3F46132A8739F6AEB75391" },
{ name = "ranger", version = "1.2.0", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "ranger", source = "hex", outer_checksum = "1566C272B1D141B3BBA38B25CB761EF56E312E79EC0E2DFD4D3C19FB0CC1F98C" },
{ name = "simplifile", version = "2.2.0", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "0DFABEF7DC7A9E2FF4BB27B108034E60C81BEBFCB7AB816B9E7E18ED4503ACD8" },
{ name = "telemetry", version = "1.3.0", build_tools = ["rebar3"], requirements = [], otp_app = "telemetry", source = "hex", outer_checksum = "7015FC8919DBE63764F4B4B87A95B7C0996BD539E0D499BE6EC9D7F3875B79E6" },
{ name = "wisp", version = "1.2.0", 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 = "F71265D2F1DE11426535A2FA1DA3B11D2FFB783B116DF9496BC8C41983EBADB4" },
]

[requirements]
gleam_erlang = { version = ">= 0.25.0 and < 1.0.0" }
gleam_http = { version = ">= 3.6.0 and < 4.0.0" }
gleam_json = { version = ">= 2.0.0 and < 3.0.0" }
gleam_pgo = { version = ">= 0.14.0 and < 1.0.0" }
gleam_stdlib = { version = ">= 0.34.0 and < 2.0.0" }
gleeunit = { version = ">= 1.0.0 and < 2.0.0" }
mist = { version = ">= 1.2.0 and < 2.0.0" }
glenvy = { version = ">= 1.0.1 and < 2.0.0" }
gluid = { version = ">= 1.0.0 and < 2.0.0" }
mist = { version = ">= 3.0.0 and < 4.0.0" }
wisp = { version = ">= 1.2.0 and < 2.0.0" }
132 changes: 132 additions & 0 deletions src/app.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,132 @@
import gleam/http.{Get}
import gleam/json
import gleam/io
import wisp.{type Request, type Response}
import gleam/pgo
import gleam/erlang/process
import db/database
import db/config
import gleam/result
import mist
import wisp/wisp_mist

pub type AppError {
DatabaseError(String)
ConfigError(String)
ServerError(String)
}

pub type ApiResponse(a) {
ApiResponse(
data: a,
status: Int,
)
}

pub type RootData {
RootData(
message: String,
new_entry: String,
count: Int,
)
}

pub fn main() {
wisp.configure_logger()
let secret_key_base = wisp.random_string(64)

use db_conn <- result.map(setup_database())

let handler = fn(req) { handle_request(req, db_conn) }

let assert Ok(_) =
handler
|> wisp_mist.handler(secret_key_base)
|> mist.new
|> mist.port(3000)
|> mist.start_http

process.sleep_forever()
}

fn setup_database() -> Result(pgo.Connection, AppError) {
use db_conn <- result.try(
config.get_db_connection()
|> result.map_error(fn(_) { ConfigError("Database connection failed") })
)

io.println("✓ Server initialized with database connection")
Ok(db_conn)
}

pub fn handle_request(req: Request, conn: pgo.Connection) -> Response {
case req.method, wisp.path_segments(req) {
Get, [] -> handle_root(conn)
Get, ["status"] -> handle_status()
_, _ -> wisp.not_found()
}
}

fn handle_root(conn: pgo.Connection) -> Response {
case process_root_request(conn) {
Ok(ApiResponse(data: data, status: status)) -> {
wisp.json_response(
json.to_string_builder(encode_root_data(data)),
status,
)
}
Error(error) -> {
let #(status, message) = error_response(error)
wisp.json_response(
json.to_string_builder(encode_error(message)),
status,
)
}
}
}

fn process_root_request(conn: pgo.Connection) -> Result(ApiResponse(RootData), AppError) {
use #(uuid, count) <- result.try(
database.add_entry_and_get_count(conn)
|> result.map_error(DatabaseError)
)

let data = RootData(
message: "This is a simple Gleam application running on Zerops.io, each request adds an entry to the PostgreSQL database and returns a count. See the source repository (https://github.com/zeropsio/recipe-nodejs) for more information.",
new_entry: uuid,
count: count,
)

Ok(ApiResponse(data: data, status: 201))
}

fn handle_status() -> Response {
wisp.json_response(
json.to_string_builder(encode_status_response()),
200,
)
}

fn encode_root_data(data: RootData) -> json.Json {
json.object([
#("message", json.string(data.message)),
#("newEntry", json.string(data.new_entry)),
#("count", json.int(data.count)),
])
}

fn encode_status_response() -> json.Json {
json.object([#("status", json.string("UP"))])
}

fn encode_error(message: String) -> json.Json {
json.object([#("error", json.string(message))])
}

fn error_response(error: AppError) -> #(Int, String) {
case error {
ConfigError(msg) -> #(500, "Configuration error: " <> msg)
DatabaseError(msg) -> #(500, "Database error: " <> msg)
ServerError(msg) -> #(500, "Server error: " <> msg)
}
}
32 changes: 32 additions & 0 deletions src/db/config.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import gleam/pgo
import gleam/io
import glenvy/dotenv
import glenvy/env

pub fn get_db_connection() -> Result(pgo.Connection, Nil) {
case dotenv.load() {
Ok(_) -> io.println("Environment variables loaded successfully")
Error(_) -> io.println("Warning: Could not load .env file")
}

case env.get_string("DATABASE_URL") {
Ok(database_url) -> {
io.println("Attempting to connect to: " <> database_url)
case pgo.url_config(database_url) {
Ok(config) -> {
let connection = pgo.connect(config)
io.println("Successfully connected to database! ✨")
Ok(connection)
}
Error(_) -> {
io.println("Failed to parse database configuration")
Error(Nil)
}
}
}
Error(_) -> {
io.println("Failed to get DATABASE_URL from environment")
Error(Nil)
}
}
}
83 changes: 83 additions & 0 deletions src/db/database.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import gleam/string
import gleam/pgo
import gleam/io
import gleam/dynamic
import gleam/int
import gluid

pub fn add_entry_and_get_count(conn: pgo.Connection) -> Result(#(String, Int), String) {
io.println("Adding entry and getting count")
let uuid = gluid.guidv4()
io.println("Generated UUID: " <> uuid)

let insert_query = "INSERT INTO entries (uuid) VALUES ($1)"
io.println("Executing insert query with UUID")

case pgo.execute(
query: insert_query,
on: conn,
with: [pgo.text(uuid)],
expecting: dynamic.dynamic
) {
Ok(_) -> {
io.println("Entry added successfully")
case get_count(conn) {
Ok(count) -> Ok(#(uuid, count))
Error(err) -> Error(err)
}
}
Error(err) -> {
let error_message = query_error_to_string(err)
io.println("Failed to add entry: " <> error_message)
Error("Failed to add entry: " <> error_message)
}
}
}

fn get_count(conn: pgo.Connection) -> Result(Int, String) {
let count_query = "SELECT COUNT(*) FROM entries"
io.println("Executing count query: " <> count_query)
case pgo.execute(
query: count_query,
on: conn,
with: [],
expecting: fn(row) {
dynamic.element(0, dynamic.int)(row)
}
) {
Ok(result) -> {
case result.rows {
[count] -> {
io.println("Count retrieved successfully: " <> int.to_string(count))
Ok(count)
}
_ -> {
io.println("Unexpected result format")
Error("Unexpected result format")
}
}
}
Error(err) -> {
let error_message = query_error_to_string(err)
io.println("Failed to get count: " <> error_message)
Error("Failed to get count: " <> error_message)
}
}
}

fn query_error_to_string(error: pgo.QueryError) -> String {
case error {
pgo.ConstraintViolated(message: message, constraint: constraint, detail: detail) ->
"Constraint violated: " <> message <> " (Constraint: " <> constraint <> ", Detail: " <> detail <> ")"
pgo.PostgresqlError(code: code, name: name, message: message) ->
"PostgreSQL error: " <> message <> " (Code: " <> code <> ", Name: " <> name <> ")"
pgo.UnexpectedArgumentCount(expected: expected, got: got) ->
"Unexpected argument count. Expected: " <> int.to_string(expected) <> ", Got: " <> int.to_string(got)
pgo.UnexpectedArgumentType(expected: expected, got: got) ->
"Unexpected argument type. Expected: " <> expected <> ", Got: " <> got
pgo.UnexpectedResultType(errors) ->
"Unexpected result type: " <> string.inspect(errors)
pgo.ConnectionUnavailable ->
"Connection unavailable"
}
}
18 changes: 0 additions & 18 deletions src/my_web_app.gleam

This file was deleted.

12 changes: 0 additions & 12 deletions test/my_web_app_test.gleam

This file was deleted.

3 changes: 3 additions & 0 deletions zerops-project-import.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,3 +5,6 @@ services:
type: [email protected]
enableSubdomainAccess: true
buildFromGit: https://github.com/zeropsio/recipe-gleam
- hostname: db
type: postgresql@16
mode: NON_HA
10 changes: 6 additions & 4 deletions zerops.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ zerops:
build:
base: [email protected]
buildCommands:
- gleam export erlang-shipment
deployFiles: build/~erlang-shipment
- gleam build
deployFiles: /
run:
base: [email protected]
envVariables:
DATABASE_URL: ${db_connectionString}/${db_dbName}
ports:
- port: 8080
- port: 3000
httpSupport: true
start: ./erlang-shipment/entrypoint.sh run
start: gleam run