diff --git a/09_goals/backend/.Renviron b/09_goals/.Renviron similarity index 100% rename from 09_goals/backend/.Renviron rename to 09_goals/.Renviron diff --git a/09_goals/backend/.Rprofile b/09_goals/.Rprofile similarity index 100% rename from 09_goals/backend/.Rprofile rename to 09_goals/.Rprofile diff --git a/09_goals/backend/.gitignore b/09_goals/.gitignore similarity index 100% rename from 09_goals/backend/.gitignore rename to 09_goals/.gitignore diff --git a/09_goals/README.md b/09_goals/README.md index d014260..5b57e38 100644 --- a/09_goals/README.md +++ b/09_goals/README.md @@ -18,10 +18,10 @@ Here's what's covered: ## Run API -1. `cd` into the `09_goals/backend/` dir: +1. `cd` into the `09_goals/` dir: ```bash - cd 09_goals/backend/ + cd 09_goals/ ``` 1. Fire up R and restore package dependencies: diff --git a/09_goals/backend/config/db.R b/09_goals/config/db.R similarity index 100% rename from 09_goals/backend/config/db.R rename to 09_goals/config/db.R diff --git a/09_goals/backend/controllers/goal_controller.R b/09_goals/controllers/goal_controller.R similarity index 100% rename from 09_goals/backend/controllers/goal_controller.R rename to 09_goals/controllers/goal_controller.R diff --git a/09_goals/backend/controllers/user_controller.R b/09_goals/controllers/user_controller.R similarity index 100% rename from 09_goals/backend/controllers/user_controller.R rename to 09_goals/controllers/user_controller.R diff --git a/09_goals/backend/helpers/generate_token.R b/09_goals/helpers/generate_token.R similarity index 100% rename from 09_goals/backend/helpers/generate_token.R rename to 09_goals/helpers/generate_token.R diff --git a/09_goals/backend/helpers/get_jwt_secret.R b/09_goals/helpers/get_jwt_secret.R similarity index 100% rename from 09_goals/backend/helpers/get_jwt_secret.R rename to 09_goals/helpers/get_jwt_secret.R diff --git a/09_goals/backend/helpers/get_port.R b/09_goals/helpers/get_port.R similarity index 100% rename from 09_goals/backend/helpers/get_port.R rename to 09_goals/helpers/get_port.R diff --git a/09_goals/backend/helpers/insert.R b/09_goals/helpers/insert.R similarity index 100% rename from 09_goals/backend/helpers/insert.R rename to 09_goals/helpers/insert.R diff --git a/09_goals/backend/helpers/mongo_query.R b/09_goals/helpers/mongo_query.R similarity index 100% rename from 09_goals/backend/helpers/mongo_query.R rename to 09_goals/helpers/mongo_query.R diff --git a/09_goals/backend/helpers/operators.R b/09_goals/helpers/operators.R similarity index 100% rename from 09_goals/backend/helpers/operators.R rename to 09_goals/helpers/operators.R diff --git a/09_goals/backend/helpers/to_json.R b/09_goals/helpers/to_json.R similarity index 100% rename from 09_goals/backend/helpers/to_json.R rename to 09_goals/helpers/to_json.R diff --git a/09_goals/backend/middleware/auth_middleware.R b/09_goals/middleware/auth_middleware.R similarity index 100% rename from 09_goals/backend/middleware/auth_middleware.R rename to 09_goals/middleware/auth_middleware.R diff --git a/09_goals/backend/middleware/error_middleware.R b/09_goals/middleware/error_middleware.R similarity index 100% rename from 09_goals/backend/middleware/error_middleware.R rename to 09_goals/middleware/error_middleware.R diff --git a/09_goals/backend/models/goal_model.R b/09_goals/models/goal_model.R similarity index 100% rename from 09_goals/backend/models/goal_model.R rename to 09_goals/models/goal_model.R diff --git a/09_goals/backend/models/user_model.R b/09_goals/models/user_model.R similarity index 100% rename from 09_goals/backend/models/user_model.R rename to 09_goals/models/user_model.R diff --git a/09_goals/backend/renv.lock b/09_goals/renv.lock similarity index 100% rename from 09_goals/backend/renv.lock rename to 09_goals/renv.lock diff --git a/09_goals/backend/renv/.gitignore b/09_goals/renv/.gitignore similarity index 100% rename from 09_goals/backend/renv/.gitignore rename to 09_goals/renv/.gitignore diff --git a/09_goals/backend/renv/activate.R b/09_goals/renv/activate.R similarity index 100% rename from 09_goals/backend/renv/activate.R rename to 09_goals/renv/activate.R diff --git a/09_goals/backend/renv/settings.json b/09_goals/renv/settings.json similarity index 100% rename from 09_goals/backend/renv/settings.json rename to 09_goals/renv/settings.json diff --git a/09_goals/backend/routes/goal_routes.R b/09_goals/routes/goal_routes.R similarity index 100% rename from 09_goals/backend/routes/goal_routes.R rename to 09_goals/routes/goal_routes.R diff --git a/09_goals/backend/routes/user_routes.R b/09_goals/routes/user_routes.R similarity index 100% rename from 09_goals/backend/routes/user_routes.R rename to 09_goals/routes/user_routes.R diff --git a/09_goals/backend/server.R b/09_goals/server.R similarity index 100% rename from 09_goals/backend/server.R rename to 09_goals/server.R diff --git a/docs/index.html b/docs/index.html index 20133d9..f27c345 100644 --- a/docs/index.html +++ b/docs/index.html @@ -425,7 +425,7 @@
-
+
-
+
diff --git a/docs/posts/09_goals/index.html b/docs/posts/09_goals/index.html index 87abe26..af9360e 100644 --- a/docs/posts/09_goals/index.html +++ b/docs/posts/09_goals/index.html @@ -189,6 +189,11 @@

On this page

  • Prerequisites
  • Run app
  • Explanation
  • +
  • Requests to the API +
  • Live reloading
  • @@ -200,18 +205,18 @@

    On this page

    -
    -

    Prerequisites

    +
    +

    Prerequisites

    -
    -

    Run app

    +
    +

    Run app

      -
    1. cd into the 09_goals/backend/ dir:

      -
      cd 09_goals/backend/
    2. +
    3. cd into the 09_goals/ dir:

      +
      cd 09_goals/
    4. Fire up R:

      R
    5. Restore package dependencies:

      @@ -221,8 +226,8 @@

      Run app

      Rscript server.R
    -
    -

    Explanation

    +
    +

    Explanation

    This app starts a server and listens on port 5000 for connections.

    In this example, we build a CRUD application backend: Goals.

    Here are the defined routes:

    @@ -252,8 +257,303 @@

    Explanation

    -
    -

    Live reloading

    +
    +

    Requests to the API

    +

    Let’s explore how you can send requests to the API. We’ll do so from another R session.

    +

    Be sure to import the required functions first:

    +
    box::use(
    +  httr2[
    +    request,
    +    req_method,
    +    req_perform,
    +    req_url_path,
    +    last_response,
    +    resp_body_json,
    +    req_body_multipart,
    +    req_auth_bearer_token,
    +  ]
    +)
    +
    +

    /api/users*

    +
    + +
    +
    +

    Since the API requires JWT auth, you first need to create an account. To do that, make a POST request to /api/users:

    +
    base_url <- "http://127.0.0.1:5000"
    +
    +# registration details:
    +user_details <- list(
    +  name = "mwavu",
    +  email = "mwavu@mail.com",
    +  password = "test123"
    +)
    +
    +req <- request(base_url = base_url) |>
    +  req_url_path("/api/users") |>
    +  req_body_multipart(!!!user_details)
    +
    +# use `tryCatch()` in case an error occurs while performing the request:
    +tryCatch(
    +  expr = req |>
    +    req_perform() |>
    +    resp_body_json(),
    +  error = \(e) {
    +    print("An error occurred!")
    +    error <- last_response() |> resp_body_json()
    +    print(error)
    +  }
    +)
    +

    If that’s successful, you get back a list containing a named list of 4 items: - _id - name - email - token: A JWT token

    +

    Here is an example:

    +
    [[1]]
    +[[1]]$`_id`
    +[1] "669c6ee4ddfa7be6a10f07e1"
    +
    +[[1]]$name
    +[1] "mwavu"
    +
    +[[1]]$email
    +[1] "mwavu@mail.com"
    +
    +[[1]]$token
    +[1] "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY0MzcsImlhdCI6MTcyMTUyODAzNywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.ebhkSXwJChW4iqLDRRoLOGrX3hb7NevRW4vZncjm5MY"
    +
    +
    +

    To login a user, we make a POST request to /api/users/login:

    +
    base_url <- "http://127.0.0.1:5000"
    +
    +# login details:
    +user_details <- list(
    +  email = "mwavu@mail.com",
    +  password = "test123"
    +)
    +
    +req <- request(base_url = base_url) |>
    +  req_url_path("/api/users/login") |>
    +  req_body_multipart(!!!user_details)
    +
    +# use `tryCatch()` in case an error occurs while performing the request:
    +tryCatch(
    +  expr = req |>
    +    req_perform() |>
    +    resp_body_json(),
    +  error = \(e) {
    +    print("An error occurred!")
    +    error <- last_response() |> resp_body_json()
    +    print(error)
    +  }
    +)
    +

    Again, on successful login, you get back a list containing a named list of 4 items:

    +
      +
    • _id
    • +
    • name
    • +
    • email
    • +
    • token: A JWT token
    • +
    +
    [[1]]
    +[[1]]$`_id`
    +[1] "669c6ee4ddfa7be6a10f07e1"
    +
    +[[1]]$name
    +[1] "mwavu"
    +
    +[[1]]$email
    +[1] "mwavu@mail.com"
    +
    +[[1]]$token
    +[1] "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4"
    +
    +
    +

    To get details of a specific user, you need the JWT token returned during register or login. The token is verified by the auth middleware.

    +

    Make a GET request to /api/users/me and include the JWT as an auth bearer token:

    +
    base_url <- "http://127.0.0.1:5000"
    +
    +# the JWT token from signup/login:
    +token <- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4"
    +
    +req <- request(base_url = base_url) |>
    +  req_url_path("/api/users/me") |>
    +  req_auth_bearer_token(token = token)
    +
    +# use `tryCatch()` in case an error occurs while performing the request:
    +tryCatch(
    +  expr = req |>
    +    req_perform() |>
    +    resp_body_json(),
    +  error = \(e) {
    +    print("An error occurred!")
    +    error <- last_response() |> resp_body_json()
    +    print(error)
    +  }
    +)
    +

    If successful, that returns a named list of 3:

    +
      +
    • _id
    • +
    • name
    • +
    • email
    • +
    +
    +
    +
    +
    +
    +

    /api/goals*

    +

    Every route in /api/goals* is protected, meaning they can only be accessed by an authenticated user. Also, each user only has access to the goals they set, not anyone elses.

    +

    In other words, send the JWT as an auth bearer token in your requests.

    +
    + +
    +
    +

    Let’s set a goal by sending a POST request to /api/goals:

    +
    base_url <- "http://127.0.0.1:5000"
    +
    +# the JWT token from signup/login:
    +token <- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4"
    +
    +# your goal:
    +text <- "Learn Rust"
    +
    +req <- request(base_url = base_url) |>
    +  req_url_path("/api/goals") |>
    +  req_auth_bearer_token(token = token) |>
    +  req_body_multipart(text = text)
    +
    +# use `tryCatch()` in case an error occurs while performing the request:
    +tryCatch(
    +  expr = req |>
    +    req_perform() |>
    +    resp_body_json(),
    +  error = \(e) {
    +    print("An error occurred!")
    +    error <- last_response() |> resp_body_json()
    +    print(error)
    +  }
    +)
    +

    If successful, that should return a list containing a named list of 3:

    +
      +
    • _id
    • +
    • user_id
    • +
    • text
    • +
    +

    For example:

    +
    [[1]]
    +[[1]]$`_id`
    +[1] "669c74e6ddfa7be6a10f07e2"
    +
    +[[1]]$user_id
    +[1] "669c6ee4ddfa7be6a10f07e1"
    +
    +[[1]]$text
    +[1] "Learn Rust"
    +
    +
    +

    To get all goals a user has set, send a GET request to /api/goals:

    +
    base_url <- "http://127.0.0.1:5000"
    +
    +# the JWT token from signup/login:
    +token <- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4"
    +
    +req <- request(base_url = base_url) |>
    +  req_url_path("/api/goals") |>
    +  req_auth_bearer_token(token = token)
    +
    +# use `tryCatch()` in case an error occurs while performing the request:
    +res <- tryCatch(
    +  expr = req |>
    +    req_perform() |>
    +    resp_body_json(),
    +  error = \(e) {
    +    print("An error occurred!")
    +    error <- last_response() |> resp_body_json()
    +    print(error)
    +  }
    +)
    +

    If successful, that should return a list of named lists. Each of the nested lists has 2 items:

    +
      +
    • _id: Id of the goal
    • +
    • text: The goal
    • +
    +

    For example:

    +
    str(res)
    +# List of 1
    +#  $ :List of 2
    +#   ..$ _id : chr "669c74e6ddfa7be6a10f07e2"
    +#   ..$ text: chr "Learn Rust"
    +
    +
    +

    To update a goal, send a PUT request to /api/users/:id:

    +
    base_url <- "http://127.0.0.1:5000"
    +
    +# the JWT token from signup/login:
    +token <- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4"
    +
    +updated_goal <- "Learn Rust & Postgres"
    +
    +req <- request(base_url = base_url) |>
    +  req_url_path("/api/goals/669c74e6ddfa7be6a10f07e2") |>
    +  req_auth_bearer_token(token = token) |>
    +  req_method(method = "PUT") |>
    +  req_body_multipart(text = updated_goal)
    +
    +# use `tryCatch()` in case an error occurs while performing the request:
    +res <- tryCatch(
    +  expr = req |>
    +    req_perform() |>
    +    resp_body_json(),
    +  error = \(e) {
    +    print("An error occurred!")
    +    error <- last_response() |> resp_body_json()
    +    print(error)
    +  }
    +)
    +

    If successful, you should get back a named list of 2:

    +
    str(res)
    +# List of 2
    +#  $ msg : chr "Goal updated successfully"
    +#  $ goal:List of 1
    +#   ..$ :List of 2
    +#   .. ..$ _id : chr "669c74e6ddfa7be6a10f07e2"
    +#   .. ..$ text: chr "Learn Rust & Postgres"
    +
    +
    +

    To delete a goal, send a, well, DELETE request to /api/goals/:id:

    +
    base_url <- "http://127.0.0.1:5000"
    +
    +# the JWT token from signup/login:
    +token <- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4"
    +
    +req <- request(base_url = base_url) |>
    +  req_url_path("/api/goals/669c74e6ddfa7be6a10f07e2") |>
    +  req_auth_bearer_token(token = token) |>
    +  req_method(method = "DELETE")
    +
    +# use `tryCatch()` in case an error occurs while performing the request:
    +res <- tryCatch(
    +  expr = req |>
    +    req_perform() |>
    +    resp_body_json(),
    +  error = \(e) {
    +    print("An error occurred!")
    +    error <- last_response() |> resp_body_json()
    +    print(error)
    +  }
    +)
    +

    If successful, you will again get back a named list of 2:

    +
    str(res)
    +# List of 2
    +#  $ msg : chr "Goal deleted successfully"
    +#  $ goal:List of 1
    +#   ..$ :List of 2
    +#   .. ..$ _id : chr "669c74e6ddfa7be6a10f07e2"
    +#   .. ..$ text: chr "Learn Rust & Postgres"
    +
    +
    +
    +
    +
    +
    +

    Live reloading

    See how you can enable ✨live reloading✨.

    diff --git a/docs/search.json b/docs/search.json index 99a46bf..1fba40d 100644 --- a/docs/search.json +++ b/docs/search.json @@ -385,32 +385,18 @@ "text": "An installation of the community edition of MongoDB\nThe mongolite R pkg" }, { - "objectID": "posts/09_goals/index.html#prerequisites", - "href": "posts/09_goals/index.html#prerequisites", + "objectID": "posts/09_goals/index.html#apiusers", + "href": "posts/09_goals/index.html#apiusers", "title": "09: goals", - "section": "", - "text": "An installation of the community edition of MongoDB\nThe mongolite R pkg" - }, - { - "objectID": "posts/09_goals/index.html#run-app", - "href": "posts/09_goals/index.html#run-app", - "title": "09: goals", - "section": "Run app", - "text": "Run app\n\ncd into the 09_goals/backend/ dir:\ncd 09_goals/backend/\nFire up R:\nR\nRestore package dependencies:\nrenv::restore()\nOnce done, exit R.\nserver.R is the entry point. To start the app, run this on the terminal:\nRscript server.R" - }, - { - "objectID": "posts/09_goals/index.html#explanation", - "href": "posts/09_goals/index.html#explanation", - "title": "09: goals", - "section": "Explanation", - "text": "Explanation\nThis app starts a server and listens on port 5000 for connections.\nIn this example, we build a CRUD application backend: Goals.\nHere are the defined routes:\n\n/api:\n\nGET /goals: Get all user goals\nPOST /goals: Create a goal\nPUT /goals/:id: Update a goal\nDELETE /goals/:id: Delete a goal\n/users:\n\nPOST /: Register new user\nPOST /login: Login user\nGET /me: Get user data\n\n\n\nYou will be able to Create, Read, Update & Delete Goals.\nHere’s what’s covered:\n\nAmbiorix + MongoDB\nWorking with middleware:\n\nAuth middleware: You will learn how you can use JSON Web Tokens (JWT) to protect routes\nError handling middleware" + "section": "/api/users*", + "text": "/api/users*\n\nRegisterLoginDetails\n\n\nSince the API requires JWT auth, you first need to create an account. To do that, make a POST request to /api/users:\nbase_url <- \"http://127.0.0.1:5000\"\n\n# registration details:\nuser_details <- list(\n name = \"mwavu\",\n email = \"mwavu@mail.com\",\n password = \"test123\"\n)\n\nreq <- request(base_url = base_url) |>\n req_url_path(\"/api/users\") |>\n req_body_multipart(!!!user_details)\n\n# use `tryCatch()` in case an error occurs while performing the request:\ntryCatch(\n expr = req |>\n req_perform() |>\n resp_body_json(),\n error = \\(e) {\n print(\"An error occurred!\")\n error <- last_response() |> resp_body_json()\n print(error)\n }\n)\nIf that’s successful, you get back a list containing a named list of 4 items: - _id - name - email - token: A JWT token\nHere is an example:\n[[1]]\n[[1]]$`_id`\n[1] \"669c6ee4ddfa7be6a10f07e1\"\n\n[[1]]$name\n[1] \"mwavu\"\n\n[[1]]$email\n[1] \"mwavu@mail.com\"\n\n[[1]]$token\n[1] \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY0MzcsImlhdCI6MTcyMTUyODAzNywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.ebhkSXwJChW4iqLDRRoLOGrX3hb7NevRW4vZncjm5MY\"\n\n\nTo login a user, we make a POST request to /api/users/login:\nbase_url <- \"http://127.0.0.1:5000\"\n\n# login details:\nuser_details <- list(\n email = \"mwavu@mail.com\",\n password = \"test123\"\n)\n\nreq <- request(base_url = base_url) |>\n req_url_path(\"/api/users/login\") |>\n req_body_multipart(!!!user_details)\n\n# use `tryCatch()` in case an error occurs while performing the request:\ntryCatch(\n expr = req |>\n req_perform() |>\n resp_body_json(),\n error = \\(e) {\n print(\"An error occurred!\")\n error <- last_response() |> resp_body_json()\n print(error)\n }\n)\nAgain, on successful login, you get back a list containing a named list of 4 items:\n\n_id\nname\nemail\ntoken: A JWT token\n\n[[1]]\n[[1]]$`_id`\n[1] \"669c6ee4ddfa7be6a10f07e1\"\n\n[[1]]$name\n[1] \"mwavu\"\n\n[[1]]$email\n[1] \"mwavu@mail.com\"\n\n[[1]]$token\n[1] \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4\"\n\n\nTo get details of a specific user, you need the JWT token returned during register or login. The token is verified by the auth middleware.\nMake a GET request to /api/users/me and include the JWT as an auth bearer token:\nbase_url <- \"http://127.0.0.1:5000\"\n\n# the JWT token from signup/login:\ntoken <- \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4\"\n\nreq <- request(base_url = base_url) |>\n req_url_path(\"/api/users/me\") |>\n req_auth_bearer_token(token = token)\n\n# use `tryCatch()` in case an error occurs while performing the request:\ntryCatch(\n expr = req |>\n req_perform() |>\n resp_body_json(),\n error = \\(e) {\n print(\"An error occurred!\")\n error <- last_response() |> resp_body_json()\n print(error)\n }\n)\nIf successful, that returns a named list of 3:\n\n_id\nname\nemail" }, { - "objectID": "posts/09_goals/index.html#live-reloading", - "href": "posts/09_goals/index.html#live-reloading", + "objectID": "posts/09_goals/index.html#apigoals", + "href": "posts/09_goals/index.html#apigoals", "title": "09: goals", - "section": "Live reloading", - "text": "Live reloading\nSee how you can enable ✨live reloading✨." + "section": "/api/goals*", + "text": "/api/goals*\nEvery route in /api/goals* is protected, meaning they can only be accessed by an authenticated user. Also, each user only has access to the goals they set, not anyone elses.\nIn other words, send the JWT as an auth bearer token in your requests.\n\nCreateReadUpdateDelete\n\n\nLet’s set a goal by sending a POST request to /api/goals:\nbase_url <- \"http://127.0.0.1:5000\"\n\n# the JWT token from signup/login:\ntoken <- \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4\"\n\n# your goal:\ntext <- \"Learn Rust\"\n\nreq <- request(base_url = base_url) |>\n req_url_path(\"/api/goals\") |>\n req_auth_bearer_token(token = token) |>\n req_body_multipart(text = text)\n\n# use `tryCatch()` in case an error occurs while performing the request:\ntryCatch(\n expr = req |>\n req_perform() |>\n resp_body_json(),\n error = \\(e) {\n print(\"An error occurred!\")\n error <- last_response() |> resp_body_json()\n print(error)\n }\n)\nIf successful, that should return a list containing a named list of 3:\n\n_id\nuser_id\ntext\n\nFor example:\n[[1]]\n[[1]]$`_id`\n[1] \"669c74e6ddfa7be6a10f07e2\"\n\n[[1]]$user_id\n[1] \"669c6ee4ddfa7be6a10f07e1\"\n\n[[1]]$text\n[1] \"Learn Rust\"\n\n\nTo get all goals a user has set, send a GET request to /api/goals:\nbase_url <- \"http://127.0.0.1:5000\"\n\n# the JWT token from signup/login:\ntoken <- \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4\"\n\nreq <- request(base_url = base_url) |>\n req_url_path(\"/api/goals\") |>\n req_auth_bearer_token(token = token)\n\n# use `tryCatch()` in case an error occurs while performing the request:\nres <- tryCatch(\n expr = req |>\n req_perform() |>\n resp_body_json(),\n error = \\(e) {\n print(\"An error occurred!\")\n error <- last_response() |> resp_body_json()\n print(error)\n }\n)\nIf successful, that should return a list of named lists. Each of the nested lists has 2 items:\n\n_id: Id of the goal\ntext: The goal\n\nFor example:\nstr(res)\n# List of 1\n# $ :List of 2\n# ..$ _id : chr \"669c74e6ddfa7be6a10f07e2\"\n# ..$ text: chr \"Learn Rust\"\n\n\nTo update a goal, send a PUT request to /api/users/:id:\nbase_url <- \"http://127.0.0.1:5000\"\n\n# the JWT token from signup/login:\ntoken <- \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4\"\n\nupdated_goal <- \"Learn Rust & Postgres\"\n\nreq <- request(base_url = base_url) |>\n req_url_path(\"/api/goals/669c74e6ddfa7be6a10f07e2\") |>\n req_auth_bearer_token(token = token) |>\n req_method(method = \"PUT\") |>\n req_body_multipart(text = updated_goal)\n\n# use `tryCatch()` in case an error occurs while performing the request:\nres <- tryCatch(\n expr = req |>\n req_perform() |>\n resp_body_json(),\n error = \\(e) {\n print(\"An error occurred!\")\n error <- last_response() |> resp_body_json()\n print(error)\n }\n)\nIf successful, you should get back a named list of 2:\nstr(res)\n# List of 2\n# $ msg : chr \"Goal updated successfully\"\n# $ goal:List of 1\n# ..$ :List of 2\n# .. ..$ _id : chr \"669c74e6ddfa7be6a10f07e2\"\n# .. ..$ text: chr \"Learn Rust & Postgres\"\n\n\nTo delete a goal, send a, well, DELETE request to /api/goals/:id:\nbase_url <- \"http://127.0.0.1:5000\"\n\n# the JWT token from signup/login:\ntoken <- \"eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4\"\n\nreq <- request(base_url = base_url) |>\n req_url_path(\"/api/goals/669c74e6ddfa7be6a10f07e2\") |>\n req_auth_bearer_token(token = token) |>\n req_method(method = \"DELETE\")\n\n# use `tryCatch()` in case an error occurs while performing the request:\nres <- tryCatch(\n expr = req |>\n req_perform() |>\n resp_body_json(),\n error = \\(e) {\n print(\"An error occurred!\")\n error <- last_response() |> resp_body_json()\n print(error)\n }\n)\nIf successful, you will again get back a named list of 2:\nstr(res)\n# List of 2\n# $ msg : chr \"Goal deleted successfully\"\n# $ goal:List of 1\n# ..$ :List of 2\n# .. ..$ _id : chr \"669c74e6ddfa7be6a10f07e2\"\n# .. ..$ text: chr \"Learn Rust & Postgres\"" }, { "objectID": "posts/07_dynamic_rendering/index.html", diff --git a/documentation/posts/09_goals/index.qmd b/documentation/posts/09_goals/index.qmd index 30153c5..8ede750 100644 --- a/documentation/posts/09_goals/index.qmd +++ b/documentation/posts/09_goals/index.qmd @@ -6,17 +6,17 @@ date: "2024-07-10" categories: [09_goals] --- -## Prerequisites +# Prerequisites - An installation of the community edition of [MongoDB](https://www.mongodb.com/docs/manual/installation/) - The [mongolite](https://github.com/jeroen/mongolite) R pkg -## Run app +# Run app -1. `cd` into the `09_goals/backend/` dir: +1. `cd` into the `09_goals/` dir: ```bash - cd 09_goals/backend/ + cd 09_goals/ ``` 1. Fire up R: @@ -38,7 +38,7 @@ categories: [09_goals] Rscript server.R ``` -## Explanation +# Explanation This app starts a server and listens on port 5000 for connections. @@ -65,6 +65,365 @@ Here's what's covered: - Auth middleware: You will learn how you can use JSON Web Tokens ([JWT](https://jwt.io/)) to protect routes - Error handling middleware -## Live reloading +# Requests to the API + +Let's explore how you can send requests to the API. We'll do so from another R session. + +Be sure to import the required functions first: + +```r +box::use( + httr2[ + request, + req_method, + req_perform, + req_url_path, + last_response, + resp_body_json, + req_body_multipart, + req_auth_bearer_token, + ] +) +``` + +## `/api/users*` + +:::{.panel-tabset} + +### Register + +Since the API requires JWT auth, you first need to create an account. To do that, +make a POST request to `/api/users`: + +```r +base_url <- "http://127.0.0.1:5000" + +# registration details: +user_details <- list( + name = "mwavu", + email = "mwavu@mail.com", + password = "test123" +) + +req <- request(base_url = base_url) |> + req_url_path("/api/users") |> + req_body_multipart(!!!user_details) + +# use `tryCatch()` in case an error occurs while performing the request: +tryCatch( + expr = req |> + req_perform() |> + resp_body_json(), + error = \(e) { + print("An error occurred!") + error <- last_response() |> resp_body_json() + print(error) + } +) +``` + +If that's successful, you get back a list containing a named list of 4 items: +- `_id` +- `name` +- `email` +- `token`: A JWT token + +Here is an example: + +``` +[[1]] +[[1]]$`_id` +[1] "669c6ee4ddfa7be6a10f07e1" + +[[1]]$name +[1] "mwavu" + +[[1]]$email +[1] "mwavu@mail.com" + +[[1]]$token +[1] "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY0MzcsImlhdCI6MTcyMTUyODAzNywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.ebhkSXwJChW4iqLDRRoLOGrX3hb7NevRW4vZncjm5MY" +``` + +### Login + +To login a user, we make a POST request to `/api/users/login`: + +```r +base_url <- "http://127.0.0.1:5000" + +# login details: +user_details <- list( + email = "mwavu@mail.com", + password = "test123" +) + +req <- request(base_url = base_url) |> + req_url_path("/api/users/login") |> + req_body_multipart(!!!user_details) + +# use `tryCatch()` in case an error occurs while performing the request: +tryCatch( + expr = req |> + req_perform() |> + resp_body_json(), + error = \(e) { + print("An error occurred!") + error <- last_response() |> resp_body_json() + print(error) + } +) +``` + +Again, on successful login, you get back a list containing a named list of 4 items: + +- `_id` +- `name` +- `email` +- `token`: A JWT token + +``` +[[1]] +[[1]]$`_id` +[1] "669c6ee4ddfa7be6a10f07e1" + +[[1]]$name +[1] "mwavu" + +[[1]]$email +[1] "mwavu@mail.com" + +[[1]]$token +[1] "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4" +``` + +### Details + +To get details of a specific user, you need the JWT token returned during +register or login. The token is verified by the auth middleware. + +Make a GET request to `/api/users/me` and include the JWT as an auth bearer token: + +```r +base_url <- "http://127.0.0.1:5000" + +# the JWT token from signup/login: +token <- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4" + +req <- request(base_url = base_url) |> + req_url_path("/api/users/me") |> + req_auth_bearer_token(token = token) + +# use `tryCatch()` in case an error occurs while performing the request: +tryCatch( + expr = req |> + req_perform() |> + resp_body_json(), + error = \(e) { + print("An error occurred!") + error <- last_response() |> resp_body_json() + print(error) + } +) +``` + +If successful, that returns a named list of 3: + +- `_id` +- `name` +- `email` + +::: + +## `/api/goals*` + +Every route in `/api/goals*` is protected, meaning they can only be accessed +by an authenticated user. Also, each user only has access to the goals they set, +not anyone elses. + +In other words, send the JWT as an auth bearer token in your requests. + +:::{.panel-tabset} + +### Create + +Let's set a goal by sending a POST request to `/api/goals`: + +```r +base_url <- "http://127.0.0.1:5000" + +# the JWT token from signup/login: +token <- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4" + +# your goal: +text <- "Learn Rust" + +req <- request(base_url = base_url) |> + req_url_path("/api/goals") |> + req_auth_bearer_token(token = token) |> + req_body_multipart(text = text) + +# use `tryCatch()` in case an error occurs while performing the request: +tryCatch( + expr = req |> + req_perform() |> + resp_body_json(), + error = \(e) { + print("An error occurred!") + error <- last_response() |> resp_body_json() + print(error) + } +) +``` + +If successful, that should return a list containing a named list of 3: + +- `_id` +- `user_id` +- `text` + +For example: + +``` +[[1]] +[[1]]$`_id` +[1] "669c74e6ddfa7be6a10f07e2" + +[[1]]$user_id +[1] "669c6ee4ddfa7be6a10f07e1" + +[[1]]$text +[1] "Learn Rust" +``` + +### Read + +To get all goals a user has set, send a GET request to `/api/goals`: + +```r +base_url <- "http://127.0.0.1:5000" + +# the JWT token from signup/login: +token <- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4" + +req <- request(base_url = base_url) |> + req_url_path("/api/goals") |> + req_auth_bearer_token(token = token) + +# use `tryCatch()` in case an error occurs while performing the request: +res <- tryCatch( + expr = req |> + req_perform() |> + resp_body_json(), + error = \(e) { + print("An error occurred!") + error <- last_response() |> resp_body_json() + print(error) + } +) +``` + +If successful, that should return a list of named lists. Each of the nested +lists has 2 items: + +- `_id`: Id of the goal +- `text`: The goal + +For example: + +```r +str(res) +# List of 1 +# $ :List of 2 +# ..$ _id : chr "669c74e6ddfa7be6a10f07e2" +# ..$ text: chr "Learn Rust" +``` + +### Update + +To update a goal, send a PUT request to `/api/users/:id`: + +```r +base_url <- "http://127.0.0.1:5000" + +# the JWT token from signup/login: +token <- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4" + +updated_goal <- "Learn Rust & Postgres" + +req <- request(base_url = base_url) |> + req_url_path("/api/goals/669c74e6ddfa7be6a10f07e2") |> + req_auth_bearer_token(token = token) |> + req_method(method = "PUT") |> + req_body_multipart(text = updated_goal) + +# use `tryCatch()` in case an error occurs while performing the request: +res <- tryCatch( + expr = req |> + req_perform() |> + resp_body_json(), + error = \(e) { + print("An error occurred!") + error <- last_response() |> resp_body_json() + print(error) + } +) +``` + +If successful, you should get back a named list of 2: + +```r +str(res) +# List of 2 +# $ msg : chr "Goal updated successfully" +# $ goal:List of 1 +# ..$ :List of 2 +# .. ..$ _id : chr "669c74e6ddfa7be6a10f07e2" +# .. ..$ text: chr "Learn Rust & Postgres" +``` + +### Delete + +To delete a goal, send a, well, DELETE request to `/api/goals/:id`: + +```r +base_url <- "http://127.0.0.1:5000" + +# the JWT token from signup/login: +token <- "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJleHAiOjE3MjQyMDY5NDcsImlhdCI6MTcyMTUyODU0NywidXNlcl9pZCI6IjY2OWM2ZWU0ZGRmYTdiZTZhMTBmMDdlMSJ9.rpSOL0LynYm2BBP60Ikpz-GNIY6mR_ZKKzH9Tai2IS4" + +req <- request(base_url = base_url) |> + req_url_path("/api/goals/669c74e6ddfa7be6a10f07e2") |> + req_auth_bearer_token(token = token) |> + req_method(method = "DELETE") + +# use `tryCatch()` in case an error occurs while performing the request: +res <- tryCatch( + expr = req |> + req_perform() |> + resp_body_json(), + error = \(e) { + print("An error occurred!") + error <- last_response() |> resp_body_json() + print(error) + } +) +``` + +If successful, you will again get back a named list of 2: + +```r +str(res) +# List of 2 +# $ msg : chr "Goal deleted successfully" +# $ goal:List of 1 +# ..$ :List of 2 +# .. ..$ _id : chr "669c74e6ddfa7be6a10f07e2" +# .. ..$ text: chr "Learn Rust & Postgres" +``` + +::: + +# Live reloading See how you can enable ✨[live reloading](../10_live_reloading/index.qmd)✨.