mirror of
https://github.com/penpot/penpot.git
synced 2025-07-17 08:38:19 +02:00
feat(backend): initial work on catacumba to plain ring migration
This commit is contained in:
parent
712269aa35
commit
f2411368ba
21 changed files with 1507 additions and 784 deletions
115
backend/src/uxbox/api.clj
Normal file
115
backend/src/uxbox/api.clj
Normal file
|
@ -0,0 +1,115 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.api
|
||||
(:require [mount.core :refer [defstate]]
|
||||
[clojure.pprint :refer [pprint]]
|
||||
[uxbox.config :as cfg]
|
||||
[ring.middleware.session :refer [wrap-session]]
|
||||
[ring.middleware.session.cookie :refer [cookie-store]]
|
||||
[ring.adapter.jetty :as jetty]
|
||||
[promesa.core :as p]
|
||||
[reitit.core :as rc]
|
||||
[reitit.ring :as ring]
|
||||
[reitit.ring.middleware.muuntaja :as muuntaja]
|
||||
[reitit.ring.middleware.multipart :as multipart]
|
||||
[reitit.ring.middleware.parameters :as parameters]
|
||||
;; [reitit.dev.pretty :as pretty]
|
||||
[uxbox.api.middleware :as api-middleware :refer [handler]]
|
||||
[uxbox.api.auth :as api-auth]
|
||||
[uxbox.api.projects :as api-projects]
|
||||
[uxbox.api.pages :as api-pages]
|
||||
[uxbox.api.errors :as api-errors]
|
||||
[muuntaja.core :as m]
|
||||
[uxbox.util.transit :as t]
|
||||
[uxbox.util.data :refer [normalize-attrs]]
|
||||
[uxbox.util.exceptions :as ex]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
;; --- Top Level Handlers
|
||||
|
||||
(defn- welcome-api
|
||||
"A GET entry point for the api that shows
|
||||
a welcome message."
|
||||
[context]
|
||||
(let [body {:message "Welcome to UXBox api."}]
|
||||
{:status 200
|
||||
:body {:query-params (:query-params context)
|
||||
:form-params (:form-params context)
|
||||
:body-params (:body-params context)
|
||||
:path-params (:path-params context)
|
||||
:params (:params context)}}))
|
||||
|
||||
;; --- Routes
|
||||
|
||||
(def routes
|
||||
(ring/router
|
||||
[["/media/*" (ring/create-resource-handler {:root "public/media"})]
|
||||
["/static/*" (ring/create-resource-handler {:root "public/static"})]
|
||||
|
||||
["/auth/login" {:post (handler #'api-auth/login)}]
|
||||
|
||||
["/api" {:middleware [api-auth/authorization-middleware]}
|
||||
["/echo" (handler #'welcome-api)]
|
||||
["/projects" {:get (handler #'api-projects/list)
|
||||
:post (handler #'api-projects/create)}]
|
||||
["/projects/by-token/:token" {:get (handler #'api-projects/get-by-share-token)}]
|
||||
["/projects/:id" {:put (handler #'api-projects/update)
|
||||
:delete (handler #'api-projects/delete)}]
|
||||
["/pages" {:get (handler #'api-pages/list)}]
|
||||
["/pages/:id" {:put (handler #'api-pages/update)
|
||||
:delete (handler #'api-pages/delete)}]
|
||||
["/pages/:id/metatata" {:put (handler #'api-pages/update-metadata)}]
|
||||
["/pages/:id/history" {:get (handler #'api-pages/retrieve-history)}]
|
||||
]]
|
||||
|
||||
{;;:reitit.middleware/transform dev/print-request-diffs
|
||||
:data {:muuntaja (m/create
|
||||
(update-in m/default-options [:formats "application/transit+json"]
|
||||
merge {:encoder-opts {:handlers t/+write-handlers+}
|
||||
:decoder-opts {:handlers t/+read-handlers+}}))
|
||||
:middleware [
|
||||
;; {:name "CORS Middleware"
|
||||
;; :wrap #(wrap-cors %
|
||||
;; :access-control-allow-origin [#".*"]
|
||||
;; :access-control-allow-methods [:get :put :post :delete]
|
||||
;; :access-control-allow-headers ["x-requested-with"
|
||||
;; "content-type"
|
||||
;; "authorization"])}
|
||||
[wrap-session {:store (cookie-store {:key "a 16-byte secret"})
|
||||
:cookie-name "session"
|
||||
:cookie-attrs {:same-site :lax
|
||||
:http-only true}}]
|
||||
parameters/parameters-middleware
|
||||
api-middleware/normalize-params-middleware
|
||||
;; content-negotiation
|
||||
muuntaja/format-negotiate-middleware
|
||||
;; encoding response body
|
||||
muuntaja/format-response-middleware
|
||||
;; exception handling
|
||||
api-errors/exception-middleware
|
||||
;; decoding request body
|
||||
muuntaja/format-request-middleware
|
||||
;; validation
|
||||
api-middleware/parameters-validation-middleware
|
||||
;; multipart
|
||||
multipart/multipart-middleware]}}))
|
||||
|
||||
(def app
|
||||
(ring/ring-handler routes (ring/create-default-handler)))
|
||||
|
||||
;; --- State Initialization
|
||||
|
||||
(defn- start-server
|
||||
[config]
|
||||
(jetty/run-jetty app {:join? false
|
||||
:async? true
|
||||
:daemon? true
|
||||
:port (:http-server-port config)}))
|
||||
|
||||
(defstate server
|
||||
:start (start-server cfg/config)
|
||||
:stop (.stop server))
|
36
backend/src/uxbox/api/auth.clj
Normal file
36
backend/src/uxbox/api/auth.clj
Normal file
|
@ -0,0 +1,36 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.api.auth
|
||||
(:require [clojure.spec.alpha :as s]
|
||||
[struct.core :as st]
|
||||
[uxbox.services :as sv]
|
||||
[uxbox.util.http :as http]
|
||||
[uxbox.util.spec :as us]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
(defn login
|
||||
{:description "User login endpoint"
|
||||
:parameters {:body {:username [st/required st/string]
|
||||
:password [st/required st/string]
|
||||
:scope [st/required st/string]}}}
|
||||
[ctx]
|
||||
(let [data (get-in ctx [:parameters :body])
|
||||
user @(sv/novelty (assoc data :type :login))]
|
||||
(-> (http/no-content)
|
||||
(assoc :session {:user-id (get user :id)}))))
|
||||
|
||||
(defn authorization-middleware
|
||||
[handler]
|
||||
(fn
|
||||
([request]
|
||||
(if-let [identity (get-in request [:session :user-id])]
|
||||
(handler (assoc request :identity identity :user identity))
|
||||
(http/forbidden nil)))
|
||||
([request respond raise]
|
||||
(if-let [identity (get-in request [:session :user-id])]
|
||||
(handler (assoc request :identity identity :user identity) respond raise)
|
||||
(respond (http/forbidden nil))))))
|
86
backend/src/uxbox/api/errors.clj
Normal file
86
backend/src/uxbox/api/errors.clj
Normal file
|
@ -0,0 +1,86 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 20162019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.api.errors
|
||||
"A errors handling for api."
|
||||
(:require [reitit.ring.middleware.exception :as exception]))
|
||||
|
||||
|
||||
(defmulti handle-exception #(:type (ex-data %)))
|
||||
|
||||
(defmethod handle-exception :validation
|
||||
[err]
|
||||
;; (println "\n*********** stack trace ***********")
|
||||
;; (.printStackTrace err)
|
||||
;; (println "\n********* end stack trace *********")
|
||||
(let [response (ex-data err)]
|
||||
{:status 400
|
||||
:body response}))
|
||||
|
||||
(defmethod handle-exception :default
|
||||
[err]
|
||||
;; (println "\n*********** stack trace ***********")
|
||||
;; (.printStackTrace err)
|
||||
;; (println "\n********* end stack trace *********")
|
||||
(let [response (ex-data err)]
|
||||
{:status 500
|
||||
:body response}))
|
||||
|
||||
;; --- Entry Point
|
||||
|
||||
(defn- handle-data-access-exception
|
||||
[err]
|
||||
(let [err (.getCause err)
|
||||
state (.getSQLState err)
|
||||
message (.getMessage err)]
|
||||
(case state
|
||||
"P0002"
|
||||
{:status 412 ;; precondition-failed
|
||||
:body {:message message
|
||||
:payload nil
|
||||
:type :occ}}
|
||||
|
||||
(do
|
||||
{:status 500
|
||||
:message {:message message
|
||||
:type :unexpected
|
||||
:payload nil}}))))
|
||||
|
||||
(defn- handle-unexpected-exception
|
||||
[err]
|
||||
(let [message (.getMessage err)]
|
||||
{:status 500
|
||||
:body {:message message
|
||||
:type :unexpected
|
||||
:payload nil}}))
|
||||
|
||||
(defn errors-handler
|
||||
[error context]
|
||||
(cond
|
||||
(instance? clojure.lang.ExceptionInfo error)
|
||||
(handle-exception error)
|
||||
|
||||
(instance? java.util.concurrent.CompletionException error)
|
||||
(errors-handler context (.getCause error))
|
||||
|
||||
(instance? org.jooq.exception.DataAccessException error)
|
||||
(handle-data-access-exception error)
|
||||
|
||||
:else
|
||||
(handle-unexpected-exception error)))
|
||||
|
||||
(defn wrap-print-errors
|
||||
[handler error request]
|
||||
(println "\n*********** stack trace ***********")
|
||||
(.printStackTrace error)
|
||||
(println "\n********* end stack trace *********")
|
||||
(handler error request))
|
||||
|
||||
(def exception-middleware
|
||||
(exception/create-exception-middleware
|
||||
(assoc exception/default-handlers
|
||||
::exception/default errors-handler
|
||||
::exception/wrap wrap-print-errors)))
|
101
backend/src/uxbox/api/middleware.clj
Normal file
101
backend/src/uxbox/api/middleware.clj
Normal file
|
@ -0,0 +1,101 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2016-2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.api.middleware
|
||||
(:require [reitit.core :as rc]
|
||||
[struct.core :as st]
|
||||
[promesa.core :as p]
|
||||
[uxbox.util.data :refer [normalize-attrs]]
|
||||
[uxbox.util.exceptions :as ex]))
|
||||
|
||||
;; (extend-protocol rc/Expand
|
||||
;; clojure.lang.Var
|
||||
;; (expand [this opts]
|
||||
;; (merge (rc/expand (deref this) opts)
|
||||
;; {::handler-metadata (meta this)})))
|
||||
|
||||
(defn transform-handler
|
||||
[handler]
|
||||
(fn [request respond raise]
|
||||
(try
|
||||
(let [response (handler request)]
|
||||
(if (p/promise? response)
|
||||
(-> response
|
||||
(p/then respond)
|
||||
(p/catch raise))
|
||||
(respond response)))
|
||||
(catch Exception e
|
||||
(raise e)))))
|
||||
|
||||
(defn handler
|
||||
[invar]
|
||||
(let [metadata (meta invar)
|
||||
hlrdata (-> metadata
|
||||
(dissoc :arglist :line :column :file :ns)
|
||||
(assoc :handler (transform-handler (var-get invar))
|
||||
:fullname (symbol (str (:ns metadata)) (str (:name metadata)))))]
|
||||
(cond-> hlrdata
|
||||
(:doc metadata) (assoc :description (:doc metadata)))))
|
||||
|
||||
(def normalize-params-middleware
|
||||
{:name ::normalize-params-middleware
|
||||
:wrap (fn [handler]
|
||||
(letfn [(transform-request [request]
|
||||
(if-let [data (get request :query-params)]
|
||||
(assoc request :query-params (normalize-attrs data))
|
||||
request))]
|
||||
(fn
|
||||
([request] (handler (transform-request request)))
|
||||
([request respond raise]
|
||||
(try
|
||||
(try
|
||||
(let [request (transform-request request)]
|
||||
(handler (transform-request request) respond raise))
|
||||
(catch Exception e
|
||||
(raise e))))))))})
|
||||
|
||||
|
||||
;; --- Validation
|
||||
|
||||
(def parameters-validation-middleware
|
||||
(letfn [(prepare [parameters]
|
||||
(reduce-kv
|
||||
(fn [acc key spec]
|
||||
(let [newkey (case key
|
||||
:path :path-params
|
||||
:query :query-params
|
||||
:body :body-params
|
||||
(throw (ex-info "Not supported key on :parameters" {})))]
|
||||
(assoc acc newkey {:key key
|
||||
:fn #(st/validate % spec)})))
|
||||
{} parameters))
|
||||
|
||||
(validate [request parameters debug]
|
||||
(reduce-kv
|
||||
(fn [req key spec]
|
||||
(let [[errors, result] ((:fn spec) (get req key))]
|
||||
(if errors
|
||||
(ex/raise :type :parameters-validation
|
||||
:code (:key spec)
|
||||
:context errors
|
||||
:message "Invalid data")
|
||||
|
||||
(assoc-in req [:parameters (:key spec)] result))))
|
||||
request parameters))]
|
||||
|
||||
{:name ::parameters-validation-middleware
|
||||
:compile (fn [route opts]
|
||||
(when-let [parameters (:parameters route)]
|
||||
(let [parameters (prepare parameters)]
|
||||
(fn [handler]
|
||||
(fn
|
||||
([request]
|
||||
(handler (validate request parameters)))
|
||||
([request respond raise]
|
||||
(try
|
||||
(handler (validate request parameters false) respond raise)
|
||||
(catch Exception e
|
||||
(raise e)))))))))}))
|
89
backend/src/uxbox/api/pages.clj
Normal file
89
backend/src/uxbox/api/pages.clj
Normal file
|
@ -0,0 +1,89 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.api.pages
|
||||
(:require [clojure.spec.alpha :as s]
|
||||
[struct.core :as st]
|
||||
[promesa.core :as p]
|
||||
[uxbox.services :as sv]
|
||||
[uxbox.util.http :as http]
|
||||
[uxbox.util.spec :as us]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
(defn list
|
||||
"List pages in a project"
|
||||
{:parameters {:query {:project [st/required st/uuid-str]}}}
|
||||
[{:keys [user parameters]}]
|
||||
(let [project (get-in parameters [:query :project])
|
||||
message {:user user :project project :type :list-pages-by-project}]
|
||||
(-> (sv/query message)
|
||||
(p/then #(http/ok %)))))
|
||||
|
||||
(defn create
|
||||
"Create page for a project"
|
||||
{:parameters {:body {:data [st/required]
|
||||
:metadata [st/required]
|
||||
:project [st/required st/uuid-str]
|
||||
:name [st/required st/string]
|
||||
:id [st/uuid-str]}}}
|
||||
[{:keys [user parameters]}]
|
||||
(let [data (get parameters :body)
|
||||
message (assoc data :user user :type :create-page)]
|
||||
(->> (sv/novelty message)
|
||||
(p/map (fn [result]
|
||||
(let [loc (str "/api/pages/" (:id result))]
|
||||
(http/created loc result)))))))
|
||||
|
||||
(defn update
|
||||
"Update page"
|
||||
{:parameters {:path {:id [st/required st/uuid-str]}
|
||||
:body {:data [st/required]
|
||||
:metadata [st/required]
|
||||
:project [st/required st/uuid-str]
|
||||
:name [st/required st/string]
|
||||
:version [st/required st/integer]
|
||||
:id [st/uuid-str]}}}
|
||||
[{:keys [user parameters]}]
|
||||
(let [id (get-in parameters [:path :id])
|
||||
data (get parameters :body)
|
||||
message (assoc data :id id :type :update-page :user user)]
|
||||
(->> (sv/novelty message)
|
||||
(p/map #(http/ok %)))))
|
||||
|
||||
(defn update-metadata
|
||||
"Update page metadata"
|
||||
{:parameters {:path {:id [st/required st/uuid-str]}
|
||||
:body {:id [st/required st/uuid-str]
|
||||
:metadata [st/required]
|
||||
:project [st/required st/uuid-str]
|
||||
:name [st/required st/string]}}}
|
||||
[{:keys [user parameters]}]
|
||||
(let [id (get-in parameters [:path :id])
|
||||
data (get parameters :body)
|
||||
message (assoc data :id id :type :update-page-metadata :user user)]
|
||||
(->> (sv/novelty message)
|
||||
(p/map #(http/ok %)))))
|
||||
|
||||
(defn delete
|
||||
{:parameters {:path {:id [st/required st/uuid-str]}}}
|
||||
[{:keys [user parameters]}]
|
||||
(let [id (get-in parameters [:path :id])
|
||||
message {:id id :type :delete-page :user user}]
|
||||
(-> (sv/novelty message)
|
||||
(p/then (fn [v] (http/no-content))))))
|
||||
|
||||
(defn retrieve-history
|
||||
"Retrieve the page history"
|
||||
{:parameters {:path {:id [st/required st/uuid-str]}
|
||||
:query {:max [st/integer-str]
|
||||
:since [st/integer-str]
|
||||
:pinned [st/boolean-str]}}}
|
||||
[{:keys [user parameters]}]
|
||||
(let [id (get-in parameters [:path :id])
|
||||
data (get parameters :query)
|
||||
message (assoc data :id id :type :list-page-history :user user)]
|
||||
(->> (sv/query message)
|
||||
(p/map #(http/ok %)))))
|
65
backend/src/uxbox/api/projects.clj
Normal file
65
backend/src/uxbox/api/projects.clj
Normal file
|
@ -0,0 +1,65 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.api.projects
|
||||
(:require [clojure.spec.alpha :as s]
|
||||
[clojure.pprint :refer [pprint]]
|
||||
[struct.core :as st]
|
||||
[promesa.core :as p]
|
||||
[uxbox.services :as sv]
|
||||
[uxbox.util.http :as http]
|
||||
[uxbox.util.spec :as us]
|
||||
[uxbox.util.uuid :as uuid]
|
||||
[uxbox.util.exceptions :as ex]))
|
||||
|
||||
(defn list
|
||||
{:description "List projects"}
|
||||
[{:keys [user] :as req}]
|
||||
(let [message {:user user :type :list-projects}]
|
||||
(->> (sv/query message)
|
||||
(p/map #(http/ok %)))))
|
||||
|
||||
(defn create
|
||||
"Create project"
|
||||
{:parameters {:body {:name [st/required st/string]
|
||||
:id [st/uuid-str]}}}
|
||||
[{:keys [user parameters] :as req}]
|
||||
(let [data (get parameters :body)
|
||||
message (assoc data :type :create-project :user user)]
|
||||
(->> (sv/novelty message)
|
||||
(p/map (fn [result]
|
||||
(let [loc (str "/api/projects/" (:id result))]
|
||||
(http/created loc result)))))))
|
||||
|
||||
(defn update
|
||||
"Update project"
|
||||
{:parameters {:path {:id [st/required st/uuid-str]}
|
||||
:body {:name [st/required st/string]
|
||||
:version [st/required st/integer]}}}
|
||||
[{:keys [user parameters] :as req}]
|
||||
(let [id (get-in parameters [:path :id])
|
||||
data (get parameters :body)
|
||||
message (assoc data :id id :type :update-project :user user)]
|
||||
(-> (sv/novelty message)
|
||||
(p/then #(http/ok %)))))
|
||||
|
||||
(defn delete
|
||||
"Delete project"
|
||||
{:parameters {:path {:id [st/required st/uuid-str]}}}
|
||||
[{:keys [user parameters] :as req}]
|
||||
(let [id (get-in parameters [:path :id])
|
||||
message {:id id :type :delete-project :user user}]
|
||||
(-> (sv/novelty message)
|
||||
(p/then (fn [v] (http/no-content))))))
|
||||
|
||||
(defn get-by-share-token
|
||||
"Get a project by shared token"
|
||||
{:parameters {:path {:token [st/required st/string]}}}
|
||||
[{:keys [user parameters] :as req}]
|
||||
(let [message {:token (get-in parameters [:path :token])
|
||||
:type :retrieve-project-by-share-token}]
|
||||
(->> (sv/query message)
|
||||
(p/map #(http/ok %)))))
|
|
@ -23,7 +23,6 @@
|
|||
[uxbox.frontend.kvstore :as kvstore]
|
||||
[uxbox.frontend.svgparse :as svgparse]
|
||||
[uxbox.frontend.debug-emails :as dbgemails]
|
||||
[uxbox.services.auth :refer [auth-opts]]
|
||||
[uxbox.util.response :refer [rsp]]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
|
@ -53,6 +52,9 @@
|
|||
|
||||
;; --- Routes
|
||||
|
||||
(def auth-opts
|
||||
{:alg :a256kw :enc :a128cbc-hs256})
|
||||
|
||||
(defn routes
|
||||
([] (routes cfg/config))
|
||||
([config]
|
||||
|
@ -157,6 +159,6 @@
|
|||
:max-body-size 52428800}]
|
||||
(ct/run-server (routes config) config)))
|
||||
|
||||
(defstate server
|
||||
:start (start-server cfg/config)
|
||||
:stop (.stop server))
|
||||
;; (defstate server
|
||||
;; :start (start-server cfg/config)
|
||||
;; :stop (.stop server))
|
||||
|
|
|
@ -8,42 +8,24 @@
|
|||
(:require [clojure.spec.alpha :as s]
|
||||
[suricatta.core :as sc]
|
||||
[buddy.hashers :as hashers]
|
||||
[buddy.sign.jwt :as jwt]
|
||||
[buddy.core.hash :as hash]
|
||||
[uxbox.config :as cfg]
|
||||
[uxbox.util.spec :as us]
|
||||
[uxbox.db :as db]
|
||||
[uxbox.services.core :as core]
|
||||
[uxbox.services.users :as users]
|
||||
[uxbox.util.exceptions :as ex]))
|
||||
|
||||
(def auth-opts
|
||||
{:alg :a256kw :enc :a128cbc-hs256})
|
||||
|
||||
;; --- Login
|
||||
|
||||
(defn- check-user-password
|
||||
[user password]
|
||||
(hashers/check password (:password user)))
|
||||
|
||||
(defn generate-token
|
||||
[user]
|
||||
(let [data {:id (:id user) :scope :auth}]
|
||||
(jwt/encrypt data cfg/secret auth-opts)))
|
||||
|
||||
(s/def ::scope string?)
|
||||
(s/def ::login
|
||||
(s/keys :req-un [::us/username ::us/password ::scope]))
|
||||
|
||||
(defmethod core/novelty :login
|
||||
[{:keys [username password scope] :as params}]
|
||||
(s/assert ::login params)
|
||||
(with-open [conn (db/connection)]
|
||||
(let [user (users/find-user-by-username-or-email conn username)]
|
||||
(when-not user
|
||||
(ex/raise :type :validation
|
||||
:code ::wrong-credentials))
|
||||
(if (check-user-password user password)
|
||||
{:token (generate-token user)}
|
||||
(when-not (check-user-password user password)
|
||||
(ex/raise :type :validation
|
||||
:code ::wrong-credentials)))))
|
||||
:code ::wrong-credentials))
|
||||
user)))
|
||||
|
|
216
backend/src/uxbox/util/http.clj
Normal file
216
backend/src/uxbox/util/http.clj
Normal file
|
@ -0,0 +1,216 @@
|
|||
;; This Source Code Form is subject to the terms of the Mozilla Public
|
||||
;; License, v. 2.0. If a copy of the MPL was not distributed with this
|
||||
;; file, You can obtain one at http://mozilla.org/MPL/2.0/.
|
||||
;;
|
||||
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.util.http)
|
||||
|
||||
(defn response
|
||||
"Create a response instance."
|
||||
([body] (response body 200 {}))
|
||||
([body status] (response body status {}))
|
||||
([body status headers] {:body body :status status :headers headers}))
|
||||
|
||||
(defn response?
|
||||
[resp]
|
||||
(and (map? resp)
|
||||
(integer? (:status resp))
|
||||
(map? (:headers resp))))
|
||||
|
||||
(defn continue
|
||||
([body] (response body 100))
|
||||
([body headers] (response body 100 headers)))
|
||||
|
||||
(defn ok
|
||||
"HTTP 200 OK
|
||||
Should be used to indicate nonspecific success. Must not be used to
|
||||
communicate errors in the response body.
|
||||
|
||||
In most cases, 200 is the code the client hopes to see. It indicates that
|
||||
the REST API successfully carried out whatever action the client requested,
|
||||
and that no more specific code in the 2xx series is appropriate. Unlike
|
||||
the 204 status code, a 200 response should include a response body."
|
||||
([body] (response body 200))
|
||||
([body headers] (response body 200 headers)))
|
||||
|
||||
(defn created
|
||||
"HTTP 201 Created
|
||||
Must be used to indicate successful resource creation.
|
||||
|
||||
A REST API responds with the 201 status code whenever a collection creates,
|
||||
or a store adds, a new resource at the client's request. There may also be
|
||||
times when a new resource is created as a result of some controller action,
|
||||
in which case 201 would also be an appropriate response."
|
||||
([location] (response "" 201 {"location" location}))
|
||||
([location body] (response body 201 {"location" location}))
|
||||
([location body headers] (response body 201 (merge headers {"location" location}))))
|
||||
|
||||
(defn accepted
|
||||
"HTTP 202 Accepted
|
||||
Must be used to indicate successful start of an asynchronous action.
|
||||
|
||||
A 202 response indicates that the client's request will be handled
|
||||
asynchronously. This response status code tells the client that the request
|
||||
appears valid, but it still may have problems once it's finally processed.
|
||||
A 202 response is typically used for actions that take a long while to
|
||||
process."
|
||||
([body] (response body 202))
|
||||
([body headers] (response body 202 headers)))
|
||||
|
||||
(defn no-content
|
||||
"HTTP 204 No Content
|
||||
Should be used when the response body is intentionally empty.
|
||||
|
||||
The 204 status code is usually sent out in response to a PUT, POST, or
|
||||
DELETE request, when the REST API declines to send back any status message
|
||||
or representation in the response message's body. An API may also send 204
|
||||
in conjunction with a GET request to indicate that the requested resource
|
||||
exists, but has no state representation to include in the body."
|
||||
([] (response "" 204))
|
||||
([headers] (response "" 204 headers)))
|
||||
|
||||
(defn moved-permanently
|
||||
"301 Moved Permanently
|
||||
Should be used to relocate resources.
|
||||
|
||||
The 301 status code indicates that the REST API's resource model has been
|
||||
significantly redesigned and a new permanent URI has been assigned to the
|
||||
client's requested resource. The REST API should specify the new URI in
|
||||
the response's Location header."
|
||||
([location] (response "" 301 {"location" location}))
|
||||
([location body] (response body 301 {"location" location}))
|
||||
([location body headers] (response body 301 (merge headers {"location" location}))))
|
||||
|
||||
(defn found
|
||||
"HTTP 302 Found
|
||||
Should not be used.
|
||||
|
||||
The intended semantics of the 302 response code have been misunderstood
|
||||
by programmers and incorrectly implemented in programs since version 1.0
|
||||
of the HTTP protocol.
|
||||
The confusion centers on whether it is appropriate for a client to always
|
||||
automatically issue a follow-up GET request to the URI in response's
|
||||
Location header, regardless of the original request's method. For the
|
||||
record, the intent of 302 is that this automatic redirect behavior only
|
||||
applies if the client's original request used either the GET or HEAD
|
||||
method.
|
||||
|
||||
To clear things up, HTTP 1.1 introduced status codes 303 (\"See Other\")
|
||||
and 307 (\"Temporary Redirect\"), either of which should be used
|
||||
instead of 302."
|
||||
([location] (response "" 302 {"location" location}))
|
||||
([location body] (response body 302 {"location" location}))
|
||||
([location body headers] (response body 302 (merge headers {"location" location}))))
|
||||
|
||||
(defn see-other
|
||||
"HTTP 303 See Other
|
||||
Should be used to refer the client to a different URI.
|
||||
|
||||
A 303 response indicates that a controller resource has finished its work,
|
||||
but instead of sending a potentially unwanted response body, it sends the
|
||||
client the URI of a response resource. This can be the URI of a temporary
|
||||
status message, or the URI to some already existing, more permanent,
|
||||
resource.
|
||||
Generally speaking, the 303 status code allows a REST API to send a
|
||||
reference to a resource without forcing the client to download its state.
|
||||
Instead, the client may send a GET request to the value of the Location
|
||||
header."
|
||||
([location] (response "" 303 {"location" location}))
|
||||
([location body] (response body 303 {"location" location}))
|
||||
([location body headers] (response body 303 (merge headers {"location" location}))))
|
||||
|
||||
(defn temporary-redirect
|
||||
"HTTP 307 Temporary Redirect
|
||||
Should be used to tell clients to resubmit the request to another URI.
|
||||
|
||||
HTTP/1.1 introduced the 307 status code to reiterate the originally
|
||||
intended semantics of the 302 (\"Found\") status code. A 307 response
|
||||
indicates that the REST API is not going to process the client's request.
|
||||
Instead, the client should resubmit the request to the URI specified by
|
||||
the response message's Location header.
|
||||
|
||||
A REST API can use this status code to assign a temporary URI to the
|
||||
client's requested resource. For example, a 307 response can be used to
|
||||
shift a client request over to another host."
|
||||
([location] (response "" 307 {"location" location}))
|
||||
([location body] (response body 307 {"location" location}))
|
||||
([location body headers] (response body 307 (merge headers {"location" location}))))
|
||||
|
||||
(defn bad-request
|
||||
"HTTP 400 Bad Request
|
||||
May be used to indicate nonspecific failure.
|
||||
|
||||
400 is the generic client-side error status, used when no other 4xx error
|
||||
code is appropriate."
|
||||
([body] (response body 400))
|
||||
([body headers] (response body 400 headers)))
|
||||
|
||||
(defn unauthorized
|
||||
"HTTP 401 Unauthorized
|
||||
Must be used when there is a problem with the client credentials.
|
||||
|
||||
A 401 error response indicates that the client tried to operate on a
|
||||
protected resource without providing the proper authorization. It may have
|
||||
provided the wrong credentials or none at all."
|
||||
([body] (response body 401))
|
||||
([body headers] (response body 401 headers)))
|
||||
|
||||
(defn forbidden
|
||||
"HTTP 403 Forbidden
|
||||
Should be used to forbid access regardless of authorization state.
|
||||
|
||||
A 403 error response indicates that the client's request is formed
|
||||
correctly, but the REST API refuses to honor it. A 403 response is not a
|
||||
case of insufficient client credentials; that would be 401 (\"Unauthorized\").
|
||||
REST APIs use 403 to enforce application-level permissions. For example, a
|
||||
client may be authorized to interact with some, but not all of a REST API's
|
||||
resources. If the client attempts a resource interaction that is outside of
|
||||
its permitted scope, the REST API should respond with 403."
|
||||
([body] (response body 403))
|
||||
([body headers] (response body 403 headers)))
|
||||
|
||||
(defn not-found
|
||||
"HTTP 404 Not Found
|
||||
Must be used when a client's URI cannot be mapped to a resource.
|
||||
|
||||
The 404 error status code indicates that the REST API can't map the
|
||||
client's URI to a resource."
|
||||
([body] (response body 404))
|
||||
([body headers] (response body 404 headers)))
|
||||
|
||||
(defn method-not-allowed
|
||||
([body] (response body 405))
|
||||
([body headers] (response body 405 headers)))
|
||||
|
||||
(defn not-acceptable
|
||||
([body] (response body 406))
|
||||
([body headers] (response body 406 headers)))
|
||||
|
||||
(defn conflict
|
||||
([body] (response body 409))
|
||||
([body headers] (response body 409 headers)))
|
||||
|
||||
(defn gone
|
||||
([body] (response body 410))
|
||||
([body headers] (response body 410 headers)))
|
||||
|
||||
(defn precondition-failed
|
||||
([body] (response body 412))
|
||||
([body headers] (response body 412 headers)))
|
||||
|
||||
(defn unsupported-mediatype
|
||||
([body] (response body 415))
|
||||
([body headers] (response body 415 headers)))
|
||||
|
||||
(defn too-many-requests
|
||||
([body] (response body 429))
|
||||
([body headers] (response body 429 headers)))
|
||||
|
||||
(defn internal-server-error
|
||||
([body] (response body 500))
|
||||
([body headers] (response body 500 headers)))
|
||||
|
||||
(defn not-implemented
|
||||
([body] (response body 501))
|
||||
([body headers] (response body 501 headers)))
|
|
@ -15,11 +15,8 @@
|
|||
|
||||
;; --- Handlers
|
||||
|
||||
(def ^:private +reader-handlers+
|
||||
dt/+read-handlers+)
|
||||
|
||||
(def ^:private +write-handlers+
|
||||
dt/+write-handlers+)
|
||||
(def +read-handlers+ dt/+read-handlers+)
|
||||
(def +write-handlers+ dt/+write-handlers+)
|
||||
|
||||
;; --- Low-Level Api
|
||||
|
||||
|
@ -27,7 +24,7 @@
|
|||
([istream]
|
||||
(reader istream nil))
|
||||
([istream {:keys [type] :or {type :json}}]
|
||||
(t/reader istream type {:handlers +reader-handlers+})))
|
||||
(t/reader istream type {:handlers +read-handlers+})))
|
||||
|
||||
(defn read!
|
||||
"Read value from streamed transit reader."
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue