;; 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) KALEIDOS INC Sucursal en EspaƱa SL (ns app.rpc.quotes "Penpot resource usage quotes." (:require [app.common.exceptions :as ex] [app.common.logging :as l] [app.common.schema :as sm] [app.common.time :as ct] [app.config :as cf] [app.db :as db] [app.worker :as wrk] [cuerdas.core :as str])) (defmulti check-quote ::id) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; PUBLIC API ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private schema:quote [:map {:title "Quote"} [::team-id {:optional true} ::sm/uuid] [::project-id {:optional true} ::sm/uuid] [::file-id {:optional true} ::sm/uuid] [::incr {:optional true} [::sm/int {:min 0}]] [::id :keyword] [::profile-id ::sm/uuid]]) (def valid-quote? (sm/lazy-validator schema:quote)) (def ^:private enabled (volatile! true)) (defn enable! "Enable quotes checking at runtime (from server REPL)." [] (vswap! enabled (constantly true))) (defn disable! "Disable quotes checking at runtime (from server REPL)." [] (vswap! enabled (constantly false))) (defn- check [cfg quote] (let [quote (merge cfg quote) id (::id quote)] (when-not (valid-quote? quote) (ex/raise :type :internal :code :invalid-quote-definition :hint "found invalid data for quote schema" :quote (name id))) (-> (assoc quote ::target (name id)) (check-quote)))) (defn check! ([cfg] (when (contains? cf/flags :quotes) (when @enabled (db/run! cfg check {})))) ([cfg & others] (when (contains? cf/flags :quotes) (when @enabled (db/run! cfg (fn [cfg] (run! (partial check cfg) others))))))) (defn- send-notification! [{:keys [::db/conn] :as params}] (l/warn :hint "max quote reached" :target (::target params) :profile-id (some-> params ::profile-id str) :team-id (some-> params ::team-id str) :project-id (some-> params ::project-id str) :file-id (some-> params ::file-id str) :quote (::quote params) :total (::total params) :incr (::inc params 1)) (when-let [admins (seq (cf/get :admins))] (let [subject (str/istr "[quotes:notification]: max quote reached ~(::target params)") content (str/istr "- Param: profile-id '~(::profile-id params)}'\n" "- Param: team-id '~(::team-id params)'\n" "- Param: project-id '~(::project-id params)'\n" "- Param: file-id '~(::file-id params)'\n" "- Quote ID: '~(::target params)'\n" "- Max: ~(::quote params)\n" "- Total: ~(::total params) (INCR ~(::incr params 1))\n")] (wrk/submit! {::db/conn conn ::wrk/task :sendmail ::wrk/delay (ct/duration "30s") ::wrk/max-retries 4 ::wrk/priority 200 ::wrk/dedupe true ::wrk/label "quotes-notification" ::wrk/params {:to (vec admins) :subject subject :body content}})))) (defn- generic-check! [{:keys [::db/conn ::incr ::quote-sql ::count-sql ::default ::target] :or {incr 1} :as params}] (let [quote (->> (db/exec! conn quote-sql) (map :quote) (reduce max (- Integer/MAX_VALUE))) quote (if (pos? quote) quote default) total (:total (db/exec-one! conn count-sql))] (when (> (+ total incr) quote) (if (contains? cf/flags :soft-quotes) (send-notification! (assoc params ::quote quote ::total total)) (ex/raise :type :restriction :code :max-quote-reached :target target :quote quote :count total))))) (def ^:private sql:get-quotes-1 "SELECT id, quote FROM usage_quote WHERE target = ? AND profile_id = ? AND team_id IS NULL AND project_id IS NULL AND file_id IS NULL;") (def ^:private sql:get-quotes-2 "SELECT id, quote FROM usage_quote WHERE target = ? AND ((team_id = ? AND (profile_id = ? OR profile_id IS NULL)) OR (profile_id = ? AND team_id IS NULL AND project_id IS NULL AND file_id IS NULL));") (def ^:private sql:get-quotes-3 "SELECT id, quote FROM usage_quote WHERE target = ? AND ((project_id = ? AND (profile_id = ? OR profile_id IS NULL)) OR (team_id = ? AND (profile_id = ? OR profile_id IS NULL)) OR (profile_id = ? AND team_id IS NULL AND project_id IS NULL AND file_id IS NULL));") (def ^:private sql:get-quotes-4 "SELECT id, quote FROM usage_quote WHERE target = ? AND ((file_id = ? AND (profile_id = ? OR profile_id IS NULL)) OR (project_id = ? AND (profile_id = ? OR profile_id IS NULL)) OR (team_id = ? AND (profile_id = ? OR profile_id IS NULL)) OR (profile_id = ? AND team_id IS NULL AND project_id IS NULL AND file_id IS NULL));") ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUOTE: TEAMS-PER-PROFILE ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private schema:teams-per-profile [:map [::profile-id ::sm/uuid]]) (def ^:private valid-teams-per-profile-quote? (sm/lazy-validator schema:teams-per-profile)) (def ^:private sql:get-teams-per-profile "SELECT count(*) AS total FROM team_profile_rel WHERE profile_id = ?") (defmethod check-quote ::teams-per-profile [{:keys [::profile-id ::target] :as quote}] (assert (valid-teams-per-profile-quote? quote) "invalid quote parameters") (-> quote (assoc ::default (cf/get :quotes-teams-per-profile Integer/MAX_VALUE)) (assoc ::quote-sql [sql:get-quotes-1 target profile-id]) (assoc ::count-sql [sql:get-teams-per-profile profile-id]) (generic-check!))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUOTE: ACCESS-TOKENS-PER-PROFILE ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private schema:access-tokens-per-profile [:map [::profile-id ::sm/uuid]]) (def ^:private valid-access-tokens-per-profile-quote? (sm/lazy-validator schema:access-tokens-per-profile)) (def ^:private sql:get-access-tokens-per-profile "SELECT count(*) AS total FROM access_token WHERE profile_id = ?") (defmethod check-quote ::access-tokens-per-profile [{:keys [::profile-id ::target] :as quote}] (assert (valid-access-tokens-per-profile-quote? quote) "invalid quote parameters") (-> quote (assoc ::default (cf/get :quotes-access-tokens-per-profile Integer/MAX_VALUE)) (assoc ::quote-sql [sql:get-quotes-1 target profile-id]) (assoc ::count-sql [sql:get-access-tokens-per-profile profile-id]) (generic-check!))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUOTE: PROJECTS-PER-TEAM ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private schema:projects-per-team [:map [::profile-id ::sm/uuid] [::team-id ::sm/uuid]]) (def ^:private valid-projects-per-team-quote? (sm/lazy-validator schema:projects-per-team)) (def ^:private sql:get-projects-per-team "SELECT count(*) AS total FROM project AS p WHERE p.team_id = ? AND p.deleted_at IS NULL") (defmethod check-quote ::projects-per-team [{:keys [::profile-id ::team-id ::target] :as quote}] (assert (valid-projects-per-team-quote? quote) "invalid quote parameters") (-> quote (assoc ::default (cf/get :quotes-projects-per-team Integer/MAX_VALUE)) (assoc ::quote-sql [sql:get-quotes-2 target team-id profile-id profile-id]) (assoc ::count-sql [sql:get-projects-per-team team-id]) (generic-check!))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUOTE: FONT-VARIANTS-PER-TEAM ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private schema:font-variants-per-team [:map [::profile-id ::sm/uuid] [::team-id ::sm/uuid]]) (def ^:private valid-font-variant-per-team-quote? (sm/lazy-validator schema:font-variants-per-team)) (def ^:private sql:get-font-variants-per-team "SELECT count(*) AS total FROM team_font_variant AS v WHERE v.team_id = ?") (defmethod check-quote ::font-variants-per-team [{:keys [::profile-id ::team-id ::target] :as quote}] (assert (valid-font-variant-per-team-quote? quote) "invalid quote parameters") (-> quote (assoc ::default (cf/get :quotes-font-variants-per-team Integer/MAX_VALUE)) (assoc ::quote-sql [sql:get-quotes-2 target team-id profile-id profile-id]) (assoc ::count-sql [sql:get-font-variants-per-team team-id]) (generic-check!))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUOTE: INVITATIONS-PER-TEAM ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private schema:invitations-per-team [:map [::profile-id ::sm/uuid] [::team-id ::sm/uuid]]) (def ^:private valid-invitations-per-team-quote? (sm/lazy-validator schema:invitations-per-team)) (def ^:private sql:get-invitations-per-team "SELECT count(*) AS total FROM team_invitation WHERE team_id = ?") (defmethod check-quote ::invitations-per-team [{:keys [::profile-id ::team-id ::target] :as quote}] (assert (valid-invitations-per-team-quote? quote) "invalid quote parameters") (-> quote (assoc ::default (cf/get :quotes-invitations-per-team Integer/MAX_VALUE)) (assoc ::quote-sql [sql:get-quotes-2 target team-id profile-id profile-id]) (assoc ::count-sql [sql:get-invitations-per-team team-id]) (generic-check!))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUOTE: PROFILES-PER-TEAM ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private schema:profiles-per-team [:map [::profile-id ::sm/uuid] [::team-id ::sm/uuid]]) (def ^:private valid-profiles-per-team-quote? (sm/lazy-validator schema:profiles-per-team)) (def ^:private sql:get-profiles-per-team "SELECT (SELECT count(*) FROM team_profile_rel WHERE team_id = ?) + (SELECT count(*) FROM team_invitation WHERE team_id = ? AND valid_until > now()) AS total;") ;; NOTE: the total number of profiles is determined by the number of ;; effective members plus ongoing valid invitations. (defmethod check-quote ::profiles-per-team [{:keys [::profile-id ::team-id ::target] :as quote}] (assert (valid-profiles-per-team-quote? quote) "invalid quote parameters") (-> quote (assoc ::default (cf/get :quotes-profiles-per-team Integer/MAX_VALUE)) (assoc ::quote-sql [sql:get-quotes-2 target team-id profile-id profile-id]) (assoc ::count-sql [sql:get-profiles-per-team team-id team-id]) (generic-check!))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUOTE: FILES-PER-PROJECT ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private schema:files-per-project [:map [::profile-id ::sm/uuid] [::project-id ::sm/uuid] [::team-id ::sm/uuid]]) (def ^:private valid-files-per-project-quote? (sm/lazy-validator schema:files-per-project)) (def ^:private sql:get-files-per-project "SELECT count(*) AS total FROM file AS f WHERE f.project_id = ? AND f.deleted_at IS NULL") (defmethod check-quote ::files-per-project [{:keys [::profile-id ::project-id ::team-id ::target] :as quote}] (assert (valid-files-per-project-quote? quote) "invalid quote parameters") (-> quote (assoc ::default (cf/get :quotes-files-per-project Integer/MAX_VALUE)) (assoc ::quote-sql [sql:get-quotes-3 target project-id profile-id team-id profile-id profile-id]) (assoc ::count-sql [sql:get-files-per-project project-id]) (generic-check!))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUOTE: COMMENT-THREADS-PER-FILE ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private schema:comment-threads-per-file [:map [::profile-id ::sm/uuid] [::project-id ::sm/uuid] [::team-id ::sm/uuid]]) (def ^:private valid-comment-threads-per-file-quote? (sm/lazy-validator schema:comment-threads-per-file)) (def ^:private sql:get-comment-threads-per-file "SELECT count(*) AS total FROM comment_thread AS ct WHERE ct.file_id = ?") (defmethod check-quote ::comment-threads-per-file [{:keys [::profile-id ::file-id ::team-id ::project-id ::target] :as quote}] (assert (valid-comment-threads-per-file-quote? quote) "invalid quote parameters") (-> quote (assoc ::default (cf/get :quotes-comment-threads-per-file Integer/MAX_VALUE)) (assoc ::quote-sql [sql:get-quotes-4 target file-id profile-id project-id profile-id team-id profile-id profile-id]) (assoc ::count-sql [sql:get-comment-threads-per-file file-id]) (generic-check!))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUOTE: COMMENTS-PER-FILE ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private schema:comments-per-file [:map [::profile-id ::sm/uuid] [::project-id ::sm/uuid] [::team-id ::sm/uuid]]) (def ^:private valid-comments-per-file-quote? (sm/lazy-validator schema:comments-per-file)) (def ^:private sql:get-comments-per-file "SELECT count(*) AS total FROM comment AS c JOIN comment_thread AS ct ON (ct.id = c.thread_id) WHERE ct.file_id = ?") (defmethod check-quote ::comments-per-file [{:keys [::profile-id ::file-id ::team-id ::project-id ::target] :as quote}] (assert (valid-comments-per-file-quote? quote) "invalid quote parameters") (-> quote (assoc ::default (cf/get :quotes-comments-per-file Integer/MAX_VALUE)) (assoc ::quote-sql [sql:get-quotes-4 target file-id profile-id project-id profile-id team-id profile-id profile-id]) (assoc ::count-sql [sql:get-comments-per-file file-id]) (generic-check!))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUOTE: SNAPSHOTS-PER-FILE ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private schema:snapshots-per-file [:map [::profile-id ::sm/uuid] [::project-id ::sm/uuid] [::team-id ::sm/uuid] [::file-id ::sm/uuid]]) (def ^:private valid-snapshots-per-file-quote? (sm/lazy-validator schema:snapshots-per-file)) (def ^:private sql:get-snapshots-per-file "SELECT count(*) AS total FROM file_change AS fc WHERE fc.file_id = ? AND fc.created_by = 'user' AND fc.deleted_at IS NULL AND fc.data IS NOT NULL") (defmethod check-quote ::snapshots-per-file [{:keys [::profile-id ::file-id ::team-id ::project-id ::target] :as quote}] (assert (valid-snapshots-per-file-quote? quote) "invalid quote parameters") (-> quote (assoc ::default (cf/get :quotes-snapshots-per-file Integer/MAX_VALUE)) (assoc ::quote-sql [sql:get-quotes-4 target file-id profile-id project-id profile-id team-id profile-id profile-id]) (assoc ::count-sql [sql:get-snapshots-per-file file-id]) (generic-check!))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUOTE: SNAPSHOTS-PER-TEAM ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private schema:snapshots-per-team [:map [::profile-id ::sm/uuid] [::team-id ::sm/uuid]]) (def ^:private valid-snapshots-per-team-quote? (sm/lazy-validator schema:snapshots-per-team)) (def ^:private sql:get-snapshots-per-team "SELECT count(*) AS total FROM file_change AS fc JOIN file AS f ON (f.id = fc.file_id) JOIN project AS p ON (p.id = f.project_id) WHERE p.team_id = ? AND fc.created_by = 'user' AND fc.deleted_at IS NULL AND fc.data IS NOT NULL") (defmethod check-quote ::snapshots-per-team [{:keys [::profile-id ::team-id ::target] :as quote}] (assert (valid-snapshots-per-team-quote? quote) "invalid quote parameters") (-> quote (assoc ::default (cf/get :quotes-snapshots-per-team Integer/MAX_VALUE)) (assoc ::quote-sql [sql:get-quotes-2 target team-id profile-id profile-id]) (assoc ::count-sql [sql:get-snapshots-per-team team-id]) (generic-check!))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUOTE: TEAM-ACCESS-REQUESTS-PER-TEAM ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private schema:team-access-requests-per-team [:map [::profile-id ::sm/uuid] [::team-id ::sm/uuid]]) (def ^:private valid-team-access-requests-per-team-quote? (sm/lazy-validator schema:team-access-requests-per-team)) (def ^:private sql:get-team-access-requests-per-team "SELECT count(*) AS total FROM team_access_request AS tar WHERE tar.team_id = ?") (defmethod check-quote ::team-access-requests-per-team [{:keys [::profile-id ::team-id ::target] :as quote}] (assert (valid-team-access-requests-per-team-quote? quote) "invalid quote parameters") (-> quote (assoc ::default (cf/get :quotes-team-access-requests-per-team Integer/MAX_VALUE)) (assoc ::quote-sql [sql:get-quotes-2 target team-id profile-id profile-id]) (assoc ::count-sql [sql:get-team-access-requests-per-team team-id]) (generic-check!))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUOTE: TEAM-ACCESS-REQUESTS-PER-REQUESTER ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private schema:team-access-requests-per-requester [:map [::profile-id ::sm/uuid]]) (def ^:private valid-team-access-requests-per-requester-quote? (sm/lazy-validator schema:team-access-requests-per-requester)) (def ^:private sql:get-team-access-requests-per-requester "SELECT count(*) AS total FROM team_access_request AS tar WHERE tar.requester_id = ?") (defmethod check-quote ::team-access-requests-per-requester [{:keys [::profile-id ::target] :as quote}] (assert (valid-team-access-requests-per-requester-quote? quote) "invalid quote parameters") (-> quote (assoc ::default (cf/get :quotes-team-access-requests-per-requester Integer/MAX_VALUE)) (assoc ::quote-sql [sql:get-quotes-1 target profile-id]) (assoc ::count-sql [sql:get-team-access-requests-per-requester profile-id]) (generic-check!))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUOTE: UPLOAD-SESSIONS-PER-PROFILE ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private schema:upload-sessions-per-profile [:map [::profile-id ::sm/uuid]]) (def ^:private valid-upload-sessions-per-profile-quote? (sm/lazy-validator schema:upload-sessions-per-profile)) (def ^:private sql:get-upload-sessions-per-profile "SELECT count(*) AS total FROM upload_session WHERE profile_id = ?") (defmethod check-quote ::upload-sessions-per-profile [{:keys [::profile-id ::target] :as quote}] (assert (valid-upload-sessions-per-profile-quote? quote) "invalid quote parameters") (-> quote (assoc ::default (cf/get :quotes-upload-sessions-per-profile Integer/MAX_VALUE)) (assoc ::quote-sql [sql:get-quotes-1 target profile-id]) (assoc ::count-sql [sql:get-upload-sessions-per-profile profile-id]) (generic-check!))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUOTE: MEDIA-STORAGE-BYTES-PER-TEAM ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (def ^:private schema:media-storage-bytes-per-team [:map [::profile-id ::sm/uuid] [::team-id ::sm/uuid]]) (def ^:private valid-media-storage-bytes-per-team-quote? (sm/lazy-validator schema:media-storage-bytes-per-team)) (def ^:private sql:get-media-storage-bytes-per-team "SELECT COALESCE(SUM(so.size), 0) AS total FROM ( SELECT fmo.media_id AS so_id FROM file_media_object AS fmo JOIN file AS f ON (f.id = fmo.file_id) JOIN project AS p ON (p.id = f.project_id) WHERE p.team_id = ? AND fmo.deleted_at IS NULL AND f.deleted_at IS NULL UNION SELECT fmo.thumbnail_id AS so_id FROM file_media_object AS fmo JOIN file AS f ON (f.id = fmo.file_id) JOIN project AS p ON (p.id = f.project_id) WHERE p.team_id = ? AND fmo.thumbnail_id IS NOT NULL AND fmo.deleted_at IS NULL AND f.deleted_at IS NULL UNION SELECT v.otf_file_id AS so_id FROM team_font_variant AS v WHERE v.team_id = ? AND v.otf_file_id IS NOT NULL AND v.deleted_at IS NULL UNION SELECT v.ttf_file_id AS so_id FROM team_font_variant AS v WHERE v.team_id = ? AND v.ttf_file_id IS NOT NULL AND v.deleted_at IS NULL UNION SELECT v.woff1_file_id AS so_id FROM team_font_variant AS v WHERE v.team_id = ? AND v.woff1_file_id IS NOT NULL AND v.deleted_at IS NULL UNION SELECT v.woff2_file_id AS so_id FROM team_font_variant AS v WHERE v.team_id = ? AND v.woff2_file_id IS NOT NULL AND v.deleted_at IS NULL ) AS refs JOIN storage_object AS so ON (so.id = refs.so_id) WHERE so.deleted_at IS NULL") (defmethod check-quote ::media-storage-bytes-per-team [{:keys [::profile-id ::team-id ::target] :as quote}] (assert (valid-media-storage-bytes-per-team-quote? quote) "invalid quote parameters") (-> quote (assoc ::default (cf/get :quotes-media-storage-bytes-per-team (* 20 1024 1024 1024))) (assoc ::quote-sql [sql:get-quotes-2 target team-id profile-id profile-id]) (assoc ::count-sql [sql:get-media-storage-bytes-per-team team-id team-id team-id team-id team-id team-id]) (generic-check!))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; QUOTE: DEFAULT ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; (defmethod check-quote :default [{:keys [::id]}] (ex/raise :type :internal :code :quote-not-defined :quote id :hint "backend using a quote identifier not defined"))