mirror of
https://github.com/penpot/penpot.git
synced 2025-07-30 09:28:24 +02:00
♻️ Refactor services (for add the project-file concept.
And fix many tests.
This commit is contained in:
parent
af62d949d8
commit
183f0a5400
40 changed files with 1279 additions and 1006 deletions
|
@ -50,7 +50,7 @@
|
|||
:email-reply-to (lookup-env env :uxbox-email-reply-to "no-reply@uxbox.io")
|
||||
:email-from (lookup-env env :uxbox-email-from "no-reply@uxbox.io")
|
||||
|
||||
:smtp-host (lookup-env env :uxbox-smtp-host "smtp")
|
||||
:smtp-host (lookup-env env :uxbox-smtp-host "localhost")
|
||||
:smtp-port (lookup-env env :uxbox-smtp-port 25)
|
||||
:smtp-user (lookup-env env :uxbox-smtp-user nil)
|
||||
:smtp-password (lookup-env env :uxbox-smtp-password nil)
|
||||
|
|
|
@ -13,6 +13,7 @@
|
|||
[uxbox.config :as cfg]
|
||||
[uxbox.core :refer [system]]
|
||||
[uxbox.util.data :as data]
|
||||
[uxbox.util.exceptions :as ex]
|
||||
[uxbox.util.pgsql :as pg]
|
||||
[vertx.core :as vx])
|
||||
(:import io.vertx.core.buffer.Buffer))
|
||||
|
@ -33,17 +34,22 @@
|
|||
:start (create-pool cfg/config system))
|
||||
|
||||
(defmacro with-atomic
|
||||
[& args]
|
||||
`(pg/with-atomic ~@args))
|
||||
[bindings & args]
|
||||
`(pg/with-atomic ~bindings (p/do! ~@args)))
|
||||
|
||||
(def row-xfm
|
||||
(comp (map pg/row->map)
|
||||
(map data/normalize-attrs)))
|
||||
|
||||
(defmacro query
|
||||
[& args]
|
||||
`(pg/query ~@args {:xfm row-xfm}))
|
||||
|
||||
[conn sql]
|
||||
`(-> (pg/query ~conn ~sql {:xfm row-xfm})
|
||||
(p/catch' (fn [err#]
|
||||
(ex/raise :type :database-error
|
||||
:cause err#)))))
|
||||
(defmacro query-one
|
||||
[& args]
|
||||
`(pg/query-one ~@args {:xfm row-xfm}))
|
||||
[conn sql]
|
||||
`(-> (pg/query-one ~conn ~sql {:xfm row-xfm})
|
||||
(p/catch' (fn [err#]
|
||||
(ex/raise :type :database-error
|
||||
:cause err#)))))
|
||||
|
|
|
@ -7,6 +7,7 @@
|
|||
(ns uxbox.fixtures
|
||||
"A initial fixtures."
|
||||
(:require
|
||||
[clojure.tools.logging :as log]
|
||||
[buddy.hashers :as hashers]
|
||||
[mount.core :as mount]
|
||||
[promesa.core :as p]
|
||||
|
@ -20,26 +21,27 @@
|
|||
|
||||
(defn- mk-uuid
|
||||
[prefix & args]
|
||||
(uuid/namespaced uuid/oid (apply str prefix args)))
|
||||
(uuid/namespaced uuid/oid (apply str prefix (interpose "-" args))))
|
||||
|
||||
;; --- Users creation
|
||||
|
||||
(def create-user-sql
|
||||
"insert into users (id, fullname, username, email, password, metadata, photo)
|
||||
values ($1, $2, $3, $4, $5, $6, $7)
|
||||
"insert into users (id, fullname, username, email, password, photo)
|
||||
values ($1, $2, $3, $4, $5, $6)
|
||||
returning *;")
|
||||
|
||||
(def password (hashers/encrypt "123123"))
|
||||
|
||||
(defn create-user
|
||||
[conn i]
|
||||
(println "create user" i)
|
||||
(db/query-one conn [create-user-sql
|
||||
(mk-uuid "user" i)
|
||||
(str "User " i)
|
||||
(str "user" i)
|
||||
(str "user" i ".test@uxbox.io")
|
||||
(hashers/encrypt "123123")
|
||||
(blob/encode {})
|
||||
""]))
|
||||
[conn user-index]
|
||||
(log/info "create user" user-index)
|
||||
(let [sql create-user-sql
|
||||
id (mk-uuid "user" user-index)
|
||||
fullname (str "User " user-index)
|
||||
username (str "user" user-index)
|
||||
email (str "user" user-index ".test@uxbox.io")
|
||||
photo ""]
|
||||
(db/query-one conn [sql id fullname username email password photo])))
|
||||
|
||||
;; --- Projects creation
|
||||
|
||||
|
@ -49,29 +51,46 @@
|
|||
returning *;")
|
||||
|
||||
(defn create-project
|
||||
[conn [pjid uid]]
|
||||
(println "create project" pjid "(for user=" uid ")")
|
||||
(db/query-one conn [create-project-sql
|
||||
(mk-uuid "project" pjid uid)
|
||||
(mk-uuid "user" uid)
|
||||
(str "sample project " pjid)]))
|
||||
[conn [project-index user-index]]
|
||||
(log/info "create project" user-index project-index)
|
||||
(let [sql create-project-sql
|
||||
id (mk-uuid "project" project-index user-index)
|
||||
user-id (mk-uuid "user" user-index)
|
||||
name (str "sample project " project-index)]
|
||||
(db/query-one conn [sql id user-id name])))
|
||||
|
||||
;; --- Pages creation
|
||||
;; --- Create Page Files
|
||||
|
||||
(def create-file-sql
|
||||
"insert into project_files (id, user_id, project_id, name)
|
||||
values ($1, $2, $3, $4) returning id")
|
||||
|
||||
(defn create-file
|
||||
[conn [file-index project-index user-index]]
|
||||
(log/info "create page file" user-index project-index file-index)
|
||||
(let [sql create-file-sql
|
||||
id (mk-uuid "page-file" file-index project-index user-index)
|
||||
user-id (mk-uuid "user" user-index)
|
||||
project-id (mk-uuid "project" project-index user-index)
|
||||
name (str "Sample file " file-index)]
|
||||
(db/query-one conn [sql id user-id project-id name])))
|
||||
|
||||
;; --- Create Pages
|
||||
|
||||
(def create-page-sql
|
||||
"insert into pages (id, user_id, project_id, name,
|
||||
"insert into project_pages (id, user_id, file_id, name,
|
||||
version, ordering, data, metadata)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, $8)
|
||||
returning id;")
|
||||
|
||||
(def create-page-history-sql
|
||||
"insert into pages_history (page_id, user_id, version, data, metadata)
|
||||
values ($1, $2, $3, $4, $5)
|
||||
"insert into project_page_history (page_id, user_id, version, data)
|
||||
values ($1, $2, $3, $4)
|
||||
returning id;")
|
||||
|
||||
(defn create-page
|
||||
[conn [pjid paid uid]]
|
||||
(println "create page" paid "(for project=" pjid ", user=" uid ")")
|
||||
[conn [page-index file-index project-index user-index]]
|
||||
(log/info "create page" user-index project-index file-index page-index)
|
||||
(let [canvas {:id (mk-uuid "canvas" 1)
|
||||
:name "Canvas-1"
|
||||
:type :canvas
|
||||
|
@ -82,29 +101,61 @@
|
|||
data {:shapes []
|
||||
:canvas [(:id canvas)]
|
||||
:shapes-by-id {(:id canvas) canvas}}
|
||||
|
||||
sql1 create-page-sql
|
||||
sql2 create-page-history-sql
|
||||
|
||||
id (mk-uuid "page" page-index file-index project-index user-index)
|
||||
user-id (mk-uuid "user" user-index)
|
||||
file-id (mk-uuid "page-file" file-index project-index user-index)
|
||||
name (str "page " page-index)
|
||||
version 0
|
||||
ordering page-index
|
||||
data (blob/encode data)
|
||||
mdata (blob/encode {})]
|
||||
(p/do!
|
||||
(db/query-one conn [create-page-sql
|
||||
(mk-uuid "page" pjid paid uid)
|
||||
(mk-uuid "user" uid)
|
||||
(mk-uuid "project" pjid uid)
|
||||
(str "page " paid)
|
||||
0
|
||||
paid
|
||||
data
|
||||
mdata])
|
||||
(db/query-one conn [create-page-history-sql
|
||||
(mk-uuid "page" pjid paid uid)
|
||||
(mk-uuid "user" uid)
|
||||
0
|
||||
data
|
||||
mdata]))))
|
||||
(db/query-one conn [sql1 id user-id file-id name version ordering data mdata])
|
||||
#_(db/query-one conn [sql2 id user-id version data]))))
|
||||
|
||||
(def preset-small
|
||||
{:users 50
|
||||
:projects 5
|
||||
:files 5
|
||||
:pages 3})
|
||||
|
||||
(def num-users 5)
|
||||
(def num-projects 5)
|
||||
(def num-pages 5)
|
||||
(def preset-medium
|
||||
{:users 500
|
||||
:projects 20
|
||||
:files 5
|
||||
:pages 3})
|
||||
|
||||
(def preset-big
|
||||
{:users 5000
|
||||
:projects 50
|
||||
:files 5
|
||||
:pages 4})
|
||||
|
||||
(defn run
|
||||
[opts]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(p/do!
|
||||
(p/run! #(create-user conn %) (range (:users opts)))
|
||||
(p/run! #(create-project conn %)
|
||||
(for [user-index (range (:users opts))
|
||||
project-index (range (:projects opts))]
|
||||
[project-index user-index]))
|
||||
(p/run! #(create-file conn %)
|
||||
(for [user-index (range (:users opts))
|
||||
project-index (range (:projects opts))
|
||||
file-index (range (:files opts))]
|
||||
[file-index project-index user-index]))
|
||||
(p/run! #(create-page conn %)
|
||||
(for [user-index (range (:users opts))
|
||||
project-index (range (:projects opts))
|
||||
file-index (range (:files opts))
|
||||
page-index (range (:pages opts))]
|
||||
[page-index file-index project-index user-index]))
|
||||
(p/promise nil))))
|
||||
|
||||
(defn -main
|
||||
[& args]
|
||||
|
@ -115,18 +166,12 @@
|
|||
#'uxbox.db/pool
|
||||
#'uxbox.migrations/migrations})
|
||||
(mount/start))
|
||||
@(db/with-atomic [conn db/pool]
|
||||
(p/do!
|
||||
(p/run! #(create-user conn %) (range num-users))
|
||||
(p/run! #(create-project conn %)
|
||||
(for [uid (range num-users)
|
||||
pjid (range num-projects)]
|
||||
[pjid uid]))
|
||||
(p/run! #(create-page conn %)
|
||||
(for [pjid(range num-projects)
|
||||
paid (range num-pages)
|
||||
uid (range num-users)]
|
||||
[pjid paid uid]))
|
||||
(p/promise 1)))
|
||||
(let [preset (case (first args)
|
||||
(nil "small") preset-small
|
||||
"medium" preset-medium
|
||||
"big" preset-big
|
||||
preset-small)]
|
||||
(log/info "Using preset:" (pr-str preset))
|
||||
(deref (run preset)))
|
||||
(finally
|
||||
(mount/stop))))
|
||||
|
|
|
@ -8,6 +8,7 @@
|
|||
"A errors handling for the http server."
|
||||
(:require
|
||||
[clojure.tools.logging :as log]
|
||||
[cuerdas.core :as str]
|
||||
[io.aviso.exception :as e]))
|
||||
|
||||
(defmulti handle-exception
|
||||
|
@ -16,9 +17,18 @@
|
|||
|
||||
(defmethod handle-exception :validation
|
||||
[err req]
|
||||
(let [response (ex-data err)]
|
||||
{:status 400
|
||||
:body response}))
|
||||
(let [header (get-in req [:headers "accept"])
|
||||
response (ex-data err)]
|
||||
(cond
|
||||
(and (str/starts-with? header "text/html")
|
||||
(= :spec-validation (:code response)))
|
||||
{:status 400
|
||||
:headers {"content-type" "text/html"}
|
||||
:body (str "<pre style='font-size:16px'>" (:explain response) "</pre>\n")}
|
||||
|
||||
:else
|
||||
{:status 400
|
||||
:body response})))
|
||||
|
||||
(defmethod handle-exception :not-found
|
||||
[err req]
|
||||
|
@ -26,6 +36,10 @@
|
|||
{:status 404
|
||||
:body response}))
|
||||
|
||||
(defmethod handle-exception :service-error
|
||||
[err req]
|
||||
(handle-exception (.getCause err) req))
|
||||
|
||||
(defmethod handle-exception :parse
|
||||
[err req]
|
||||
{:status 400
|
||||
|
|
|
@ -26,18 +26,15 @@
|
|||
{:desc "Initial projects tables"
|
||||
:name "0003-projects"
|
||||
:fn (mg/resource "migrations/0003.projects.sql")}
|
||||
{:desc "Initial pages tables"
|
||||
:name "0004-pages"
|
||||
:fn (mg/resource "migrations/0004.pages.sql")}
|
||||
{:desc "Initial emails related tables"
|
||||
:name "0005-emails"
|
||||
:fn (mg/resource "migrations/0005.emails.sql")}
|
||||
:name "0004-emails"
|
||||
:fn (mg/resource "migrations/0004.emails.sql")}
|
||||
{:desc "Initial images tables"
|
||||
:name "0006-images"
|
||||
:fn (mg/resource "migrations/0006.images.sql")}
|
||||
:name "0005-images"
|
||||
:fn (mg/resource "migrations/0005.images.sql")}
|
||||
{:desc "Initial icons tables"
|
||||
:name "0007-icons"
|
||||
:fn (mg/resource "migrations/0007.icons.sql")}
|
||||
:name "0006-icons"
|
||||
:fn (mg/resource "migrations/0006.icons.sql")}
|
||||
]})
|
||||
|
||||
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
|
||||
|
|
|
@ -13,20 +13,22 @@
|
|||
[]
|
||||
(require 'uxbox.services.queries.icons)
|
||||
(require 'uxbox.services.queries.images)
|
||||
(require 'uxbox.services.queries.pages)
|
||||
(require 'uxbox.services.queries.profiles)
|
||||
(require 'uxbox.services.queries.projects)
|
||||
(require 'uxbox.services.queries.user-storage))
|
||||
(require 'uxbox.services.queries.project-files)
|
||||
(require 'uxbox.services.queries.project-pages)
|
||||
(require 'uxbox.services.queries.users)
|
||||
(require 'uxbox.services.queries.user-attrs))
|
||||
|
||||
(defn- load-mutation-services
|
||||
[]
|
||||
(require 'uxbox.services.mutations.auth)
|
||||
(require 'uxbox.services.mutations.icons)
|
||||
(require 'uxbox.services.mutations.images)
|
||||
(require 'uxbox.services.mutations.projects)
|
||||
(require 'uxbox.services.mutations.pages)
|
||||
(require 'uxbox.services.mutations.profiles)
|
||||
(require 'uxbox.services.mutations.user-storage))
|
||||
(require 'uxbox.services.mutations.project-files)
|
||||
(require 'uxbox.services.mutations.project-pages)
|
||||
(require 'uxbox.services.mutations.auth)
|
||||
(require 'uxbox.services.mutations.users)
|
||||
(require 'uxbox.services.mutations.user-attrs))
|
||||
|
||||
(defstate query-services
|
||||
:start (load-query-services))
|
||||
|
|
|
@ -6,11 +6,13 @@
|
|||
|
||||
(ns uxbox.services.mutations
|
||||
(:require
|
||||
[uxbox.util.dispatcher :as uds]))
|
||||
[uxbox.util.dispatcher :as uds]
|
||||
[uxbox.util.exceptions :as ex]))
|
||||
|
||||
(uds/defservice handle
|
||||
{:dispatch-by ::type
|
||||
:interceptors [uds/spec-interceptor
|
||||
uds/wrap-errors
|
||||
#_logging-interceptor
|
||||
#_context-interceptor]})
|
||||
|
||||
|
|
|
@ -1,174 +0,0 @@
|
|||
;; 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.services.mutations.pages
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[promesa.core :as p]
|
||||
[uxbox.db :as db]
|
||||
[uxbox.util.spec :as us]
|
||||
[uxbox.services.mutations :as sm]
|
||||
[uxbox.services.util :as su]
|
||||
[uxbox.services.queries.pages :refer [decode-row]]
|
||||
[uxbox.util.sql :as sql]
|
||||
[uxbox.util.blob :as blob]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
;; --- Helpers & Specs
|
||||
|
||||
;; TODO: validate `:data` and `:metadata`
|
||||
|
||||
(s/def ::id ::us/uuid)
|
||||
(s/def ::name ::us/string)
|
||||
(s/def ::data any?)
|
||||
(s/def ::user ::us/uuid)
|
||||
(s/def ::project-id ::us/uuid)
|
||||
(s/def ::metadata any?)
|
||||
(s/def ::ordering ::us/number)
|
||||
|
||||
;; --- Mutation: Create Page
|
||||
|
||||
(declare create-page)
|
||||
|
||||
(s/def ::create-page
|
||||
(s/keys :req-un [::data ::user ::project-id ::name ::metadata]
|
||||
:opt-un [::id]))
|
||||
|
||||
(sm/defmutation ::create-page
|
||||
[params]
|
||||
(create-page db/pool params))
|
||||
|
||||
(defn create-page
|
||||
[conn {:keys [id user project-id name ordering data metadata] :as params}]
|
||||
(let [sql "insert into pages (id, user_id, project_id, name,
|
||||
ordering, data, metadata, version)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, 0)
|
||||
returning *"
|
||||
id (or id (uuid/next))
|
||||
data (blob/encode data)
|
||||
mdata (blob/encode metadata)]
|
||||
(-> (db/query-one db/pool [sql id user project-id name ordering data mdata])
|
||||
(p/then' decode-row))))
|
||||
|
||||
;; --- Mutation: Update Page
|
||||
|
||||
(s/def ::update-page
|
||||
(s/keys :req-un [::data ::user ::project-id ::name ::data ::metadata ::id]))
|
||||
|
||||
(letfn [(select-for-update [conn id]
|
||||
(let [sql "select p.id, p.version
|
||||
from pages as p
|
||||
where p.id = $1
|
||||
and deleted_at is null
|
||||
for update;"]
|
||||
(-> (db/query-one conn [sql id])
|
||||
(p/then' su/raise-not-found-if-nil))))
|
||||
|
||||
(update-page [conn {:keys [id name version data metadata user]}]
|
||||
(let [sql "update pages
|
||||
set name = $1,
|
||||
version = $2,
|
||||
data = $3,
|
||||
metadata = $4
|
||||
where id = $5
|
||||
and user_id = $6"]
|
||||
(-> (db/query-one conn [sql name version data metadata id user])
|
||||
(p/then' su/constantly-nil))))
|
||||
|
||||
(update-history [conn {:keys [user id version data metadata]}]
|
||||
(let [sql "insert into pages_history (user_id, page_id, version, data, metadata)
|
||||
values ($1, $2, $3, $4, $5)"]
|
||||
(-> (db/query-one conn [sql user id version data metadata])
|
||||
(p/then' su/constantly-nil))))]
|
||||
|
||||
(sm/defmutation ::update-page
|
||||
[{:keys [id data metadata] :as params}]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(-> (select-for-update conn id)
|
||||
(p/then (fn [{:keys [id version]}]
|
||||
(let [data (blob/encode data)
|
||||
mdata (blob/encode metadata)
|
||||
version (inc version)
|
||||
params (assoc params
|
||||
:id id
|
||||
:version version
|
||||
:data data
|
||||
:metadata mdata)]
|
||||
(p/do! (update-page conn params)
|
||||
(update-history conn params)
|
||||
(select-keys params [:id :version])))))))))
|
||||
|
||||
;; --- Mutation: Rename Page
|
||||
|
||||
(s/def ::rename-page
|
||||
(s/keys :req-un [::id ::name ::user]))
|
||||
|
||||
(sm/defmutation ::rename-page
|
||||
[{:keys [id name user]}]
|
||||
(let [sql "update pages
|
||||
set name = $3
|
||||
where id = $1
|
||||
and user_id = $2
|
||||
and deleted_at is null"]
|
||||
(-> (db/query-one db/pool [sql id user name])
|
||||
(p/then su/constantly-nil))))
|
||||
|
||||
;; --- Mutation: Update Page Metadata
|
||||
|
||||
(s/def ::update-page-metadata
|
||||
(s/keys :req-un [::user ::project-id ::name ::metadata ::id]))
|
||||
|
||||
(sm/defmutation ::update-page-metadata
|
||||
[{:keys [id user project-id name metadata]}]
|
||||
(let [sql "update pages
|
||||
set name = $3,
|
||||
metadata = $4
|
||||
where id = $1
|
||||
and user_id = $2
|
||||
and deleted_at is null
|
||||
returning *"
|
||||
mdata (blob/encode metadata)]
|
||||
(-> (db/query-one db/pool [sql id user name mdata])
|
||||
(p/then' decode-row))))
|
||||
|
||||
;; --- Mutation: Delete Page
|
||||
|
||||
(s/def ::delete-page
|
||||
(s/keys :req-un [::user ::id]))
|
||||
|
||||
(sm/defmutation ::delete-page
|
||||
[{:keys [id user]}]
|
||||
(let [sql "update pages
|
||||
set deleted_at = clock_timestamp()
|
||||
where id = $1
|
||||
and user_id = $2
|
||||
and deleted_at is null
|
||||
returning id"]
|
||||
(-> (db/query-one db/pool [sql id user])
|
||||
(p/then su/raise-not-found-if-nil)
|
||||
(p/then su/constantly-nil))))
|
||||
|
||||
;; ;; --- Update Page History
|
||||
|
||||
;; (defn update-page-history
|
||||
;; [conn {:keys [user id label pinned]}]
|
||||
;; (let [sqlv (sql/update-page-history {:user user
|
||||
;; :id id
|
||||
;; :label label
|
||||
;; :pinned pinned})]
|
||||
;; (some-> (db/fetch-one conn sqlv)
|
||||
;; (decode-row))))
|
||||
|
||||
;; (s/def ::label ::us/string)
|
||||
;; (s/def ::update-page-history
|
||||
;; (s/keys :req-un [::user ::id ::pinned ::label]))
|
||||
|
||||
;; (sm/defmutation :update-page-history
|
||||
;; {:doc "Update page history"
|
||||
;; :spec ::update-page-history}
|
||||
;; [params]
|
||||
;; (with-open [conn (db/connection)]
|
||||
;; (update-page-history conn params)))
|
142
backend/src/uxbox/services/mutations/project_files.clj
Normal file
142
backend/src/uxbox/services/mutations/project_files.clj
Normal file
|
@ -0,0 +1,142 @@
|
|||
;; 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.services.mutations.project-files
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[promesa.core :as p]
|
||||
[uxbox.db :as db]
|
||||
[uxbox.util.spec :as us]
|
||||
[uxbox.services.mutations :as sm]
|
||||
[uxbox.services.mutations.projects :as proj]
|
||||
[uxbox.services.util :as su]
|
||||
[uxbox.util.exceptions :as ex]
|
||||
[uxbox.util.blob :as blob]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
;; --- Helpers & Specs
|
||||
|
||||
(s/def ::id ::us/uuid)
|
||||
(s/def ::name ::us/string)
|
||||
(s/def ::user ::us/uuid)
|
||||
(s/def ::project-id ::us/uuid)
|
||||
|
||||
;; --- Permissions Checks
|
||||
|
||||
;; A query that returns all (not-equal) user assignations for a
|
||||
;; requested file (project level and file level).
|
||||
|
||||
;; Is important having the condition of user_id in the join and not in
|
||||
;; where clause because we need all results independently if value is
|
||||
;; true, false or null; with that, the empty result means there are no
|
||||
;; file found.
|
||||
|
||||
(def ^:private sql:file-permissions
|
||||
"select pf.id,
|
||||
pfu.can_edit as can_edit
|
||||
from project_files as pf
|
||||
left join project_file_users as pfu
|
||||
on (pfu.file_id = pf.id and pfu.user_id = $1)
|
||||
where pf.id = $2
|
||||
union all
|
||||
select pf.id,
|
||||
pu.can_edit as can_edit
|
||||
from project_files as pf
|
||||
left join project_users as pu
|
||||
on (pf.project_id = pu.project_id and pu.user_id = $1)
|
||||
where pf.id = $2")
|
||||
|
||||
(defn check-edition-permissions!
|
||||
[conn user file-id]
|
||||
(-> (db/query conn [sql:file-permissions user file-id])
|
||||
(p/then' seq)
|
||||
(p/then' su/raise-not-found-if-nil)
|
||||
(p/then' (fn [rows]
|
||||
(when-not (some :can-edit rows)
|
||||
(ex/raise :type :validation
|
||||
:code :not-authorized))))))
|
||||
|
||||
;; --- Mutation: Create Project
|
||||
|
||||
(declare create-file)
|
||||
(declare create-page)
|
||||
|
||||
(s/def ::create-project-file
|
||||
(s/keys :req-un [::user ::name ::project-id]
|
||||
:opt-un [::id]))
|
||||
|
||||
(sm/defmutation ::create-project-file
|
||||
[{:keys [user project-id] :as params}]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(proj/check-edition-permissions! conn user project-id)
|
||||
(p/let [file (create-file conn params)]
|
||||
(create-page conn (assoc params :file-id (:id file)))
|
||||
file)))
|
||||
|
||||
(defn create-file
|
||||
[conn {:keys [id user name project-id] :as params}]
|
||||
(let [id (or id (uuid/next))
|
||||
sql "insert into project_files (id, user_id, project_id, name)
|
||||
values ($1, $2, $3, $4) returning *"]
|
||||
(db/query-one conn [sql id user project-id name])))
|
||||
|
||||
(defn- create-page
|
||||
"Creates an initial page for the file."
|
||||
[conn {:keys [user file-id] :as params}]
|
||||
(let [id (uuid/next)
|
||||
name "Page 1"
|
||||
data (blob/encode {})
|
||||
sql "insert into project_pages (id, user_id, file_id, name, version,
|
||||
ordering, data)
|
||||
values ($1, $2, $3, $4, 0, 1, $5) returning id"]
|
||||
(db/query-one conn [sql id user file-id name data])))
|
||||
|
||||
;; --- Mutation: Update Project
|
||||
|
||||
(declare update-file)
|
||||
|
||||
(s/def ::update-project-file
|
||||
(s/keys :req-un [::user ::name ::id]))
|
||||
|
||||
(sm/defmutation ::update-project-file
|
||||
[{:keys [id user] :as params}]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(check-edition-permissions! conn user id)
|
||||
(update-file conn params)))
|
||||
|
||||
(defn- update-file
|
||||
[conn {:keys [id name user] :as params}]
|
||||
(let [sql "update project_files
|
||||
set name = $2
|
||||
where id = $1
|
||||
and deleted_at is null"]
|
||||
(-> (db/query-one conn [sql id name])
|
||||
(p/then' su/constantly-nil))))
|
||||
|
||||
;; --- Mutation: Delete Project
|
||||
|
||||
(declare delete-file)
|
||||
|
||||
(s/def ::delete-project-file
|
||||
(s/keys :req-un [::id ::user]))
|
||||
|
||||
(sm/defmutation ::delete-project-file
|
||||
[{:keys [id user] :as params}]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(check-edition-permissions! conn user id)
|
||||
(delete-file conn params)))
|
||||
|
||||
(def ^:private sql:delete-file
|
||||
"update project_files
|
||||
set deleted_at = clock_timestamp()
|
||||
where id = $1
|
||||
and deleted_at is null")
|
||||
|
||||
(defn delete-file
|
||||
[conn {:keys [id] :as params}]
|
||||
(let [sql sql:delete-file]
|
||||
(-> (db/query-one conn [sql id])
|
||||
(p/then' su/constantly-nil))))
|
190
backend/src/uxbox/services/mutations/project_pages.clj
Normal file
190
backend/src/uxbox/services/mutations/project_pages.clj
Normal file
|
@ -0,0 +1,190 @@
|
|||
;; 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.services.mutations.project-pages
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[promesa.core :as p]
|
||||
[uxbox.db :as db]
|
||||
[uxbox.services.mutations :as sm]
|
||||
[uxbox.services.mutations.project-files :as files]
|
||||
[uxbox.services.queries.project-pages :refer [decode-row]]
|
||||
[uxbox.services.util :as su]
|
||||
[uxbox.util.blob :as blob]
|
||||
[uxbox.util.spec :as us]
|
||||
[uxbox.util.sql :as sql]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
;; --- Helpers & Specs
|
||||
|
||||
;; TODO: validate `:data` and `:metadata`
|
||||
|
||||
(s/def ::id ::us/uuid)
|
||||
(s/def ::name ::us/string)
|
||||
(s/def ::data any?)
|
||||
(s/def ::user ::us/uuid)
|
||||
(s/def ::project-id ::us/uuid)
|
||||
(s/def ::metadata any?)
|
||||
(s/def ::ordering ::us/number)
|
||||
|
||||
;; --- Mutation: Create Page
|
||||
|
||||
(declare create-page)
|
||||
|
||||
(s/def ::create-project-page
|
||||
(s/keys :req-un [::user ::file-id ::name ::ordering ::metadata ::data]
|
||||
:opt-un [::id]))
|
||||
|
||||
(sm/defmutation ::create-project-page
|
||||
[{:keys [user file-id] :as params}]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(files/check-edition-permissions! conn user file-id)
|
||||
(create-page conn params)))
|
||||
|
||||
(defn create-page
|
||||
[conn {:keys [id user file-id name ordering data metadata] :as params}]
|
||||
(let [sql "insert into project_pages (id, user_id, file_id, name,
|
||||
ordering, data, metadata, version)
|
||||
values ($1, $2, $3, $4, $5, $6, $7, 0)
|
||||
returning *"
|
||||
id (or id (uuid/next))
|
||||
data (blob/encode data)
|
||||
mdata (blob/encode metadata)]
|
||||
(-> (db/query-one conn [sql id user file-id name ordering data mdata])
|
||||
(p/then' decode-row))))
|
||||
|
||||
;; --- Mutation: Update Page
|
||||
|
||||
(declare select-page-for-update)
|
||||
(declare update-page)
|
||||
(declare update-history)
|
||||
|
||||
(s/def ::update-project-page-data
|
||||
(s/keys :req-un [::id ::user ::data]))
|
||||
|
||||
(sm/defmutation ::update-project-page-data
|
||||
[{:keys [id user data] :as params}]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(p/let [{:keys [version file-id]} (select-page-for-update conn id)]
|
||||
(files/check-edition-permissions! conn user file-id)
|
||||
(let [data (blob/encode data)
|
||||
version (inc version)
|
||||
params (assoc params :id id :data data :version version)]
|
||||
(p/do! (update-page conn params)
|
||||
(update-history conn params)
|
||||
(select-keys params [:id :version]))))))
|
||||
|
||||
(defn- select-page-for-update
|
||||
[conn id]
|
||||
(let [sql "select p.id, p.version, p.file_id
|
||||
from project_pages as p
|
||||
where p.id = $1
|
||||
and deleted_at is null
|
||||
for update;"]
|
||||
(-> (db/query-one conn [sql id])
|
||||
(p/then' su/raise-not-found-if-nil))))
|
||||
|
||||
(defn- update-page
|
||||
[conn {:keys [id name version data metadata]}]
|
||||
(let [sql "update project_pages
|
||||
set version = $1,
|
||||
data = $2
|
||||
where id = $3"]
|
||||
(-> (db/query-one conn [sql version data id])
|
||||
(p/then' su/constantly-nil))))
|
||||
|
||||
(defn- update-history
|
||||
[conn {:keys [user id version data]}]
|
||||
(let [sql "insert into project_page_history (user_id, page_id, version, data)
|
||||
values ($1, $2, $3, $4)"]
|
||||
(-> (db/query-one conn [sql user id version data])
|
||||
(p/then' su/constantly-nil))))
|
||||
|
||||
;; --- Mutation: Rename Page
|
||||
|
||||
(declare rename-page)
|
||||
|
||||
(s/def ::rename-project-page
|
||||
(s/keys :req-un [::id ::name ::user]))
|
||||
|
||||
(sm/defmutation ::rename-project-page
|
||||
[{:keys [id name user]}]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(p/let [page (select-page-for-update conn id)]
|
||||
(files/check-edition-permissions! conn user (:file-id page))
|
||||
(rename-page conn (assoc page :name name)))))
|
||||
|
||||
(defn- rename-page
|
||||
[conn {:keys [id name] :as params}]
|
||||
(let [sql "update project_pages
|
||||
set name = $2
|
||||
where id = $1
|
||||
and deleted_at is null"]
|
||||
(-> (db/query-one db/pool [sql id name])
|
||||
(p/then su/constantly-nil))))
|
||||
|
||||
;; --- Mutation: Update Page Metadata
|
||||
|
||||
;; (s/def ::update-page-metadata
|
||||
;; (s/keys :req-un [::user ::project-id ::name ::metadata ::id]))
|
||||
|
||||
;; (sm/defmutation ::update-page-metadata
|
||||
;; [{:keys [id user project-id name metadata]}]
|
||||
;; (let [sql "update pages
|
||||
;; set name = $3,
|
||||
;; metadata = $4
|
||||
;; where id = $1
|
||||
;; and user_id = $2
|
||||
;; and deleted_at is null
|
||||
;; returning *"
|
||||
;; mdata (blob/encode metadata)]
|
||||
;; (-> (db/query-one db/pool [sql id user name mdata])
|
||||
;; (p/then' decode-row))))
|
||||
|
||||
;; --- Mutation: Delete Page
|
||||
|
||||
(declare delete-page)
|
||||
|
||||
(s/def ::delete-project-page
|
||||
(s/keys :req-un [::user ::id]))
|
||||
|
||||
(sm/defmutation ::delete-project-page
|
||||
[{:keys [id user]}]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(p/let [page (select-page-for-update conn id)]
|
||||
(files/check-edition-permissions! conn user (:file-id page))
|
||||
(delete-page conn id))))
|
||||
|
||||
(defn- delete-page
|
||||
[conn id]
|
||||
(let [sql "update project_pages
|
||||
set deleted_at = clock_timestamp()
|
||||
where id = $1
|
||||
and deleted_at is null"]
|
||||
(-> (db/query-one conn [sql id])
|
||||
(p/then su/constantly-nil))))
|
||||
|
||||
;; --- Update Page History
|
||||
|
||||
;; (defn update-page-history
|
||||
;; [conn {:keys [user id label pinned]}]
|
||||
;; (let [sqlv (sql/update-page-history {:user user
|
||||
;; :id id
|
||||
;; :label label
|
||||
;; :pinned pinned})]
|
||||
;; (some-> (db/fetch-one conn sqlv)
|
||||
;; (decode-row))))
|
||||
|
||||
;; (s/def ::label ::us/string)
|
||||
;; (s/def ::update-page-history
|
||||
;; (s/keys :req-un [::user ::id ::pinned ::label]))
|
||||
|
||||
;; (sm/defmutation :update-page-history
|
||||
;; {:doc "Update page history"
|
||||
;; :spec ::update-page-history}
|
||||
;; [params]
|
||||
;; (with-open [conn (db/connection)]
|
||||
;; (update-page-history conn params)))
|
|
@ -13,6 +13,7 @@
|
|||
[uxbox.services.mutations :as sm]
|
||||
[uxbox.services.util :as su]
|
||||
[uxbox.util.blob :as blob]
|
||||
[uxbox.util.exceptions :as ex]
|
||||
[uxbox.util.uuid :as uuid]))
|
||||
|
||||
;; --- Helpers & Specs
|
||||
|
@ -22,6 +23,27 @@
|
|||
(s/def ::token ::us/string)
|
||||
(s/def ::user ::us/uuid)
|
||||
|
||||
;; --- Permissions Checks
|
||||
|
||||
(def ^:private sql:project-permissions
|
||||
"select p.id,
|
||||
pu.can_edit as can_edit
|
||||
from projects as p
|
||||
inner join project_users as pu
|
||||
on (pu.project_id = p.id)
|
||||
where pu.user_id = $1
|
||||
and p.id = $2
|
||||
for update of p;")
|
||||
|
||||
(defn check-edition-permissions!
|
||||
[conn user project-id]
|
||||
(-> (db/query-one conn [sql:project-permissions user project-id])
|
||||
(p/then' su/raise-not-found-if-nil)
|
||||
(p/then' (fn [{:keys [id can-edit] :as proj}]
|
||||
(when-not can-edit
|
||||
(ex/raise :type :validation
|
||||
:code :not-authorized))))))
|
||||
|
||||
;; --- Mutation: Create Project
|
||||
|
||||
(declare create-project)
|
||||
|
@ -31,11 +53,9 @@
|
|||
:opt-un [::id]))
|
||||
|
||||
(sm/defmutation ::create-project
|
||||
[{:keys [id user name] :as params}]
|
||||
(let [id (or id (uuid/next))
|
||||
sql "insert into projects (id, user_id, name)
|
||||
values ($1, $2, $3) returning *"]
|
||||
(db/query-one db/pool [sql id user name])))
|
||||
[params]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(create-project conn params)))
|
||||
|
||||
(defn create-project
|
||||
[conn {:keys [id user name] :as params}]
|
||||
|
@ -46,32 +66,49 @@
|
|||
|
||||
;; --- Mutation: Update Project
|
||||
|
||||
(declare update-project)
|
||||
|
||||
(s/def ::update-project
|
||||
(s/keys :req-un [::user ::name ::id]))
|
||||
|
||||
(sm/defmutation ::update-project
|
||||
[{:keys [id name user] :as params}]
|
||||
[{:keys [id user] :as params}]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(check-edition-permissions! conn user id)
|
||||
(update-project conn params)))
|
||||
|
||||
(defn update-project
|
||||
[conn {:keys [id name user] :as params}]
|
||||
(let [sql "update projects
|
||||
set name = $3
|
||||
where id = $1
|
||||
and user_id = $2
|
||||
and deleted_at is null
|
||||
returning *"]
|
||||
(db/query-one db/pool [sql id user name])))
|
||||
(db/query-one conn [sql id user name])))
|
||||
|
||||
;; --- Mutation: Delete Project
|
||||
|
||||
(declare delete-project)
|
||||
|
||||
(s/def ::delete-project
|
||||
(s/keys :req-un [::id ::user]))
|
||||
|
||||
(sm/defmutation ::delete-project
|
||||
[{:keys [id user] :as params}]
|
||||
(let [sql "update projects
|
||||
set deleted_at = clock_timestamp()
|
||||
where id = $1
|
||||
and user_id = $2
|
||||
and deleted_at is null
|
||||
returning id"]
|
||||
(-> (db/query-one db/pool [sql id user])
|
||||
(p/then' su/raise-not-found-if-nil)
|
||||
(db/with-atomic [conn db/pool]
|
||||
(check-edition-permissions! conn user id)
|
||||
(delete-project conn params)))
|
||||
|
||||
(def ^:private sql:delete-project
|
||||
"update projects
|
||||
set deleted_at = clock_timestamp()
|
||||
where id = $1
|
||||
and deleted_at is null
|
||||
returning id")
|
||||
|
||||
(defn delete-project
|
||||
[conn {:keys [id user] :as params}]
|
||||
(let [sql sql:delete-project]
|
||||
(-> (db/query-one conn [sql id])
|
||||
(p/then' su/constantly-nil))))
|
||||
|
|
|
@ -4,14 +4,14 @@
|
|||
;;
|
||||
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.services.mutations.user-storage
|
||||
(ns uxbox.services.mutations.user-attrs
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[promesa.core :as p]
|
||||
[uxbox.db :as db]
|
||||
[uxbox.services.mutations :as sm]
|
||||
[uxbox.services.util :as su]
|
||||
[uxbox.services.queries.user-storage :refer [decode-row]]
|
||||
[uxbox.services.queries.user-attrs :refer [decode-row]]
|
||||
[uxbox.util.blob :as blob]
|
||||
[uxbox.util.spec :as us]))
|
||||
|
||||
|
@ -21,12 +21,12 @@
|
|||
(s/def ::key ::us/string)
|
||||
(s/def ::val any?)
|
||||
|
||||
(s/def ::upsert-user-storage-entry
|
||||
(s/def ::upsert-user-attr
|
||||
(s/keys :req-un [::key ::val ::user]))
|
||||
|
||||
(sm/defmutation ::upsert-user-storage-entry
|
||||
(sm/defmutation ::upsert-user-attr
|
||||
[{:keys [key val user] :as params}]
|
||||
(let [sql "insert into user_storage (key, val, user_id)
|
||||
(let [sql "insert into user_attrs (key, val, user_id)
|
||||
values ($1, $2, $3)
|
||||
on conflict (user_id, key)
|
||||
do update set val = $2"
|
||||
|
@ -36,12 +36,12 @@
|
|||
|
||||
;; --- Delete KVStore
|
||||
|
||||
(s/def ::delete-user-storage-entry
|
||||
(s/def ::delete-user-attr
|
||||
(s/keys :req-un [::key ::user]))
|
||||
|
||||
(sm/defmutation ::delete-user-storage-entry
|
||||
(sm/defmutation ::delete-user-attr
|
||||
[{:keys [user key] :as params}]
|
||||
(let [sql "delete from user_storage
|
||||
(let [sql "delete from user_attrs
|
||||
where user_id = $2
|
||||
and key = $1"]
|
||||
(-> (db/query-one db/pool [sql key user])
|
|
@ -4,7 +4,7 @@
|
|||
;;
|
||||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.services.mutations.profiles
|
||||
(ns uxbox.services.mutations.users
|
||||
(:require
|
||||
[buddy.hashers :as hashers]
|
||||
[clojure.spec.alpha :as s]
|
||||
|
@ -19,10 +19,10 @@
|
|||
[uxbox.media :as media]
|
||||
[uxbox.services.mutations :as sm]
|
||||
[uxbox.services.util :as su]
|
||||
[uxbox.services.queries.profiles :refer [get-profile
|
||||
decode-profile-row
|
||||
strip-private-attrs
|
||||
resolve-thumbnail]]
|
||||
[uxbox.services.queries.users :refer [get-profile
|
||||
decode-profile-row
|
||||
strip-private-attrs
|
||||
resolve-thumbnail]]
|
||||
[uxbox.util.blob :as blob]
|
||||
[uxbox.util.exceptions :as ex]
|
||||
[uxbox.util.spec :as us]
|
||||
|
@ -56,7 +56,7 @@
|
|||
and id != $1
|
||||
) as val"]
|
||||
(p/let [res1 (db/query-one conn [sql1 id username])
|
||||
res2 (db/query-one conn [sql2 id email])]
|
||||
res2 (db/query-one conn [sql2 id email])]
|
||||
(when (:val res1)
|
||||
(ex/raise :type :validation
|
||||
:code ::username-already-exists))
|
||||
|
@ -83,9 +83,7 @@
|
|||
(s/def ::update-profile
|
||||
(s/keys :req-un [::id ::username ::email ::fullname ::metadata]))
|
||||
|
||||
(sm/defmutation :update-profile
|
||||
{:doc "Update self profile."
|
||||
:spec ::update-profile}
|
||||
(sm/defmutation ::update-profile
|
||||
[params]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(-> (p/resolved params)
|
||||
|
@ -134,9 +132,7 @@
|
|||
(def valid-image-types?
|
||||
#{"image/jpeg", "image/png", "image/webp"})
|
||||
|
||||
(sm/defmutation :update-profile-photo
|
||||
{:doc "Update profile photo."
|
||||
:spec ::update-profile-photo}
|
||||
(sm/defmutation ::update-profile-photo
|
||||
[{:keys [user file] :as params}]
|
||||
(letfn [(store-photo [{:keys [name path] :as upload}]
|
||||
(let [filename (fs/name name)
|
||||
|
@ -149,16 +145,17 @@
|
|||
set photo = $1
|
||||
where id = $2
|
||||
and deleted_at is null
|
||||
returning *"]
|
||||
returning id, photo"]
|
||||
(-> (db/query-one db/pool [sql (str path) user])
|
||||
(p/then' su/raise-not-found-if-nil)
|
||||
(p/then' strip-private-attrs)
|
||||
;; (p/then' strip-private-attrs)
|
||||
(p/then resolve-thumbnail))))]
|
||||
|
||||
(when-not (valid-image-types? (:mtype file))
|
||||
(ex/raise :type :validation
|
||||
:code :image-type-not-allowed
|
||||
:hint "Seems like you are uploading an invalid image."))
|
||||
|
||||
(-> (store-photo file)
|
||||
(p/then update-user-photo))))
|
||||
|
|
@ -11,6 +11,7 @@
|
|||
(uds/defservice handle
|
||||
{:dispatch-by ::type
|
||||
:interceptors [uds/spec-interceptor
|
||||
uds/wrap-errors
|
||||
#_logging-interceptor
|
||||
#_context-interceptor]})
|
||||
|
||||
|
|
|
@ -1,92 +0,0 @@
|
|||
;; 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.services.queries.pages
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[promesa.core :as p]
|
||||
[uxbox.db :as db]
|
||||
[uxbox.services.queries :as sq]
|
||||
[uxbox.util.blob :as blob]
|
||||
[uxbox.util.spec :as us]
|
||||
[uxbox.util.sql :as sql]))
|
||||
|
||||
;; --- Helpers & Specs
|
||||
|
||||
(declare decode-row)
|
||||
|
||||
(s/def ::id ::us/uuid)
|
||||
(s/def ::user ::us/uuid)
|
||||
(s/def ::project-id ::us/uuid)
|
||||
|
||||
;; --- Query: Pages by Project
|
||||
|
||||
(s/def ::pages-by-project
|
||||
(s/keys :req-un [::user ::project-id]))
|
||||
|
||||
(sq/defquery ::pages-by-project
|
||||
[{:keys [user project-id] :as params}]
|
||||
(let [sql "select pg.*,
|
||||
pg.data,
|
||||
pg.metadata
|
||||
from pages as pg
|
||||
where pg.user_id = $2
|
||||
and pg.project_id = $1
|
||||
and pg.deleted_at is null
|
||||
order by pg.created_at asc;"]
|
||||
(-> (db/query db/pool [sql project-id user])
|
||||
(p/then #(mapv decode-row %)))))
|
||||
|
||||
;; --- Query: Page by Id
|
||||
|
||||
(s/def ::page
|
||||
(s/keys :req-un [::user ::id]))
|
||||
|
||||
(sq/defquery ::page
|
||||
[{:keys [user id] :as params}]
|
||||
(let [sql "select pg.*,
|
||||
pg.data,
|
||||
pg.metadata
|
||||
from pages as pg
|
||||
where pg.user_id = $2
|
||||
and pg.id = $1
|
||||
and pg.deleted_at is null"]
|
||||
(-> (db/query-one db/pool [sql id user])
|
||||
(p/then' decode-row))))
|
||||
|
||||
;; --- Query: Page History
|
||||
|
||||
(s/def ::page-id ::us/uuid)
|
||||
(s/def ::max ::us/integer)
|
||||
(s/def ::pinned ::us/boolean)
|
||||
(s/def ::since ::us/integer)
|
||||
|
||||
(s/def ::page-history
|
||||
(s/keys :req-un [::page-id ::user]
|
||||
:opt-un [::max ::pinned ::since]))
|
||||
|
||||
(sq/defquery ::page-history
|
||||
[{:keys [page-id user since max pinned] :or {since Long/MAX_VALUE max 10}}]
|
||||
(let [sql (-> (sql/from ["pages_history" "ph"])
|
||||
(sql/select "ph.*")
|
||||
(sql/where ["ph.user_id = ?" user]
|
||||
["ph.page_id = ?" page-id]
|
||||
["ph.version < ?" since]
|
||||
(when pinned
|
||||
["ph.pinned = ?" true]))
|
||||
(sql/order "ph.version desc")
|
||||
(sql/limit max))]
|
||||
(-> (db/query db/pool (sql/fmt sql))
|
||||
(p/then (partial mapv decode-row)))))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(defn decode-row
|
||||
[{:keys [data metadata] :as row}]
|
||||
(when row
|
||||
(cond-> row
|
||||
data (assoc :data (blob/decode data))
|
||||
metadata (assoc :metadata (blob/decode metadata)))))
|
55
backend/src/uxbox/services/queries/project_files.clj
Normal file
55
backend/src/uxbox/services/queries/project_files.clj
Normal file
|
@ -0,0 +1,55 @@
|
|||
;; 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.services.queries.project-files
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[promesa.core :as p]
|
||||
[uxbox.db :as db]
|
||||
[uxbox.services.queries :as sq]
|
||||
[uxbox.util.blob :as blob]
|
||||
[uxbox.util.spec :as us]))
|
||||
|
||||
(declare decode-row)
|
||||
|
||||
;; --- Helpers & Specs
|
||||
|
||||
(s/def ::id ::us/uuid)
|
||||
(s/def ::name ::us/string)
|
||||
(s/def ::project-id ::us/uuid)
|
||||
(s/def ::user ::us/uuid)
|
||||
|
||||
;; --- Query: Project Files
|
||||
|
||||
(def ^:private sql:project-files
|
||||
"select pf.*,
|
||||
array_agg(pp.id) as pages
|
||||
from project_files as pf
|
||||
inner join projects as p on (pf.project_id = p.id)
|
||||
inner join project_users as pu on (p.id = pu.project_id)
|
||||
left join project_pages as pp on (pf.id = pp.file_id)
|
||||
where pu.user_id = $1
|
||||
and pu.project_id = $2
|
||||
and pu.can_edit = true
|
||||
group by pf.id
|
||||
order by pf.created_at asc;")
|
||||
|
||||
(s/def ::project-files
|
||||
(s/keys :req-un [::user ::project-id]))
|
||||
|
||||
(sq/defquery ::project-files
|
||||
[{:keys [user project-id] :as params}]
|
||||
(-> (db/query db/pool [sql:project-files user project-id])
|
||||
(p/then' (partial mapv decode-row))))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(defn decode-row
|
||||
[{:keys [metadata pages] :as row}]
|
||||
(when row
|
||||
(cond-> row
|
||||
pages (assoc :pages (vec (remove nil? pages)))
|
||||
metadata (assoc :metadata (blob/decode metadata)))))
|
145
backend/src/uxbox/services/queries/project_pages.clj
Normal file
145
backend/src/uxbox/services/queries/project_pages.clj
Normal file
|
@ -0,0 +1,145 @@
|
|||
;; 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.services.queries.project-pages
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[promesa.core :as p]
|
||||
[uxbox.db :as db]
|
||||
[uxbox.services.queries :as sq]
|
||||
[uxbox.services.util :as su]
|
||||
[uxbox.util.blob :as blob]
|
||||
[uxbox.util.spec :as us]
|
||||
[uxbox.util.sql :as sql]))
|
||||
|
||||
;; --- Helpers & Specs
|
||||
|
||||
(declare decode-row)
|
||||
|
||||
(s/def ::id ::us/uuid)
|
||||
(s/def ::user ::us/uuid)
|
||||
(s/def ::project-id ::us/uuid)
|
||||
(s/def ::file-id ::us/uuid)
|
||||
|
||||
(def ^:private sql:generic-project-pages
|
||||
"select pp.*
|
||||
from project_pages as pp
|
||||
inner join project_files as pf on (pf.id = pp.file_id)
|
||||
inner join projects as p on (p.id = pf.project_id)
|
||||
left join project_users as pu on (pu.project_id = p.id)
|
||||
left join project_file_users as pfu on (pfu.file_id = pf.id)
|
||||
where ((pfu.user_id = $1 and pfu.can_edit = true) or
|
||||
(pu.user_id = $1 and pu.can_edit = true))
|
||||
order by pp.created_at")
|
||||
|
||||
;; --- Query: Project Pages (By File ID)
|
||||
|
||||
(def ^:private sql:project-pages
|
||||
(str "with pages as (" sql:generic-project-pages ")"
|
||||
" select * from pages where file_id = $2"))
|
||||
|
||||
;; (defn project-pages-sql
|
||||
;; [user]
|
||||
;; (-> (sql/from ["project_pages" "pp"])
|
||||
;; (sql/join ["project_files" "pf"] "pf.id = pp.file_id")
|
||||
;; (sql/join ["projects" "p"] "p.id = pf.project_id")
|
||||
;; (sql/ljoin ["project_users", "pu"] "pu.project_id = p.id")
|
||||
;; (sql/ljoin ["project_file_users", "pfu"] "pfu.file_id = pf.id")
|
||||
;; (sql/select "pp.*")
|
||||
;; (sql/where ["((pfu.user_id = ? and pfu.can_edit = true) or
|
||||
;; (pu.user_id = ? and pu.can_edit = true))" user user])
|
||||
;; (sql/order "pp.created_at")))
|
||||
|
||||
;; (let [sql (-> (project-pages-sql user)
|
||||
;; (sql/where ["pp.file_id = ?" file-id])
|
||||
;; (sql/fmt))]
|
||||
;; (-> (db/query db/pool sql)
|
||||
;; (p/then #(mapv decode-row %)))))
|
||||
|
||||
(s/def ::project-pages
|
||||
(s/keys :req-un [::user ::file-id]))
|
||||
|
||||
(sq/defquery ::project-pages
|
||||
[{:keys [user file-id] :as params}]
|
||||
(let [sql sql:project-pages]
|
||||
(-> (db/query db/pool [sql user file-id])
|
||||
(p/then #(mapv decode-row %)))))
|
||||
|
||||
;; --- Query: Project Page (By ID)
|
||||
|
||||
(def ^:private sql:project-page
|
||||
(str "with pages as (" sql:generic-project-pages ")"
|
||||
" select * from pages where id = $2"))
|
||||
|
||||
(defn retrieve-page
|
||||
[conn {:keys [user id] :as params}]
|
||||
(let [sql sql:project-page]
|
||||
(-> (db/query-one conn [sql user id])
|
||||
(p/then' su/raise-not-found-if-nil)
|
||||
(p/then' decode-row))))
|
||||
|
||||
(s/def ::project-page
|
||||
(s/keys :req-un [::user ::id]))
|
||||
|
||||
(sq/defquery ::project-page
|
||||
[{:keys [user id] :as params}]
|
||||
(retrieve-page db/pool params))
|
||||
|
||||
;; --- Query: Project Page History (by Page ID)
|
||||
|
||||
;; (def ^:private sql:generic-page-history
|
||||
;; "select pph.*
|
||||
;; from project_page_history as pph
|
||||
;; where pph.page_id = $2
|
||||
;; and pph.version < $3
|
||||
;; order by pph.version < desc")
|
||||
|
||||
;; (def ^:private sql:page-history
|
||||
;; (str "with history as (" sql:generic-page-history ")"
|
||||
;; " select * from history limit $4"))
|
||||
|
||||
;; (def ^:private sql:pinned-page-history
|
||||
;; (str "with history as (" sql:generic-page-history ")"
|
||||
;; " select * from history where pinned = true limit $4"))
|
||||
|
||||
(s/def ::page-id ::us/uuid)
|
||||
(s/def ::max ::us/integer)
|
||||
(s/def ::pinned ::us/boolean)
|
||||
(s/def ::since ::us/integer)
|
||||
|
||||
(s/def ::page-history
|
||||
(s/keys :req-un [::page-id ::user]
|
||||
:opt-un [::max ::pinned ::since]))
|
||||
|
||||
(defn retrieve-page-history
|
||||
[{:keys [page-id user since max pinned] :or {since Long/MAX_VALUE max 10}}]
|
||||
(let [sql (-> (sql/from ["pages_history" "ph"])
|
||||
(sql/select "ph.*")
|
||||
(sql/where ["ph.user_id = ?" user]
|
||||
["ph.page_id = ?" page-id]
|
||||
["ph.version < ?" since]
|
||||
(when pinned
|
||||
["ph.pinned = ?" true]))
|
||||
(sql/order "ph.version desc")
|
||||
(sql/limit max))]
|
||||
(-> (db/query db/pool (sql/fmt sql))
|
||||
(p/then (partial mapv decode-row)))))
|
||||
|
||||
(sq/defquery ::page-history
|
||||
[{:keys [page-id user] :as params}]
|
||||
(db/with-atomic [conn db/pool]
|
||||
(p/do! (retrieve-page conn {:id page-id :user user})
|
||||
(retrieve-page-history conn params))))
|
||||
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(defn decode-row
|
||||
[{:keys [data metadata] :as row}]
|
||||
(when row
|
||||
(cond-> row
|
||||
data (assoc :data (blob/decode data))
|
||||
metadata (assoc :metadata (blob/decode metadata)))))
|
|
@ -13,6 +13,8 @@
|
|||
[uxbox.util.blob :as blob]
|
||||
[uxbox.util.spec :as us]))
|
||||
|
||||
(declare decode-row)
|
||||
|
||||
;; --- Helpers & Specs
|
||||
|
||||
(s/def ::id ::us/uuid)
|
||||
|
@ -22,19 +24,26 @@
|
|||
|
||||
;; --- Query: Projects
|
||||
|
||||
;; (def ^:private projects-sql
|
||||
;; "select distinct on (p.id, p.created_at)
|
||||
;; p.*,
|
||||
;; array_agg(pg.id) over (
|
||||
;; partition by p.id
|
||||
;; order by pg.created_at
|
||||
;; range between unbounded preceding and unbounded following
|
||||
;; ) as pages
|
||||
;; from projects as p
|
||||
;; left join pages as pg
|
||||
;; on (pg.project_id = p.id)
|
||||
;; where p.user_id = $1
|
||||
;; order by p.created_at asc")
|
||||
|
||||
(def ^:private projects-sql
|
||||
"select distinct on (p.id, p.created_at)
|
||||
p.*,
|
||||
array_agg(pg.id) over (
|
||||
partition by p.id
|
||||
order by pg.created_at
|
||||
range between unbounded preceding and unbounded following
|
||||
) as pages
|
||||
from projects as p
|
||||
left join pages as pg
|
||||
on (pg.project_id = p.id)
|
||||
where p.user_id = $1
|
||||
order by p.created_at asc")
|
||||
"select p.*
|
||||
from project_users as pu
|
||||
inner join projects as p on (p.id = pu.project_id)
|
||||
where pu.can_edit = true
|
||||
and pu.user_id = $1;")
|
||||
|
||||
(s/def ::projects
|
||||
(s/keys :req-un [::user]))
|
||||
|
@ -42,5 +51,12 @@
|
|||
(sq/defquery ::projects
|
||||
[{:keys [user] :as params}]
|
||||
(-> (db/query db/pool [projects-sql user])
|
||||
(p/then (fn [rows]
|
||||
(mapv #(update % :pages vec) rows)))))
|
||||
(p/then' (partial mapv decode-row))))
|
||||
|
||||
;; --- Helpers
|
||||
|
||||
(defn decode-row
|
||||
[{:keys [metadata] :as row}]
|
||||
(when row
|
||||
(cond-> row
|
||||
metadata (assoc :metadata (blob/decode metadata)))))
|
||||
|
|
|
@ -4,7 +4,7 @@
|
|||
;;
|
||||
;; Copyright (c) 2019 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.services.queries.user-storage
|
||||
(ns uxbox.services.queries.user-attrs
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[promesa.core :as p]
|
||||
|
@ -20,13 +20,13 @@
|
|||
(cond-> row
|
||||
val (assoc :val (blob/decode val)))))
|
||||
|
||||
(s/def ::user-storage-entry
|
||||
(s/def ::user-attr
|
||||
(s/keys :req-un [::key ::user]))
|
||||
|
||||
(sq/defquery ::user-storage-entry
|
||||
(sq/defquery ::user-attr
|
||||
[{:keys [key user]}]
|
||||
(let [sql "select kv.*
|
||||
from user_storage as kv
|
||||
from user_attrs as kv
|
||||
where kv.user_id = $2
|
||||
and kv.key = $1"]
|
||||
(-> (db/query-one db/pool [sql key user])
|
|
@ -4,7 +4,7 @@
|
|||
;;
|
||||
;; Copyright (c) 2016 Andrey Antukh <niwi@niwi.nz>
|
||||
|
||||
(ns uxbox.services.queries.profiles
|
||||
(ns uxbox.services.queries.users
|
||||
(:require
|
||||
[clojure.spec.alpha :as s]
|
||||
[promesa.core :as p]
|
||||
|
@ -51,9 +51,7 @@
|
|||
(s/def ::profile
|
||||
(s/keys :req-un [::user]))
|
||||
|
||||
(sq/defquery :profile
|
||||
{:doc "Retrieve the user profile."
|
||||
:spec ::profile}
|
||||
(sq/defquery ::profile
|
||||
[{:keys [user] :as params}]
|
||||
(-> (get-profile db/pool user)
|
||||
(p/then' strip-private-attrs)))
|
|
@ -28,7 +28,7 @@
|
|||
IDispatcher
|
||||
(add [this key f metadata]
|
||||
(.put ^Map reg key (MapEntry/create f metadata))
|
||||
nil)
|
||||
this)
|
||||
|
||||
clojure.lang.IDeref
|
||||
(deref [_]
|
||||
|
@ -56,7 +56,7 @@
|
|||
|
||||
(defn dispatcher?
|
||||
[v]
|
||||
(instance? Dispatcher v))
|
||||
(instance? IDispatcher v))
|
||||
|
||||
(defmacro defservice
|
||||
[sname {:keys [dispatch-by interceptors]}]
|
||||
|
@ -118,5 +118,16 @@
|
|||
:code :spec-validation
|
||||
:explain (with-out-str
|
||||
(expound/printer data))
|
||||
:data data))))
|
||||
:data (::s/problems data)))))
|
||||
data)))})
|
||||
|
||||
(def wrap-errors
|
||||
{:error
|
||||
(fn [data]
|
||||
(let [error (:error data)
|
||||
mdata (meta (:request data))]
|
||||
(assoc data :error (ex/error :type :service-error
|
||||
:name (:spec mdata)
|
||||
:cause error))))})
|
||||
|
||||
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
(s/def ::code keyword?)
|
||||
(s/def ::mesage string?)
|
||||
(s/def ::hint string?)
|
||||
(s/def ::cause #(instance? Exception %))
|
||||
(s/def ::cause #(instance? Throwable %))
|
||||
(s/def ::error-params
|
||||
(s/keys :req-un [::type]
|
||||
:opt-un [::code
|
||||
|
|
|
@ -4,213 +4,5 @@
|
|||
;;
|
||||
;; 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)))
|
||||
(ns uxbox.util.http
|
||||
"Http related helpers.")
|
||||
|
|
|
@ -157,7 +157,9 @@
|
|||
(into rp p)
|
||||
(first n)
|
||||
(rest n)))
|
||||
[(str prefix (str/join join-with rs) suffix) rp]))))
|
||||
(if (empty? rs)
|
||||
["" []]
|
||||
[(str prefix (str/join join-with rs) suffix) rp])))))
|
||||
|
||||
(defn- process-param-tokens
|
||||
[sql]
|
||||
|
@ -168,7 +170,7 @@
|
|||
(def ^:private select-formatters
|
||||
[#(format-exprs (::select %) {:prefix "SELECT "})
|
||||
#(format-exprs (::from %) {:prefix "FROM "})
|
||||
#(format-exprs (::join %))
|
||||
#(format-exprs (::join %) {:join-with " "})
|
||||
#(format-exprs (::where %) {:prefix "WHERE ("
|
||||
:join-with ") AND ("
|
||||
:suffix ")"})
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue