When I attempt to use the submit-login function or the submit-guess function, I am not getting a proper response back. I get {:status 404, :success false, :body <div><h1>Figwheel Server: Resource not found</h1><h3><em>Keep on figwheelin' yep</em></h3></div>, :headers {access-control-allow-methods DELETE, GET, HEAD, OPTIONS, PATCH, POST, PUT, access-control-allow-origin http://localhost:9500, cache-control no-cache, content-type text/html;charset=utf-8, date Sun, 18 Feb 2024 10:50:23 GMT, server Jetty(9.4.36.v20210114), transfer-encoding chunked}, :trace-redirects [http://localhost:9500/api/login http://localhost:9500/api/login], :error-code :http-error, :error-text Not Found [404]}.

Here is my project:

(defproject simple-server "0.1.0-SNAPSHOT"
  :description "FIXME: write description"
  :url "http://example.com/FIXME"
  :license {:name "EPL-2.0 OR GPL-2.0-or-later WITH Classpath-exception-2.0"
            :url "https://www.eclipse.org/legal/epl-2.0/"}
  
  :min-lein-version "2.7.1"

  :dependencies [[org.clojure/clojure "1.10.0"]
                 [org.clojure/clojurescript "1.11.4"]
                 [cljsjs/react "17.0.2-0"]
                 [cljsjs/react-dom "17.0.2-0"]
                 [reagent "1.1.1"]
                 [org.clojure/core.async "1.6.681"]

                      ;; JDBC
                 [org.postgresql/postgresql "42.7.1"]
                 [org.clojure/java.jdbc "0.7.11"]
                 [org.xerial/sqlite-jdbc "3.30.1"]

                 [cljs-ajax "0.8.4"]
                 [cljs-http "0.1.46"]
                 [compojure "1.6.1"]
                 [ring/ring-mock "0.3.0"]
                 [ring/ring-devel "1.5.0"]
                 [ring-cors "0.1.13"]

                 [ring/ring-defaults "0.3.2"]
                 [hiccup "1.0.5"]
                 [metosin/reitit "0.5.18"]
                 
                 ;; For string encoding and decoding
                 [org.clj-commons/byte-transforms "0.2.1"]]
  
  :source-paths ["src"]

  :aliases {"fig:build" ["run" "-m" "figwheel.main" "-b" "dev" "-r"]
            "fig:min"   ["run" "-m" "figwheel.main" "-O" "advanced" "-bo" "dev"]
            "fig:test"  ["run" "-m" "figwheel.main" "-co" "test.cljs.edn" "-m" "simple-server.test-runner"]}

  :profiles {:dev {:dependencies [[com.bhauman/figwheel-main "0.2.17"]
                                  [org.slf4j/slf4j-nop "1.7.30"]]

                   :resource-paths ["target"]
                   ;; need to add the compiled assets to the :clean-targets
                   :clean-targets ^{:protect false} ["target"]}})


Here is my frontend:

(ns ^:figwheel-hooks simple-server.core
  (:require [clojure.string :as str]
            [ajax.core :refer [GET POST]]
            [goog.dom :as gdom]
            [goog.events :as events]
            [reagent.core :as r :refer [atom]]
            [reagent.dom :as rdom]
            [reitit.core :as reitit]
            [cljs.core.async :as a
             :refer [>! <! go chan buffer close! alts! timeout]]
            [cljs-http.client :as http]))
(def site-url "http://localhost:9500")

;; guesser page stuff
(def guess-val      (r/atom 5))

(def guess-text "Guess the number I'm thinking of!")
(defonce app-state (r/atom {:text guess-text}))


(defn slider-on-change-handler [js-event]
  (reset! guess-val (-> js-event .-target .-value)))

(defn multiply [a b] (* a b))


;; login page functions
(defn form-sanitizer [input]
  (str/lower-case (str/trim input)))


;; Channels, async, other considerations
(defn get-app-element []
  (gdom/getElement "app"))

(defonce event-channel (chan 10))

(defn dispatch-event! [e]
  (case (:type e)
    :login-success  (swap! app-state merge
                           {:user-authenticated true
                            :message "Login successful, start guessing!"})
    :login-fail     (swap! app-state assoc :message "Login failed! Try again.")
    :guess-response (swap! app-state assoc :message (str "Server says: " (:message e)))
    :guess-error    (swap! app-state assoc :message "Error submitting guess.")
    (println "Unknown event type: " e)))

;; (defonce global-handler
;;   (go
;;     (while true
;;       (let [e (<! event-channel)]
;;         (dispatch-event! e)))))


;; Frontend event handler to update username and password
(defn update-credentials [field value]
  (swap! app-state assoc field value))


;; Frontend event handler to submit the login form
(defn submit-login []
  (let [{:keys [username password]} @app-state]
    ;; (println "keys are" username "and" password)
    (go
      (let [response (<! (http/post (str site-url "/api/login")
                                    {:json-params {:username (form-sanitizer username) :password password}}))]
        (println response)
        (if (:success response)
          (>! event-channel {:type :login-success})
          (>! event-channel {:type :login-fail}))))))

;; Frontend event handler to submit guesses to the backend
(defn submit-guess []
  (let [guess (@app-state :guess)]
    (go
      (let [response (<! (http/post (str site-url "/api/guess")
                                    {:json-params {:guess guess}}))]
        (if (:success response)
          (>! event-channel {:type :guess-response :message (:body response)})
          (>! event-channel {:type :guess-error}))))))


;; Login component
(defn login-page []
  [:div.login-form
   [:h1 "Login"]
   [:input {:type "text"
            :placeholder "Username"
            :on-change #(update-credentials :username (-> % .-target .-value))}]
   [:input {:type "password"
            :placeholder "Password"
            :on-change #(update-credentials :password (-> % .-target .-value))}]
   [:button {:on-click submit-login} "Login"]
   [:div.message (@app-state :message)]
   [:h3  (str @app-state)]])




(defn guessing-page []
  [:div.guessing-game
   [:h3 "Guess the number I'm thinking of!"]
   [:input {:type "number"
            :value (@app-state :guess)
            :on-change #(swap! app-state assoc :guess (-> % .-target .-value))}]
   [:button {:on-click submit-guess} "Submit Guess"]
   [:div.message (@app-state :message)]])


;; (defn guess-page []
;;   [:div
;;    [:h1 "Guessing Game"]
;;    [:h3 (:text @app-state)]
;;    [:div {:class "slidecontainer"}
;;     [:input {:type  "range"
;;              :id    "MyRange1"
;;              :min   1
;;              :max   10
;;              :value 5
;;              :on-change slider-on-change-handler}]]
;;    [:h3 @guess-val]])

;; Conditional rendering based on authentication
(defn app-root []
  (if (@app-state :user-authenticated)
    [guessing-page]
    [login-page]))

(defn mount-app []
  (rdom/render [app-root] (.getElementById js/document "app")))

#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defn ^:after-load on-reload []
  ;; Re-mount the app after code reloading occurs
  (mount-app))

;; Initial mounting of the app
(mount-app)




And here is my backend:

(ns simple-server.server
  (:require
   [clojure.pprint]
   [clojure.string :as str]
   [compojure.coercions   :refer [as-int]]
   [compojure.core        :refer [ANY defroutes GET POST OPTIONS]]
   [compojure.route           :as route]
   [ring.adapter.jetty    :refer [run-jetty]]
   [ring.middleware.defaults  :as middleware]
   [ring.middleware.cookies   :as cookies]
   [ring.middleware.multipart-params :as multi]
  ;;  [ring.middleware.cors :refer [wrap-cors]]
   [ring.mock.request         :as mock]
   [ring.util.response        :as ring
    :refer [not-found redirect response status]]
   [simple-server.simple-game :as game]
   [simple-server.db          :as db]
   [byte-transforms           :as transforms]
   [clojure.pprint :as pprint]))

;; (defn random-api []
;;   (println "I've been called!")
;;   {:status 200
;;    :body (pr-str {:lucky-number (rand-int 1000)
;;                   :a-set #{1 "foo" :baz [::a ::b]}})
;;    :headers {"Content-Type" "application/edn"}})

(defn set-session-cookie [response user-id]
  (let [cookie-value (transforms/hash (str user-id) :crc32)] ; Generate a simple cookie value based on the user ID
    (ring/set-cookie response "token" cookie-value {:max-age 3600}))) ; Set cookie with a maximum age of 1 hour

(defn get-session-cookie [request]
  (get-in request [:cookies "token" :value]))


(defn new-game-handler [request]
  (when (game/new-game! (get-session-cookie request))
    (response (str "OK - start guessing at /guess/?guess=N"))))


(defn valid-login? [token password] ;; placeholder login logic...
  (or (and (= token "foo") (= password "bar"))
      (and (= token "admin") (= password "123"))
      (and (= token "user") (= password "pass"))
      (and (= token "egg") (= password "man"))
      (and (= token "test") (= password "test"))))

;; ;; tried to combine these functions but the login page is very fragile
;; (defn login-page-handler []
;;   (response (slurp "resources/public/login.html")))
;; ;; (defn guess-page-handler []
;; ;;   (response (slurp "res/guess.html")))


(defn validate-user-credentials [username password]
  (let [user-id-map (first (db/get-user-id username)) 
        user-id (:user_id user-id-map)                 
        password-map (first (db/get-password user-id))
        stored-password (:password password-map)]
    (= password stored-password)))

(defn random-api []
  (println "I've been called!")
  {:status 200
   :body (pr-str {:lucky-number (rand-int 1000)
                  :a-set #{1 "foo" :baz [::a ::b]}})
   :headers {"Content-Type" "application/edn"}})

(defn login-api-handler [request]
  (println request)
  (let [params (get request :params)
        username (:username params)
        password (:password params)]
    (if (validate-user-credentials username password)
      (let [user-id (-> (first (db/get-user-id username))
                        :user_id)]
        (-> (response {:status "success", :message "Login successful"})
            (set-session-cookie user-id)
            (ring/content-type "application/json")))
      (ring/response {:status "error", :message "Invalid login. Try again."}))))

(defn guess-api-handler [request]
  (let [params (get request :params)
        guess (Integer/parseInt (:guess params))
        token (get-session-cookie request)]
    (if (nil? token)
      (ring/response {:status "error", :message "Unauthorized"})
      (let [result (game/guess-answer guess token)]
        (response {:status "success", :message (name result), :tries-left (db/get-remaining-tries token)})))))

(defn guess-handler [guess user-hash]
  (condp = (game/guess-answer guess user-hash)
    nil       (-> (response  "You need to supply a guess with /guess?guess=N")
                  (status 400))
    :game-win  (response  (str "Congratulations! You win!"))
    :game-over (response  "Too bad! You ran out of tries!")
    :too-low   (response  (str "Too low! "  (db/get-remaining-tries user-hash) " tries remaining!"))
    :too-high  (response  (str "Too high! " (db/get-remaining-tries user-hash) " tries remaining!"))))



(defroutes site-routes

  ;; (GET  "/login"             []                          (login-page-handler))
  ;; (POST "/login"             request                     (login-handler request))
  (POST "/api/login"         request                     (login-api-handler request))
  (POST "/api/guess"         request                     (guess-api-handler request)) 
  (GET  "/api/random"        []                          (random-api))
  
  ;; (OPTIONS "*"               []                          {}) ;; handle preflight requests
 
  ;; (OPTIONS "*" [] (fn [request]
  ;;                   (-> (response "")
  ;;                       (ring/header "Access-Control-Allow-Origin" "http://localhost:9500")
  ;;                       (ring/header "Access-Control-Allow-Methods" "POST, GET, OPTIONS")
  ;;                       (ring/header "Access-Control-Allow-Headers", "content-type, x-requested-with")
  ;;                       (ring/header "Access-Control-Allow-Credentials", "true"))))

  (GET  "/new-game"          request                     (new-game-handler request))
  ;; (GET  "/guess:token"    [token]                     (guess-page-handler token))
  (GET  "/guess"             [guess :<< as-int :as request]  (guess-handler guess (get-session-cookie request)))
  (ANY  "*"                  []                          (not-found (str "Sorry, no such URI on this server!"))))

(defn add-content-type-htmltext-header [handler]
  (fn [request]
    (let [response (handler request)]
      (-> response
          (ring/header "Content-Type" "text/html")))))


(defn redirect-to-login-middleware
  "If a login cookie (at present, can be anything) is not found, redirects user to the login page."
  [handler]
  (fn [request]
    (let [token (get-session-cookie request)]
      (clojure.pprint/pprint request)
      (if (nil? token)
        (-> (handler (assoc request :uri "/login"))  ; Redirect to /login
            (ring/header "Content-Type" "text/html"))
        (handler request)))))

(defn wrap-error-handling [handler]
  (fn [request]
    (try
      (handler request)
      (catch Exception e
        (do
          (println "Unexpected error:" (.getMessage e))
          (ring/response "error"))))))

(def handler
  (-> site-routes
      ;; (wrap-cors :access-control-allow-origin ["http://localhost:9500"] ;; replace with your frontend domain/URL
      ;;            :access-control-allow-credentials true
      ;;            :access-control-allow-methods [:get :post :put :delete :options]
      ;;            :access-control-allow-headers ["Content-Type" "Authorization"]
      ;;            :access-control-max-age 3600)
      ;; (middleware/wrap-defaults middleware/api-defaults)
      (redirect-to-login-middleware)
      (add-content-type-htmltext-header)
      (multi/wrap-multipart-params)
      (cookies/wrap-cookies)))

(comment
  (handler (mock/request :get "/new-game"))
  (handler (mock/request :get "/guess?guess=3"))
  (handler (mock/request :get "/dunno")))

#_{:clj-kondo/ignore [:clojure-lsp/unused-public-var]}
(defonce server
  (future (run-jetty #'handler {:port 3000 :join? false})))