ambhtmx.crud / app.R
jrosell's picture
a veure el deploy
8fe56ae
if(!"rlang" %in% installed.packages()){
if(!interactive()) { stop("The package \"rlang\" is required.") }
cat("The package \"rlang\" is required.\n✖ Would you like to install it?\n\n1: Yes\n2: No\n\nSelection:")
if (readLines(n = 1) == "1"){
install.packages("rlang")
}
}
rlang::check_installed("remotes")
rlang::check_installed("ambhtmx", action = \(pkg, ... ) remotes::install_github("devOpifex/ambhtmx"))
rlang::check_installed("ambiorix", action = \(pkg, ... ) remotes::install_github("devOpifex/ambiorix"))
rlang::check_installed("scilis", action = \(pkg, ... ) remotes::install_github("devOpifex/scilis"))
rlang::check_installed("signaculum", action = \(pkg, ... ) remotes::install_github("devOpifex/signaculum"))
rlang::check_installed("tidyverse")
rlang::check_installed("zeallot")
rlang::check_installed("glue")
rlang::check_installed("htmltools")
rlang::check_installed("this.path")
options(
'ambiorix.host'=Sys.getenv('AMBHTMX_HOST'),
'ambiorix.port'=Sys.getenv('AMBHTMX_PORT')
)
# remotes::install_github("jrosell/ambhtmx", force = TRUE)
library(ambhtmx)
library(zeallot)
page_title <- "Password protected CRUD (Create, Read, Update, and Delete) example with ambhtmx"
render_index <- \() {
main <- NULL
tryCatch({
index <- p("Add your first item.")
item_rows <- items$read_rows()
if(nrow(item_rows) > 0) {
index <- item_rows |>
rowwise() |>
group_split() |>
map(\(item) {
tags$li(
tags$a(
item$name,
href = glue("/items/{item$id}"),
`hx-get`= glue("/items/{item$id}"),
`hx-target` = "#main",
`hx-swap` = "innerHTML"
)
)
})
}
main <- div(id = "page", style = "margin: 50px",
div(style ="float:right", id = "logout", button("Logout", onclick = "void(location.href='/logout')")),
h1(page_title),
div(id = "main", style = "margin-top: 20px", tagList(
h2("Index of items"),
index,
button(
"New item",
style = "margin-top:20px",
`hx-get` = "/items/new",
`hx-target` = "#main",
`hx-swap` = "innerHTML"
)
))
)
},
error = \(e) print(e)
)
return(main)
}
render_new <- \(req, res) {
errors <- process_error_get(req, res)
render_tags(tagList(
h2("New item"),
div(label("Name", p(input(name = "name")))),
div(label("Content", p(textarea(name = "content")))),
a(
"Go back",
href = "/",
style = "margin-right:20px",
`hx-confirm` = "Are you sure you want to go back?",
`hx-get` = "/items",
`hx-target` = "#page",
`hx-swap` = "outerHTML",
`hx-encoding` = "multipart/form-data"
),
button(
"Create",
style = "margin-top:20px",
`hx-post` = "/items",
`hx-target` = "#page",
`hx-swap` = "outerHTML",
`hx-include` = "[name='name'], [name='content']",
),
errors
))
}
render_row <- \(item) {
tags$div(
tags$strong(item$name),
tags$br(),
HTML(item$content)
)
}
#' Starting the app
counter <- 0
c(app, context, items) %<-%
ambhtmx_app(
dbname = getOption("ambiorix.dbname") %||% "items.sqlite",
host = getOption("ambiorix.host") %||% "127.0.0.1",
port = getOption("ambiorix.port")%||% "3000",
value = tibble(
id = character(1),
name = character(1),
content = character(1)
),
render_index = render_index,
render_row = render_row
)
#' Authentication feature with secret cookies and .Renviron variables
app$get("/login", \(req, res) {
process_login_get(req, res)
})
app$post("/login", \(req, res) {
process_login_post(req, res)
})
app$get("/logout", \(req, res) {
process_logout_get(req, res)
})
app$use(\(req, res){
process_loggedin_middleware(req, res)
})
#' Some CRUD operations examples
cat("\nBe sure is initially empty:\n")
walk(items$read_rows()$id, \(x) items$delete_row(id = x))
items$read_rows() |> print()
cat("\nAdd some items:\n")
tibble(name = "Elis elis", content = "Putxinelis.",) |>
items$add_row() -> first_id
tibble(name = "Que bombolles", content = "T\'empatolles.") |>
items$add_row() -> some_id
tibble(name = "Holi", content = "Guapi.") |>
items$add_row() -> last_id
items$read_rows() |> print()
cat("\nDelete last item:\n")
items$delete_row(id = last_id)
items$read_rows() |> print()
cat("\nUpdate first items:\n")
tibble(name = "First", content = "Hello in <span style='color:red'>red</span>.") |>
items$update_row(id = first_id)
items$read_rows() |> print()
cat("\nRender the first item:\n")
items$read_row(id = first_id) |>
items$read_row() |>
as.character() |>
cat()
cat("\nAdd an item with id 1:\n")
tibble(id = "1", name = "Quines postres", content = "Tant bones.") |>
items$add_row()
items$read_rows() |> print()
#' The main page
app$get("/", \(req, res){
if (!req$loggedin) {
return(res$redirect("/login", status = 302L))
}
html <- ""
tryCatch({
html <- render_page(
page_title = page_title,
main = items$render_index()
)
},
error = \(e) print(e)
)
res$send(html)
})
#' Read the index of the items
app$get("/items", \(req, res){
if (!req$loggedin) {
return(res$redirect("/login", status = 302L))
}
res$send(items$render_index())
})
#' New item form
app$get("/items/new", \(req, res){
if (!req$loggedin) {
return(res$redirect("/login", status = 302L))
}
tryCatch({
html <- render_new(req, res)
},
error = \(e) print(e)
)
res$send(html)
})
#' Show an existing item
app$get("/items/:id", \(req, res){
if (!req$loggedin) {
return(res$redirect("/login", status = 302L))
}
item_id <- req$params$id %||% ""
item <- items$read_row(id = item_id)
html <- render_tags(tagList(
h2("Show item details"),
items$render_row(item),
a(
"Go back",
href = "/",
style = "margin-right:20px",
`hx-get` = "/items",
`hx-target` = "#page",
`hx-swap` = "outerHTML",
),
a(
"Delete",
href = "/",
style = "color: red; margin-right:20px",
`hx-confirm` = "Are you sure you want to delete the item?",
`hx-delete` = glue("/items/{item$id}"),
`hx-target` = "#page",
`hx-swap` = "outerHTML",
`hx-encoding` = "multipart/form-data"
),
button(
"Edit",
style = "margin-top:20px",
`hx-get` = glue("/items/{item_id}/edit"),
`hx-target` = "#main",
`hx-swap` = "innerHTML"
)
))
res$send(html)
})
#' Edit item form
app$get("/items/:id/edit", \(req, res){
if (!req$loggedin) {
return(res$redirect("/login", status = 302L))
}
item_id <- req$params$id %||% ""
item <- items$read_row(id = item_id)
html <- render_tags(tagList(
h2("Edit item"),
input(type = "hidden", name = "id", value = item$id),
div(label("Name", p(input(name = "name", value = item$name)))),
div(HTML(glue('<textarea rows=5 name="content">{item$content}</textarea>'))),
a(
"Go back",
href = "/",
style = "margin-right:20px",
`hx-confirm` = "Are you sure you want to go back?",
`hx-get` = "/items",
`hx-target` = "#page",
`hx-swap` = "outerHTML",
`hx-encoding` = "multipart/form-data"
),
button(
"Update",
style = "margin-top:20px",
`hx-put` = glue("/items/{item$id}"),
`hx-target` = "#page",
`hx-swap` = "outerHTML",
`hx-include` = "[name='name'], [name='content']",
)
))
res$send(html)
})
#' Create a new item
app$post("/items", \(req, res){
if (!req$loggedin) {
return(res$redirect("/login", status = 302L))
}
params <- parse_multipart(req)
if (is.null(params[["name"]])) {
error_message <- "Name is required."
res$cookie(
name = "errors",
value = error_message
)
res$header("HX-Retarget", "#main")
res$header("HX-Reswap", "innerHTML")
print("Retarget amb error")
return(res$send(render_new(req, res)))
}
if (is.null(params[["content"]])) {
params[["content"]] = ""
}
tryCatch({
params |>
as_tibble() |>
items$add_row()
},
error = \(e) print(e)
)
res$send(items$render_index())
})
#' Update an existing item
app$put("/items/:id", \(req, res){
if (!req$loggedin) {
return(res$redirect("/login", status = 302L))
}
item_id <- req$params$id %||% ""
params <- parse_multipart(req) |>
as_tibble() |>
mutate(id = item_id)
item <- items$read_row(id = item_id)
tryCatch({
item |>
dplyr::rows_upsert(params, by = "id") |>
items$update_row()
},
error = \(e) print(e)
)
res$send(items$render_index())
})
#' Delete an existing item
app$delete("/items/:id", \(req, res){
if (!req$loggedin) {
return(res$redirect("/login", status = 302L))
}
item_id <- req$params$id %||% ""
items$delete_row(id = item_id)
res$send(items$render_index())
})
#' Start the app with all the previous defined routes
app$start()