;; handlers/LinksAPIHandler.fnl
;; Provides a RequestHandler that handles service logic for the /links API endpoint

;; -------------------------------------------------------------------------- ;;
;; Dependencies

(local turbo (require :turbo))

;; -------------------------------------------------------------------------- ;;
;; Local modules

(local config (require :modules.config))
(local link_utils (require :modules.link_utils))
(local links_db (require :modules.links_db))
(local sessions_db (require :modules.sessions_db))

;; -------------------------------------------------------------------------- ;;
;; Handler definition

(local LinksAPIHandler (class "LinksAPIHandler" turbo.web.RequestHandler))

(fn LinksAPIHandler.prepare [self]
  "Check that the client session is authorized before handling the request.
Responds with 401 Unauthorized and finished the request if the client session is not authorized."
  (let [session_id (self:get_cookie "session_id")]
    (when (not (and session_id
                    (sessions_db.is_session_active? session_id)))
      (self:set_status 401)
      (self:finish))))

;; ---------------------------------- ;;
;; GET

(fn LinksAPIHandler.get [self ?query]
  "Return up to config.query_limit most recent links from the database that contain `?query` in link_id or link_dest.
If `?query` is not a valid base62 string, return up to config.query_limit most recent links from the database.
If custom (provided in GET body) is set, return only links with custom IDs.
Responds with 200 OK and an array of links."
  (let [custom (self:get_argument "custom" false true)]
    (if (link_utils.valid_link_id? ?query)
        (self:write (links_db.search_links ?query config.query_limit custom))
        (self:write (links_db.fetch_links config.query_limit custom)))))

;; ---------------------------------- ;;
;; POST

(fn LinksAPIHandler.post [self ?link_id]
  "Insert a link to link_dest (provided in POST body) with optional custom ID `?link_id` into the database.
Responds with 200 OK and the ID of the new link on success.
Responds with 409 Conflict if the ID is already taken.
Responds with 400 Bad Request if the provided link destination is not a valid URI."
  (let [link_id (if (link_utils.valid_link_id? ?link_id) ?link_id nil)
        link_dest (link_utils.normalize_uri (self:get_argument "link_dest" nil true))]
    (if (link_utils.valid_link_dest? link_dest)
        (let [result (links_db.insert_link link_id link_dest)]
          (if result
              (self:write result)
              (self:set_status 409)))
        (self:set_status 400))))

;; ---------------------------------- ;;
;; PUT

(fn LinksAPIHandler.put [self link_id]
  "Redirect the link with ID `link_id` to new destination link_dest (provided in POST body) in the database.
Responds with 204 No Content on success.
Responds with 500 Internal Server Error on failure.
Responds with 404 Not Found if the specified link cannot be found.
Responds with 400 Bad Request if the provided link destination is not a valid URI."
  (let [link_dest (link_utils.normalize_uri (self:get_argument "link_dest" nil true))]
    (if (link_utils.valid_link_dest? link_dest)
        (if (links_db.fetch_link_dest link_id)
            (if (links_db.redirect_link link_id link_dest)
                (self:set_status 204)
                (self:set_status 500))
            (self:set_status 404))
        (self:set_status 400))))

;; ---------------------------------- ;;
;; DELETE

(fn LinksAPIHandler.delete [self link_id]
  "Delete the link with ID `link_id` from the database.
Responds with 204 No Content on success.
Responds with 500 Internal Server Error on failure.
Responds with 404 Not Found if the specified link cannot be found."
  (if (links_db.fetch_link_dest link_id)
      (if (links_db.delete_link link_id)
          (self:set_status 204)
          (self:set_status 500))
      (self:set_status 404)))

;; -------------------------------------------------------------------------- ;;
;; Return handler

LinksAPIHandler
