From c7b8b6185a6bb0360863c304314368b6c25cfe84 Mon Sep 17 00:00:00 2001 From: Erika Rowland Date: Sat, 30 Mar 2024 23:43:37 -0700 Subject: [PATCH] it works --- .gitignore | 2 ++ README.md | 28 ++++++++++++++++ gleam.toml | 15 +++++++++ manifest.toml | 32 ++++++++++++++++++ src/ring.gleam | 17 ++++++++++ src/ring/router.gleam | 66 +++++++++++++++++++++++++++++++++++++ src/ring/web.gleam | 13 ++++++++ test/app_test.gleam | 75 +++++++++++++++++++++++++++++++++++++++++++ 8 files changed, 248 insertions(+) create mode 100644 .gitignore create mode 100644 README.md create mode 100644 gleam.toml create mode 100644 manifest.toml create mode 100644 src/ring.gleam create mode 100644 src/ring/router.gleam create mode 100644 src/ring/web.gleam create mode 100644 test/app_test.gleam diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..074ed42 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build/ +members.db diff --git a/README.md b/README.md new file mode 100644 index 0000000..0ba3e40 --- /dev/null +++ b/README.md @@ -0,0 +1,28 @@ +# Wisp Example: Routing + +```sh +gleam run # Run the server +gleam test # Run the tests +``` + +This example shows how to route requests to different handlers based on the +request path and method. + +This example is based off of the ["Hello, World!" example][hello], so read that +one first. The additions are detailed here and commented in the code. + +[hello]: https://github.com/lpil/wisp/tree/main/examples/00-hello-world + +### `app/router` module + +The `handle_request` function now pattern matches on the request and calls other +request handler functions depending on where the request should go. + +### `app_test` module + +Tests have been added for each of the routes. The `wisp/testing` module is used +to create different requests to test the application with. + +### Other files + +No changes have been made to the other files. diff --git a/gleam.toml b/gleam.toml new file mode 100644 index 0000000..f1178a5 --- /dev/null +++ b/gleam.toml @@ -0,0 +1,15 @@ +name = "ring" +version = "1.0.0" +description = "A Wisp example" +gleam = ">= 0.32.0" + +[dependencies] +gleam_stdlib = "~> 0.30" +gleam_erlang = "~> 0.23" +mist = "~> 0.14" +gleam_http = "~> 3.5" +wisp = "~> 0.14" +sqlight = "~> 0.9" + +[dev-dependencies] +gleeunit = "~> 1.0" diff --git a/manifest.toml b/manifest.toml new file mode 100644 index 0000000..6526abb --- /dev/null +++ b/manifest.toml @@ -0,0 +1,32 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ + { name = "esqlite", version = "0.8.6", build_tools = ["rebar3"], requirements = [], otp_app = "esqlite", source = "hex", outer_checksum = "607E45F4DA42601D8F530979417F57A4CD629AB49085891849302057E68EA188" }, + { 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.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_json", version = "1.0.0", build_tools = ["gleam"], requirements = ["gleam_stdlib", "thoas"], otp_app = "gleam_json", source = "hex", outer_checksum = "8B197DD5D578EA6AC2C0D4BDC634C71A5BCA8E7DB5F47091C263ECB411A60DF3" }, + { 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.36.0", build_tools = ["gleam"], requirements = [], otp_app = "gleam_stdlib", source = "hex", outer_checksum = "C0D14D807FEC6F8A08A7C9EF8DFDE6AE5C10E40E21325B2B29365965D82EB3D4" }, + { name = "gleeunit", version = "1.0.2", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "gleeunit", source = "hex", outer_checksum = "D364C87AFEB26BDB4FB8A5ABDE67D635DC9FA52D6AB68416044C35B096C6882D" }, + { name = "glisten", version = "0.11.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_otp", "gleam_stdlib"], otp_app = "glisten", source = "hex", outer_checksum = "73BC09C8487C2FFC0963BFAB33ED2F0D636FDFA43B966E65C1251CBAB8458099" }, + { name = "logging", version = "1.0.1", build_tools = ["gleam"], requirements = ["gleam_stdlib"], otp_app = "logging", source = "hex", outer_checksum = "82C112ED9B6C30C1772A6FE2613B94B13F62EA35F5869A2630D13948D297BD39" }, + { name = "marceau", version = "1.1.0", build_tools = ["gleam"], requirements = [], otp_app = "marceau", source = "hex", outer_checksum = "1AAD727A30BE0F95562C3403BB9B27C823797AD90037714255EEBF617B1CDA81" }, + { name = "mist", version = "0.17.0", build_tools = ["gleam"], requirements = ["gleam_erlang", "gleam_http", "gleam_otp", "gleam_stdlib", "glisten"], otp_app = "mist", source = "hex", outer_checksum = "DA8ACEE52C1E4892A75181B3166A4876D8CBC69D555E4770250BC84C80F75524" }, + { name = "simplifile", version = "1.6.1", build_tools = ["gleam"], requirements = ["filepath", "gleam_stdlib"], otp_app = "simplifile", source = "hex", outer_checksum = "B75D3C64E526D9D7EDEED5F3BA31DAAF5F2B4D80A4183FE17FDB02ED526E4E96" }, + { name = "sqlight", version = "0.9.0", build_tools = ["gleam"], requirements = ["esqlite", "gleam_stdlib"], otp_app = "sqlight", source = "hex", outer_checksum = "2D9C9BA420A5E7DCE7DB2DAAE4CAB0BE6218BEB48FD1531C583550B3D1316E94" }, + { name = "thoas", version = "0.4.1", build_tools = ["rebar3"], requirements = [], otp_app = "thoas", source = "hex", outer_checksum = "4918D50026C073C4AB1388437132C77A6F6F7C8AC43C60C13758CC0ADCE2134E" }, + { name = "wisp", version = "0.14.0", build_tools = ["gleam"], requirements = ["exception", "gleam_crypto", "gleam_erlang", "gleam_http", "gleam_json", "gleam_stdlib", "logging", "marceau", "mist", "simplifile"], otp_app = "wisp", source = "hex", outer_checksum = "9F5453AF1F9275E6F8707BC815D6A6A9DF41551921B16FBDBA52883773BAE684" }, +] + +[requirements] +gleam_erlang = { version = "~> 0.23" } +gleam_http = { version = "~> 3.5" } +gleam_stdlib = { version = "~> 0.30" } +gleeunit = { version = "~> 1.0" } +mist = { version = "~> 0.14" } +sqlight = { version = "~> 0.9"} +wisp = { version = "~> 0.14" } diff --git a/src/ring.gleam b/src/ring.gleam new file mode 100644 index 0000000..4002b8f --- /dev/null +++ b/src/ring.gleam @@ -0,0 +1,17 @@ +import gleam/erlang/process +import mist +import wisp +import ring/router + +pub fn main() { + wisp.configure_logger() + let secret_key_base = wisp.random_string(64) + + let assert Ok(_) = + wisp.mist_handler(router.handle_request, secret_key_base) + |> mist.new + |> mist.port(8000) + |> mist.start_http + + process.sleep_forever() +} diff --git a/src/ring/router.gleam b/src/ring/router.gleam new file mode 100644 index 0000000..682a0b6 --- /dev/null +++ b/src/ring/router.gleam @@ -0,0 +1,66 @@ +import wisp.{type Request, type Response} +import gleam/string_builder +import gleam/http.{Get} +import gleam/io +import gleam/dynamic +import ring/web +import sqlight + +pub fn handle_request(req: Request) -> Response { + use req <- web.middleware(req) + + case wisp.path_segments(req) { + [] -> home_page(req) + + [hash, "previous"] -> handle_previous(hash) + [hash, "next"] -> handle_next(hash) + + _ -> wisp.not_found() + } +} + +type Row { + Row(String) +} + +fn handle_previous(hash) { + let db_path = "members.db" + use conn <- sqlight.with_connection(db_path) + let previous = + sqlight.query( + "select previous from ring where hash = ?", + on: conn, + with: [sqlight.text(hash)], + expecting: dynamic.decode1(Row, dynamic.element(0, dynamic.string)), + ) + case previous { + Ok([Row(previous_link)]) -> wisp.redirect(previous_link) + _ -> wisp.not_found() + } +} + +fn handle_next(hash) { + let db_path = "members.db" + use conn <- sqlight.with_connection(db_path) + let next = + sqlight.query( + "select next from ring where hash = ?", + on: conn, + with: [sqlight.text(hash)], + expecting: dynamic.decode1(Row, dynamic.element(0, dynamic.string)), + ) + case next { + Ok([Row(next_link)]) -> wisp.redirect(next_link) + _ -> wisp.not_found() + } +} + +fn home_page(req: Request) -> Response { + // The home page can only be accessed via GET requests, so this middleware is + // used to return a 405: Method Not Allowed response for all other methods. + use <- wisp.require_method(req, Get) + + let html = string_builder.from_string("Hello, Joe!") + wisp.ok() + |> wisp.html_body(html) +} diff --git a/src/ring/web.gleam b/src/ring/web.gleam new file mode 100644 index 0000000..adb6932 --- /dev/null +++ b/src/ring/web.gleam @@ -0,0 +1,13 @@ +import wisp + +pub fn middleware( + req: wisp.Request, + handle_request: fn(wisp.Request) -> wisp.Response, +) -> wisp.Response { + let req = wisp.method_override(req) + use <- wisp.log_request(req) + use <- wisp.rescue_crashes + use req <- wisp.handle_head(req) + + handle_request(req) +} diff --git a/test/app_test.gleam b/test/app_test.gleam new file mode 100644 index 0000000..26dc407 --- /dev/null +++ b/test/app_test.gleam @@ -0,0 +1,75 @@ +import gleeunit +import gleeunit/should +import wisp/testing +import app/router + +pub fn main() { + gleeunit.main() +} + +pub fn get_home_page_test() { + let request = testing.get("/", []) + let response = router.handle_request(request) + + response.status + |> should.equal(200) + + response.headers + |> should.equal([#("content-type", "text/html")]) + + response + |> testing.string_body + |> should.equal("Hello, Joe!") +} + +pub fn post_home_page_test() { + let request = testing.post("/", [], "a body") + let response = router.handle_request(request) + response.status + |> should.equal(405) +} + +pub fn page_not_found_test() { + let request = testing.get("/nothing-here", []) + let response = router.handle_request(request) + response.status + |> should.equal(404) +} + +pub fn get_comments_test() { + let request = testing.get("/comments", []) + let response = router.handle_request(request) + response.status + |> should.equal(200) +} + +pub fn post_comments_test() { + let request = testing.post("/comments", [], "") + let response = router.handle_request(request) + response.status + |> should.equal(201) +} + +pub fn delete_comments_test() { + let request = testing.delete("/comments", [], "") + let response = router.handle_request(request) + response.status + |> should.equal(405) +} + +pub fn get_comment_test() { + let request = testing.get("/comments/123", []) + let response = router.handle_request(request) + response.status + |> should.equal(200) + response + |> testing.string_body + |> should.equal("Comment with id 123") +} + +pub fn delete_comment_test() { + let request = testing.delete("/comments/123", [], "") + let response = router.handle_request(request) + response.status + |> should.equal(405) +}