jrosell commited on
Commit
8fe56ae
·
1 Parent(s): c3974c3

a veure el deploy

Browse files
Files changed (3) hide show
  1. Dockerfile +3 -3
  2. Makefile +6 -2
  3. app.R +227 -252
Dockerfile CHANGED
@@ -43,11 +43,11 @@ RUN set -eux; \
43
  rm -rf /var/lib/apt/lists/*;
44
 
45
  # Install R packages
46
- RUN install2.r --error -s --deps TRUE htmltools tidyverse zeallot rlang glue this.path DBI pool RSQLite remotes promises assertthat log here
47
  RUN Rscript -e "install.packages('b64', repos = c('https://extendr.r-universe.dev', getOption('repos')))"
48
  RUN Rscript -e "install.packages('uwu', repos = c('https://josiahparry.r-universe.dev', getOption('repos')))"
49
- RUN installGithub.r devOpifex/ambiorix devOpifex/scilis devOpifex/signaculum jrosell/ambhtmx
50
-
51
 
52
  # Prepare a user
53
  RUN useradd --create-home --shell /bin/bash user
 
43
  rm -rf /var/lib/apt/lists/*;
44
 
45
  # Install R packages
46
+ RUN install2.r --error -s --deps TRUE htmltools tibble dplyr purrr rlang glue this.path DBI pool RSQLite remotes promises assertthat log here zeallot dbplyr stringr tidyverse
47
  RUN Rscript -e "install.packages('b64', repos = c('https://extendr.r-universe.dev', getOption('repos')))"
48
  RUN Rscript -e "install.packages('uwu', repos = c('https://josiahparry.r-universe.dev', getOption('repos')))"
49
+ RUN --mount=type=secret,id=GITHUB_PAT GITHUB_PAT=$(cat /run/secrets/GITHUB_PAT) \
50
+ installGithub.r devOpifex/ambiorix devOpifex/scilis devOpifex/signaculum jrosell/ambhtmx
51
 
52
  # Prepare a user
53
  RUN useradd --create-home --shell /bin/bash user
Makefile CHANGED
@@ -14,7 +14,11 @@ clean:
14
  && echo "Done"
15
 
16
  build:
17
- docker build -f Dockerfile --no-cache --progress=plain -t ambhtmx-image . 2>&1 | tee build.log
18
 
19
  run:
20
- docker run --env-file=.Renviron -p 7860:7860 --name ambhtmx-container --rm ambhtmx-image
 
 
 
 
 
14
  && echo "Done"
15
 
16
  build:
17
+ docker build -f Dockerfile --no-cache --progress=plain -t ambhtmx-image . 2>&1 | tee build.log
18
 
19
  run:
20
+ docker run --env-file=.Renviron -p 7860:7860 --name ambhtmx-container --rm ambhtmx-image
21
+
22
+ secrets:
23
+ docker build --secret id=GITHUB_PAT,src=/run/secrets/GITHUB_PAT -f Dockerfile -t ambhtmx-image . \
24
+ && docker run --env-file=.Renviron -p 7860:7860 --name ambhtmx-container --rm ambhtmx-image
app.R CHANGED
@@ -7,8 +7,7 @@ if(!"rlang" %in% installed.packages()){
7
  }
8
 
9
  rlang::check_installed("remotes")
10
- remotes::install_github("jrosell/ambhtmx", force = TRUE)
11
- rlang::check_installed("ambiorix", action = \(pkg, ... ) remotes::install_github("devOpifex/ambiorix"))
12
  rlang::check_installed("ambiorix", action = \(pkg, ... ) remotes::install_github("devOpifex/ambiorix"))
13
  rlang::check_installed("scilis", action = \(pkg, ... ) remotes::install_github("devOpifex/scilis"))
14
  rlang::check_installed("signaculum", action = \(pkg, ... ) remotes::install_github("devOpifex/signaculum"))
@@ -22,24 +21,12 @@ options(
22
  'ambiorix.host'=Sys.getenv('AMBHTMX_HOST'),
23
  'ambiorix.port'=Sys.getenv('AMBHTMX_PORT')
24
  )
25
-
26
  library(ambhtmx)
27
- # devtools::load_all()
28
- library(ambiorix)
29
- library(tidyverse)
30
  library(zeallot)
31
- library(glue)
32
- library(htmltools)
33
- library(signaculum)
34
 
35
  page_title <- "Password protected CRUD (Create, Read, Update, and Delete) example with ambhtmx"
36
 
37
- live_path <- tryCatch(
38
- {this.path::this.path()},
39
- error = function(e) return("")
40
- )
41
- print(live_path)
42
-
43
  render_index <- \() {
44
  main <- NULL
45
  tryCatch({
@@ -61,7 +48,7 @@ render_index <- \() {
61
  )
62
  })
63
  }
64
- main <- withTags(div(id = "page", style = "margin: 50px",
65
  div(style ="float:right", id = "logout", button("Logout", onclick = "void(location.href='/logout')")),
66
  h1(page_title),
67
  div(id = "main", style = "margin-top: 20px", tagList(
@@ -75,13 +62,40 @@ render_index <- \() {
75
  `hx-swap` = "innerHTML"
76
  )
77
  ))
78
- ))
79
  },
80
  error = \(e) print(e)
81
  )
82
  return(main)
83
  }
84
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
85
 
86
  render_row <- \(item) {
87
  tags$div(
@@ -103,270 +117,231 @@ c(app, context, items) %<-%
103
  name = character(1),
104
  content = character(1)
105
  ),
106
- # live = live_path,
107
  render_index = render_index,
108
  render_row = render_row
109
  )
110
 
111
  #' Authentication feature with secret cookies and .Renviron variables
112
  app$get("/login", \(req, res) {
113
- process_login_get(
114
- req,
115
- res
116
- )
117
  })
118
- app$post("/login", \(req, res) {
119
- print(req$HTTP_HOST)
120
- process_login_post(
121
- req,
122
- res,
123
- user = Sys.getenv("AMBHTMX_USER"),
124
- password = Sys.getenv("AMBHTMX_PASSWORD"),
125
- )
126
  })
127
  app$get("/logout", \(req, res) {
128
- process_logout_get(
129
- req,
130
- res
131
- )
132
  })
133
  app$use(\(req, res){
134
- process_loggedin_middleware(
135
- req,
136
- res,
137
- user = Sys.getenv("AMBHTMX_USER")
138
- )
139
  })
140
 
141
  #' Some CRUD operations examples
142
  cat("\nBe sure is initially empty:\n")
143
- items$read_rows(context = context) |> print()
 
144
 
145
- # cat("\nNot, it should be empty:\n")
146
- # walk(items$read_rows()$id, \(x) items$delete_row(id = x))
147
- # items$read_rows() |> print()
 
 
 
 
 
148
 
149
- # cat("\nAdd some items:\n")
150
- # tibble(name = "Elis elis", content = "Putxinelis.",) |>
151
- # items$add_row() -> first_id
152
- # tibble(name = "Que bombolles", content = "T\'empatolles.") |>
153
- # items$add_row() -> some_id
154
- # tibble(name = "Holi", content = "Guapi.") |>
155
- # items$add_row() -> last_id
156
- # items$read_rows() |> print()
157
 
158
- # cat(glue("\nDelete last item id={last_id}:\n"))
159
- # items$delete_row(context = context, id = last_id)
160
- # cat(glue("\nDelete last item id={last_id}:\n"))
161
- # items$read_rows() |> print()
162
 
163
- # cat("\nUpdate first items:\n")
164
- # tibble(name = "First", content = "Hello in <span style='color:red'>red</span>.") |>
165
- # items$update_row(id = first_id)
166
- # items$read_rows() |> print()
 
167
 
168
- # cat("\nRender the first item:\n")
169
- # items$read_row(id = first_id) |>
170
- # items$read_row() |>
171
- # as.character() |>
172
- # cat()
173
 
174
- # cat("\nAdd an item with id 1:\n")
175
- # tibble(id = "1", name = "Quines postres", content = "Tant bones.") |>
176
- # items$add_row()
177
- # items$read_rows() |> print()
178
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
179
 
180
- # #' The main page
181
- # app$get("/", \(req, res){
182
- # # r <- process_loggedin_redirect(req, res)
183
- # # if(!is.null(r)) return(r)
184
- # if (!req$loggedin) {
185
- # return(res$redirect("/login", status = 302L))
186
- # }
187
- # html <- ""
188
- # tryCatch({
189
- # html <- render_page(
190
- # page_title = page_title,
191
- # main = items$render_index()
192
- # )
193
- # },
194
- # error = \(e) print(e)
195
- # )
196
- # res$send(html)
197
- # })
198
 
199
- # #' Read the index of the items
200
- # app$get("/items", \(req, res){
201
- # if (!req$loggedin) {
202
- # return(res$redirect("/login", status = 302L))
203
- # }
204
- # res$send(items$render_index())
205
- # })
 
 
 
 
 
206
 
207
- # #' New item form
208
- # app$get("/items/new", \(req, res){
209
- # if (!req$loggedin) {
210
- # return(res$redirect("/login", status = 302L))
211
- # }
212
- # errors <- process_error_get(req, res)
213
- # html <- render_tags(withTags(tagList(
214
- # h2("New item"),
215
- # div(label("Name", p(input(name = "name")))),
216
- # div(label("Content", p(textarea(name = "content")))),
217
- # a(
218
- # "Go back",
219
- # href = "/",
220
- # style = "margin-right:20px",
221
- # `hx-confirm` = "Are you sure you want to go back?",
222
- # `hx-get` = "/items",
223
- # `hx-target` = "#page",
224
- # `hx-swap` = "outerHTML",
225
- # `hx-encoding` = "multipart/form-data"
226
- # ),
227
- # button(
228
- # "Create",
229
- # style = "margin-top:20px",
230
- # `hx-post` = "/items",
231
- # `hx-target` = "#page",
232
- # `hx-swap` = "outerHTML",
233
- # `hx-include` = "[name='name'], [name='content']",
234
- # ),
235
- # errors
236
- # )))
237
- # res$send(html)
238
- # })
239
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
240
 
241
- # #' Show an existing item
242
- # app$get("/items/:id", \(req, res){
243
- # if (!req$loggedin) {
244
- # return(res$redirect("/login", status = 302L))
245
- # }
246
- # item_id <- req$params$id %||% ""
247
- # item <- items$read_row(id = item_id)
248
- # html <- render_tags(withTags(tagList(
249
- # h2("Show item details"),
250
- # items$render_row(item),
251
- # a(
252
- # "Go back",
253
- # href = "/",
254
- # style = "margin-right:20px",
255
- # `hx-get` = "/items",
256
- # `hx-target` = "#page",
257
- # `hx-swap` = "outerHTML",
258
- # ),
259
- # a(
260
- # "Delete",
261
- # href = "/",
262
- # style = "color: red; margin-right:20px",
263
- # `hx-confirm` = "Are you sure you want to delete the item?",
264
- # `hx-delete` = glue("/items/{item$id}"),
265
- # `hx-target` = "#page",
266
- # `hx-swap` = "outerHTML",
267
- # `hx-encoding` = "multipart/form-data"
268
- # ),
269
- # button(
270
- # "Edit",
271
- # style = "margin-top:20px",
272
- # `hx-get` = glue("/items/{item_id}/edit"),
273
- # `hx-target` = "#main",
274
- # `hx-swap` = "innerHTML"
275
- # )
276
- # )))
277
- # res$send(html)
278
- # })
279
 
280
- # #' Edit item form
281
- # app$get("/items/:id/edit", \(req, res){
282
- # if (!req$loggedin) {
283
- # return(res$redirect("/login", status = 302L))
284
- # }
285
- # item_id <- req$params$id %||% ""
286
- # item <- items$read_row(id = item_id)
287
- # html <- render_tags(withTags(tagList(
288
- # h2("Edit item"),
289
- # input(type = "hidden", name = "id", value = item$id),
290
- # div(label("Name", p(input(name = "name", value = item$name)))),
291
- # div(HTML(glue('<textarea rows=5 name="content">{item$content}</textarea>'))),
292
- # a(
293
- # "Go back",
294
- # href = "/",
295
- # style = "margin-right:20px",
296
- # `hx-confirm` = "Are you sure you want to go back?",
297
- # `hx-get` = "/items",
298
- # `hx-target` = "#page",
299
- # `hx-swap` = "outerHTML",
300
- # `hx-encoding` = "multipart/form-data"
301
- # ),
302
- # button(
303
- # "Update",
304
- # style = "margin-top:20px",
305
- # `hx-put` = glue("/items/{item$id}"),
306
- # `hx-target` = "#page",
307
- # `hx-swap` = "outerHTML",
308
- # `hx-include` = "[name='name'], [name='content']",
309
- # )
310
- # )))
311
- # res$send(html)
312
- # })
313
 
314
- # #' Create a new item
315
- # app$post("/items", \(req, res){
316
- # if (!req$loggedin) {
317
- # return(res$redirect("/login", status = 302L))
318
- # }
319
- # params <- parse_multipart(req)
320
- # if (is.null(params[["name"]])) {
321
- # return(process_error_post(
322
- # req,
323
- # res,
324
- # errors = "Name is required",
325
- # error_url = "/items/new"
326
- # ))
327
- # }
328
- # if (is.null(params[["content"]])) {
329
- # params[["content"]] = ""
330
- # }
331
- # tryCatch({
332
- # params |>
333
- # as_tibble() |>
334
- # items$add_row()
335
- # },
336
- # error = \(e) print(e)
337
- # )
338
- # res$send(items$render_index())
339
- # })
 
 
 
340
 
341
- # #' Update an existing item
342
- # app$put("/items/:id", \(req, res){
343
- # if (!req$loggedin) {
344
- # return(res$redirect("/login", status = 302L))
345
- # }
346
- # item_id <- req$params$id %||% ""
347
- # params <- parse_multipart(req) |>
348
- # as_tibble() |>
349
- # mutate(id = item_id)
350
- # item <- items$read_row(id = item_id)
351
- # tryCatch({
352
- # item |>
353
- # dplyr::rows_upsert(params, by = "id") |>
354
- # items$update_row()
355
- # },
356
- # error = \(e) print(e)
357
- # )
358
- # res$send(items$render_index())
359
- # })
360
 
361
- # #' Delete an existing item
362
- # app$delete("/items/:id", \(req, res){
363
- # if (!req$loggedin) {
364
- # return(res$redirect("/login", status = 302L))
365
- # }
366
- # item_id <- req$params$id %||% ""
367
- # items$delete_row(id = item_id)
368
- # res$send(items$render_index())
369
- # })
370
 
371
- # #' Start the app with all the previous defined routes
372
- # app$start(open = FALSE)
 
7
  }
8
 
9
  rlang::check_installed("remotes")
10
+ rlang::check_installed("ambhtmx", action = \(pkg, ... ) remotes::install_github("devOpifex/ambhtmx"))
 
11
  rlang::check_installed("ambiorix", action = \(pkg, ... ) remotes::install_github("devOpifex/ambiorix"))
12
  rlang::check_installed("scilis", action = \(pkg, ... ) remotes::install_github("devOpifex/scilis"))
13
  rlang::check_installed("signaculum", action = \(pkg, ... ) remotes::install_github("devOpifex/signaculum"))
 
21
  'ambiorix.host'=Sys.getenv('AMBHTMX_HOST'),
22
  'ambiorix.port'=Sys.getenv('AMBHTMX_PORT')
23
  )
24
+ # remotes::install_github("jrosell/ambhtmx", force = TRUE)
25
  library(ambhtmx)
 
 
 
26
  library(zeallot)
 
 
 
27
 
28
  page_title <- "Password protected CRUD (Create, Read, Update, and Delete) example with ambhtmx"
29
 
 
 
 
 
 
 
30
  render_index <- \() {
31
  main <- NULL
32
  tryCatch({
 
48
  )
49
  })
50
  }
51
+ main <- div(id = "page", style = "margin: 50px",
52
  div(style ="float:right", id = "logout", button("Logout", onclick = "void(location.href='/logout')")),
53
  h1(page_title),
54
  div(id = "main", style = "margin-top: 20px", tagList(
 
62
  `hx-swap` = "innerHTML"
63
  )
64
  ))
65
+ )
66
  },
67
  error = \(e) print(e)
68
  )
69
  return(main)
70
  }
71
 
72
+ render_new <- \(req, res) {
73
+ errors <- process_error_get(req, res)
74
+ render_tags(tagList(
75
+ h2("New item"),
76
+ div(label("Name", p(input(name = "name")))),
77
+ div(label("Content", p(textarea(name = "content")))),
78
+ a(
79
+ "Go back",
80
+ href = "/",
81
+ style = "margin-right:20px",
82
+ `hx-confirm` = "Are you sure you want to go back?",
83
+ `hx-get` = "/items",
84
+ `hx-target` = "#page",
85
+ `hx-swap` = "outerHTML",
86
+ `hx-encoding` = "multipart/form-data"
87
+ ),
88
+ button(
89
+ "Create",
90
+ style = "margin-top:20px",
91
+ `hx-post` = "/items",
92
+ `hx-target` = "#page",
93
+ `hx-swap` = "outerHTML",
94
+ `hx-include` = "[name='name'], [name='content']",
95
+ ),
96
+ errors
97
+ ))
98
+ }
99
 
100
  render_row <- \(item) {
101
  tags$div(
 
117
  name = character(1),
118
  content = character(1)
119
  ),
 
120
  render_index = render_index,
121
  render_row = render_row
122
  )
123
 
124
  #' Authentication feature with secret cookies and .Renviron variables
125
  app$get("/login", \(req, res) {
126
+ process_login_get(req, res)
 
 
 
127
  })
128
+ app$post("/login", \(req, res) {
129
+ process_login_post(req, res)
 
 
 
 
 
 
130
  })
131
  app$get("/logout", \(req, res) {
132
+ process_logout_get(req, res)
 
 
 
133
  })
134
  app$use(\(req, res){
135
+ process_loggedin_middleware(req, res)
 
 
 
 
136
  })
137
 
138
  #' Some CRUD operations examples
139
  cat("\nBe sure is initially empty:\n")
140
+ walk(items$read_rows()$id, \(x) items$delete_row(id = x))
141
+ items$read_rows() |> print()
142
 
143
+ cat("\nAdd some items:\n")
144
+ tibble(name = "Elis elis", content = "Putxinelis.",) |>
145
+ items$add_row() -> first_id
146
+ tibble(name = "Que bombolles", content = "T\'empatolles.") |>
147
+ items$add_row() -> some_id
148
+ tibble(name = "Holi", content = "Guapi.") |>
149
+ items$add_row() -> last_id
150
+ items$read_rows() |> print()
151
 
152
+ cat("\nDelete last item:\n")
153
+ items$delete_row(id = last_id)
154
+ items$read_rows() |> print()
 
 
 
 
 
155
 
156
+ cat("\nUpdate first items:\n")
157
+ tibble(name = "First", content = "Hello in <span style='color:red'>red</span>.") |>
158
+ items$update_row(id = first_id)
159
+ items$read_rows() |> print()
160
 
161
+ cat("\nRender the first item:\n")
162
+ items$read_row(id = first_id) |>
163
+ items$read_row() |>
164
+ as.character() |>
165
+ cat()
166
 
167
+ cat("\nAdd an item with id 1:\n")
168
+ tibble(id = "1", name = "Quines postres", content = "Tant bones.") |>
169
+ items$add_row()
170
+ items$read_rows() |> print()
 
171
 
 
 
 
 
172
 
173
+ #' The main page
174
+ app$get("/", \(req, res){
175
+ if (!req$loggedin) {
176
+ return(res$redirect("/login", status = 302L))
177
+ }
178
+ html <- ""
179
+ tryCatch({
180
+ html <- render_page(
181
+ page_title = page_title,
182
+ main = items$render_index()
183
+ )
184
+ },
185
+ error = \(e) print(e)
186
+ )
187
+ res$send(html)
188
+ })
189
 
190
+ #' Read the index of the items
191
+ app$get("/items", \(req, res){
192
+ if (!req$loggedin) {
193
+ return(res$redirect("/login", status = 302L))
194
+ }
195
+ res$send(items$render_index())
196
+ })
 
 
 
 
 
 
 
 
 
 
 
197
 
198
+ #' New item form
199
+ app$get("/items/new", \(req, res){
200
+ if (!req$loggedin) {
201
+ return(res$redirect("/login", status = 302L))
202
+ }
203
+ tryCatch({
204
+ html <- render_new(req, res)
205
+ },
206
+ error = \(e) print(e)
207
+ )
208
+ res$send(html)
209
+ })
210
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
211
 
212
+ #' Show an existing item
213
+ app$get("/items/:id", \(req, res){
214
+ if (!req$loggedin) {
215
+ return(res$redirect("/login", status = 302L))
216
+ }
217
+ item_id <- req$params$id %||% ""
218
+ item <- items$read_row(id = item_id)
219
+ html <- render_tags(tagList(
220
+ h2("Show item details"),
221
+ items$render_row(item),
222
+ a(
223
+ "Go back",
224
+ href = "/",
225
+ style = "margin-right:20px",
226
+ `hx-get` = "/items",
227
+ `hx-target` = "#page",
228
+ `hx-swap` = "outerHTML",
229
+ ),
230
+ a(
231
+ "Delete",
232
+ href = "/",
233
+ style = "color: red; margin-right:20px",
234
+ `hx-confirm` = "Are you sure you want to delete the item?",
235
+ `hx-delete` = glue("/items/{item$id}"),
236
+ `hx-target` = "#page",
237
+ `hx-swap` = "outerHTML",
238
+ `hx-encoding` = "multipart/form-data"
239
+ ),
240
+ button(
241
+ "Edit",
242
+ style = "margin-top:20px",
243
+ `hx-get` = glue("/items/{item_id}/edit"),
244
+ `hx-target` = "#main",
245
+ `hx-swap` = "innerHTML"
246
+ )
247
+ ))
248
+ res$send(html)
249
+ })
250
 
251
+ #' Edit item form
252
+ app$get("/items/:id/edit", \(req, res){
253
+ if (!req$loggedin) {
254
+ return(res$redirect("/login", status = 302L))
255
+ }
256
+ item_id <- req$params$id %||% ""
257
+ item <- items$read_row(id = item_id)
258
+ html <- render_tags(tagList(
259
+ h2("Edit item"),
260
+ input(type = "hidden", name = "id", value = item$id),
261
+ div(label("Name", p(input(name = "name", value = item$name)))),
262
+ div(HTML(glue('<textarea rows=5 name="content">{item$content}</textarea>'))),
263
+ a(
264
+ "Go back",
265
+ href = "/",
266
+ style = "margin-right:20px",
267
+ `hx-confirm` = "Are you sure you want to go back?",
268
+ `hx-get` = "/items",
269
+ `hx-target` = "#page",
270
+ `hx-swap` = "outerHTML",
271
+ `hx-encoding` = "multipart/form-data"
272
+ ),
273
+ button(
274
+ "Update",
275
+ style = "margin-top:20px",
276
+ `hx-put` = glue("/items/{item$id}"),
277
+ `hx-target` = "#page",
278
+ `hx-swap` = "outerHTML",
279
+ `hx-include` = "[name='name'], [name='content']",
280
+ )
281
+ ))
282
+ res$send(html)
283
+ })
 
 
 
 
 
284
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
285
 
286
+ #' Create a new item
287
+ app$post("/items", \(req, res){
288
+ if (!req$loggedin) {
289
+ return(res$redirect("/login", status = 302L))
290
+ }
291
+ params <- parse_multipart(req)
292
+ if (is.null(params[["name"]])) {
293
+ error_message <- "Name is required."
294
+ res$cookie(
295
+ name = "errors",
296
+ value = error_message
297
+ )
298
+ res$header("HX-Retarget", "#main")
299
+ res$header("HX-Reswap", "innerHTML")
300
+ print("Retarget amb error")
301
+ return(res$send(render_new(req, res)))
302
+ }
303
+ if (is.null(params[["content"]])) {
304
+ params[["content"]] = ""
305
+ }
306
+ tryCatch({
307
+ params |>
308
+ as_tibble() |>
309
+ items$add_row()
310
+ },
311
+ error = \(e) print(e)
312
+ )
313
+ res$send(items$render_index())
314
+ })
315
 
316
+ #' Update an existing item
317
+ app$put("/items/:id", \(req, res){
318
+ if (!req$loggedin) {
319
+ return(res$redirect("/login", status = 302L))
320
+ }
321
+ item_id <- req$params$id %||% ""
322
+ params <- parse_multipart(req) |>
323
+ as_tibble() |>
324
+ mutate(id = item_id)
325
+ item <- items$read_row(id = item_id)
326
+ tryCatch({
327
+ item |>
328
+ dplyr::rows_upsert(params, by = "id") |>
329
+ items$update_row()
330
+ },
331
+ error = \(e) print(e)
332
+ )
333
+ res$send(items$render_index())
334
+ })
335
 
336
+ #' Delete an existing item
337
+ app$delete("/items/:id", \(req, res){
338
+ if (!req$loggedin) {
339
+ return(res$redirect("/login", status = 302L))
340
+ }
341
+ item_id <- req$params$id %||% ""
342
+ items$delete_row(id = item_id)
343
+ res$send(items$render_index())
344
+ })
345
 
346
+ #' Start the app with all the previous defined routes
347
+ app$start()