🎉 Add entrypoint for autogenerated api docs.

This commit is contained in:
Andrey Antukh 2021-10-19 15:33:49 +02:00 committed by Andrés Moya
parent a7241d4128
commit 55784f64b8
11 changed files with 328 additions and 38 deletions

View file

@ -10,6 +10,7 @@
[app.common.exceptions :as ex]
[app.common.logging :as l]
[app.common.spec :as us]
[app.http.doc :as doc]
[app.http.errors :as errors]
[app.http.middleware :as middleware]
[app.metrics :as mtx]
@ -151,6 +152,8 @@
[middleware/errors errors/handle]
[middleware/cookies]]}
["/_doc" {:get (doc/handler rpc)}]
["/feedback" {:middleware [(:middleware session)]
:post feedback}]
["/auth/oauth/:provider" {:post (:handler oauth)}]

View file

@ -0,0 +1,53 @@
;; 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) UXBOX Labs SL
(ns app.http.doc
"API autogenerated documentation."
(:require
[app.common.data :as d]
[app.config :as cf]
[app.util.services :as sv]
[app.util.template :as tmpl]
[clojure.java.io :as io]
[clojure.spec.alpha :as s]
[pretty-spec.core :as ps]))
(defn get-spec-str
[k]
(with-out-str
(ps/pprint (s/form k)
{:ns-aliases {"clojure.spec.alpha" "s"
"clojure.core.specs.alpha" "score"
"clojure.core" nil}})))
(defn prepare-context
[rpc]
(letfn [(gen-doc [type [name f]]
(let [mdata (meta f)]
;; (prn name mdata)
{:type (d/name type)
:name (d/name name)
:auth (:auth mdata true)
:docs (::sv/docs mdata)
:spec (get-spec-str (::sv/spec mdata))}))]
{:query-methods
(into []
(map (partial gen-doc :query))
(->> rpc :methods :query (sort-by first)))
:mutation-methods
(into []
(map (partial gen-doc :mutation))
(->> rpc :methods :mutation (sort-by first)))}))
(defn handler
[rpc]
(let [context (prepare-context rpc)]
(if (contains? cf/flags :api-doc)
(fn [_]
{:status 200
:body (-> (io/resource "api-doc.tmpl")
(tmpl/render context))})
(constantly {:status 404 :body ""}))))

View file

@ -97,37 +97,39 @@
auth? (:auth mdata true)]
(l/trace :action "register" :name (::sv/name mdata))
(fn [params]
(with-meta
(fn [params]
;; Raise authentication error when rpc method requires auth but
;; no profile-id is found in the request.
(when (and auth? (not (uuid? (:profile-id params))))
(ex/raise :type :authentication
:code :authentication-required
:hint "authentication required for this endpoint"))
;; Raise authentication error when rpc method requires auth but
;; no profile-id is found in the request.
(when (and auth? (not (uuid? (:profile-id params))))
(ex/raise :type :authentication
:code :authentication-required
:hint "authentication required for this endpoint"))
(let [params' (dissoc params ::request)
params' (us/conform spec params')
result (f cfg params')]
(let [params' (dissoc params ::request)
params' (us/conform spec params')
result (f cfg params')]
;; When audit log is enabled (default false).
(when (fn? audit)
(let [resultm (meta result)
request (::request params)
profile-id (or (:profile-id params')
(:profile-id result)
(::audit/profile-id resultm))
props (d/merge params' (::audit/props resultm))]
(audit :cmd :submit
:type (or (::audit/type resultm)
(::type cfg))
:name (or (::audit/name resultm)
(::sv/name mdata))
:profile-id profile-id
:ip-addr (audit/parse-client-ip request)
:props props)))
;; When audit log is enabled (default false).
(when (fn? audit)
(let [resultm (meta result)
request (::request params)
profile-id (or (:profile-id params')
(:profile-id result)
(::audit/profile-id resultm))
props (d/merge params' (::audit/props resultm))]
(audit :cmd :submit
:type (or (::audit/type resultm)
(::type cfg))
:name (or (::audit/name resultm)
(::sv/name mdata))
:profile-id profile-id
:ip-addr (audit/parse-client-ip request)
:props props)))
result))))
result))
mdata)))
(defn- process-method
[cfg vfn]

View file

@ -31,6 +31,11 @@
:opt-un [::pages]))
(sv/defmethod ::create-share-link
"Creates a share-link object.
Share links are resources that allows external users access to
specific files with specific permissions (flags)."
[{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}]
(db/with-atomic [conn pool]
(files/check-edition-permissions! conn profile-id file-id)

View file

@ -204,6 +204,7 @@
(s/keys :req-un [::profile-id ::id]))
(sv/defmethod ::file
"Retrieve a file by its ID. Only authenticated users."
[{:keys [pool] :as cfg} {:keys [profile-id id] :as params}]
(db/with-atomic [conn pool]
(let [cfg (assoc cfg :conn conn)

View file

@ -7,21 +7,34 @@
(ns app.util.services
"A helpers and macros for define rpc like registry based services."
(:refer-clojure :exclude [defmethod])
(:require [app.common.data :as d]))
(:require
[app.common.data :as d]
[cuerdas.core :as str]))
(defmacro defmethod
[sname & body]
(let [[mdata args body] (if (map? (first body))
[(first body) (first (rest body)) (drop 2 body)]
[nil (first body) (rest body)])
mdata (assoc mdata
::spec sname
::name (name sname))
(let [[docs body] (if (string? (first body))
[(first body) (rest body)]
[nil body])
[mdata body] (if (map? (first body))
[(first body) (rest body)]
[nil body])
sym (symbol (str "sm$" (name sname)))]
`(do
(def ~sym (fn ~args ~@body))
(reset-meta! (var ~sym) ~mdata))))
[args body] (if (vector? (first body))
[(first body) (rest body)]
[nil body])]
(when-not args
(throw (IllegalArgumentException. "Missing arguments on `defmethod` macro.")))
(let [mdata (assoc mdata
::docs (some-> docs str/<<-)
::spec sname
::name (name sname))
sym (symbol (str "sm$" (name sname)))]
`(do
(def ~sym (fn ~args ~@body))
(reset-meta! (var ~sym) ~mdata)))))
(def nsym-xf
(comp