Skip to content

Commit

Permalink
feat: improve routing ergonomics by narrowing path
Browse files Browse the repository at this point in the history
  • Loading branch information
soulsam480 committed Nov 2, 2024
1 parent fecc625 commit 7bcc90d
Show file tree
Hide file tree
Showing 15 changed files with 148 additions and 75 deletions.
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,6 @@ erl_crash.dump

*.sqlite
*.sqlite-*


.lasso-marks-tracker
Empty file removed .lasso-marks-tracker
Empty file.
18 changes: 14 additions & 4 deletions src/app/config.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,25 @@ import sqlight
/// this is app context. when user is authenticated, it has user
/// or db connection only
pub type Context {
Context(db: sqlight.Connection, user: Option(user.User))
Context(
db: sqlight.Connection,
user: Option(user.User),
scoped_segments: List(String),
)
}

pub fn acquire_context(db: sqlight.Connection, run: fn(Context) -> a) -> a {
run(Context(db, None))
run(Context(db, None, []))
}

pub fn set_user(ctx: Context, user: user.User) {
let Context(db, ..) = ctx
let Context(db, _, scope) = ctx

Context(db, Some(user))
Context(db, Some(user), scope)
}

pub fn scope_to(ctx: Context, scoped_segments: List(String)) {
let Context(db, user, ..) = ctx

Context(db, user, scoped_segments)
}
21 changes: 11 additions & 10 deletions src/app/controllers/sessions.gleam
Original file line number Diff line number Diff line change
@@ -1,11 +1,12 @@
import app/config
import app/db/models/user
import app/hooks/auth
import app/serializers/base
import app/serializers/user as user_serializer
import app/serializers/base_serializer
import app/serializers/user_serializer
import gleam/http
import gleam/list
import gleam/result
import gleam/string
import wisp.{type Request, type Response}

type LoginParam {
Expand Down Expand Up @@ -38,7 +39,7 @@ fn handle_login(req: Request, ctx: config.Context) -> Response {
case user_result {
Error(_) -> {
wisp.bad_request()
|> wisp.json_body(base.serialize_error(
|> wisp.json_body(base_serializer.serialize_error(
"Either email or password is incorrect!",
))
}
Expand All @@ -60,7 +61,7 @@ fn handle_login(req: Request, ctx: config.Context) -> Response {

False -> {
wisp.bad_request()
|> wisp.json_body(base.serialize_error(
|> wisp.json_body(base_serializer.serialize_error(
"Either email or password is incorrect !",
))
}
Expand Down Expand Up @@ -95,11 +96,10 @@ fn handle_register(req: Request, ctx: config.Context) {

case my_user {
Error(e) -> {
// TODO: add error specific response messages and codes
wisp.log_error(e.message)
wisp.log_error("DB: " <> string.inspect(e))

wisp.internal_server_error()
|> wisp.json_body(base.serialize_error("Error signing up!"))
|> wisp.json_body(base_serializer.serialize_error("Error signing up!"))
}

Ok(new_user) -> {
Expand All @@ -109,9 +109,10 @@ fn handle_register(req: Request, ctx: config.Context) {
}

pub fn controller(req: Request, ctx: config.Context) -> Response {
case wisp.path_segments(req), req.method {
["sessions", "login"], http.Post -> handle_login(req, ctx)
["sessions", "register"], http.Post -> handle_register(req, ctx)
case ctx.scoped_segments, req.method {
["login"], http.Post -> handle_login(req, ctx)
["register"], http.Post -> handle_register(req, ctx)

_, _ -> wisp.not_found()
}
}
20 changes: 20 additions & 0 deletions src/app/controllers/users.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import app/config
import app/serializers/user_serializer
import gleam/http
import gleam/option
import wisp

fn handle_current_user(_req: wisp.Request, ctx: config.Context) {
case ctx.user {
option.Some(user) -> wisp.ok() |> wisp.json_body(user_serializer.run(user))
option.None -> wisp.not_found()
}
}

pub fn controller(req: wisp.Request, ctx: config.Context) {
case ctx.scoped_segments, req.method {
["current"], http.Get -> handle_current_user(req, ctx)

_, _ -> wisp.not_found()
}
}
4 changes: 3 additions & 1 deletion src/app/db/connection.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,8 @@ pub fn run_query_with(
db_conn: sqlight.Connection,
model_decoder dcdr,
) {
io.println("---- query start ----")

let prp_stm = sqlite.cake_query_to_prepared_statement(query)
let sql = cake.get_sql(prp_stm) |> tap_println
let params = cake.get_params(prp_stm)
Expand All @@ -51,7 +53,7 @@ pub fn run_query_with(
NullParam -> sqlight.null()
}
})
|> tap_debug("Params: ")
|> tap_debug("with params:: ")

sql |> sqlight.query(on: db_conn, with: db_params, expecting: dcdr)
}
6 changes: 2 additions & 4 deletions src/app/db/models/user.gleam
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,8 @@ import cake/select
import cake/where
import decode/zero
import gleam/dynamic
import gleam/io
import gleam/list
import gleam/result
import gleam/string
import sqlight

pub type User {
Expand All @@ -24,8 +22,6 @@ pub type User {
}

fn decode_user(row: dynamic.Dynamic) {
row |> string.inspect |> io.println

let decoder = {
use id <- zero.field(0, zero.int)
use name <- zero.field(1, zero.string)
Expand Down Expand Up @@ -98,6 +94,8 @@ pub fn insert_user(
insertable user: InsertableUser,
connection conn: sqlight.Connection,
) {
// TODO: think we can get it done with one query, check that out

use _ <- result.try(
insert.from_records(
records: [user],
Expand Down
10 changes: 7 additions & 3 deletions src/app/hooks/auth.gleam
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import app/config
import app/db/models/user
import app/lib/response_helpers
import app/serializers/base
import app/serializers/base_serializer
import gleam/result
import wisp

Expand All @@ -21,13 +21,17 @@ pub fn hook(
wisp.get_cookie(req, cookie_name, wisp.Signed)
|> result.map_error(fn(_) {
response_helpers.unauthorized()
|> wisp.json_body(base.serialize_error("Invalid token or token not found"))
|> wisp.json_body(base_serializer.serialize_error(
"Invalid token or token not found",
))
})
|> result.try(fn(user_email) {
user.find_by_email(user_email, ctx.db)
|> result.map_error(fn(_) {
response_helpers.unauthorized()
|> wisp.json_body(base.serialize_error("Invalid token or token not found"))
|> wisp.json_body(base_serializer.serialize_error(
"Invalid token or token not found",
))
})
})
|> result.map(fn(user) { config.set_user(ctx, user) |> handle })
Expand Down
7 changes: 6 additions & 1 deletion src/app/router.gleam
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import app/config
import app/controllers/home
import app/controllers/sessions
import app/controllers/users
import app/hooks/hook
import wisp.{type Request, type Response}

Expand All @@ -10,7 +11,11 @@ pub fn handle_request(req: Request, ctx: config.Context) -> Response {
case wisp.path_segments(req) {
[] -> home.controller(req)

["sessions", ..] -> sessions.controller(req, ctx)
["sessions", ..session_segments] ->
sessions.controller(req, ctx |> config.scope_to(session_segments))

["auth", "users", ..user_segments] ->
users.controller(req, ctx |> config.scope_to(user_segments))

_ -> {
wisp.not_found()
Expand Down
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import app/db/models/user
import app/serializers/base
import app/serializers/base_serializer
import gleam/json

/// here we can do many things. taking inspirations from rails serializers
Expand All @@ -13,5 +13,5 @@ pub fn run(user: user.User) {
#("name", json.string(user.name)),
#("created_at", json.string(user.created_at)),
])
|> base.wrap
|> base_serializer.wrap
}
40 changes: 40 additions & 0 deletions test/controllers/home.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import app/router
import gleeunit
import gleeunit/should
import helpers/context
import wisp/testing

pub fn main() {
gleeunit.main()
}

pub fn get_home_page_test() {
let request = testing.get("/", [])
let response = router.handle_request(request, context.get_connection())

response.status
|> should.equal(200)

response.headers
|> should.equal([#("content-type", "text/html; charset=utf-8")])

response
|> testing.string_body
|> should.equal("Welcome to Okane")
}

pub fn post_home_page_test() {
let request = testing.post("/", [], "a body")
let response = router.handle_request(request, context.get_connection())

response.status
|> should.equal(405)
}

pub fn page_not_found_test() {
let request = testing.get("/nothing-here", [])
let response = router.handle_request(request, context.get_connection())

response.status
|> should.equal(404)
}
29 changes: 29 additions & 0 deletions test/controllers/sessions.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import app/router
import gleeunit
import gleeunit/should
import helpers/context
import wisp/testing

pub fn main() {
gleeunit.main()
}

pub fn sessions_register_missing_form_test() {
let response =
testing.post_form("/sessions/register", [], [])
|> router.handle_request(context.get_connection())

response.status |> should.equal(400)
}

pub fn sessions_register_form_test() {
let response =
testing.post_form("/sessions/register", [], [
#("name", "zoro"),
#("email", "[email protected]"),
#("password", "123"),
])
|> router.handle_request(context.get_connection())

response.status |> should.equal(201)
}
9 changes: 9 additions & 0 deletions test/helpers/context.gleam
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import app/config
import gleam/option
import sqlight

pub fn get_connection() -> config.Context {
use conn <- sqlight.with_connection(":memory:")

config.Context(conn, option.None, [])
}
52 changes: 2 additions & 50 deletions test/okane_test.gleam
Original file line number Diff line number Diff line change
@@ -1,58 +1,10 @@
import app/config
import app/router
import gleam/option
import gleeunit
import gleeunit/should
import sqlight
import wisp/testing

pub fn main() {
gleeunit.main()
}

fn get_connection() -> config.Context {
use conn <- sqlight.with_connection(":memory:")

config.Context(conn, option.None)
}

pub fn get_home_page_test() {
let request = testing.get("/", [])
let response = router.handle_request(request, get_connection())

response.status
|> should.equal(200)

response.headers
|> should.equal([#("content-type", "text/html")])

response
|> testing.string_body
|> should.equal("Welcome to Okane")
}

pub fn post_home_page_test() {
let request = testing.post("/", [], "a body")
let response = router.handle_request(request, get_connection())

response.status
|> should.equal(405)
}

pub fn page_not_found_test() {
let request = testing.get("/nothing-here", [])
let response = router.handle_request(request, get_connection())

response.status
|> should.equal(404)
}

pub fn page_session_show_test() {
let request = testing.get("/session", [])

let response = router.handle_request(request, get_connection())

response.status |> should.equal(200)

response |> testing.string_body |> should.equal("Welcome Okane")
pub fn base_test() {
10 |> should.equal(10)
}

0 comments on commit 7bcc90d

Please sign in to comment.