diff --git a/reset_password/metabase/reset_password/core.clj b/reset_password/metabase/reset_password/core.clj index 2f60c84fcc959709dd7538fb478d114a284e46e7..4d345fcb290b4d45f0e38245d40b262dc77093a8 100644 --- a/reset_password/metabase/reset_password/core.clj +++ b/reset_password/metabase/reset_password/core.clj @@ -8,7 +8,7 @@ [email-address] (let [user-id (or (db/select-one-id 'User, :email email-address) (throw (Exception. (format "No user found with email address '%s'. Please check the spelling and try again." email-address))))] - (user/set-user-password-reset-token user-id))) + (user/set-user-password-reset-token! user-id))) (defn -main [email-address] diff --git a/src/metabase/api/dashboard.clj b/src/metabase/api/dashboard.clj index 2da9075eb36f7a6e56a39748ece4346ede1d5138..26a232ece8487130268b4ac66c1d39cc2f57e078 100644 --- a/src/metabase/api/dashboard.clj +++ b/src/metabase/api/dashboard.clj @@ -8,7 +8,7 @@ [card :refer [Card]] [common :as common] [dashboard :refer [Dashboard]] - [dashboard-card :refer [DashboardCard create-dashboard-card update-dashboard-card delete-dashboard-card]]) + [dashboard-card :refer [DashboardCard create-dashboard-card! update-dashboard-card! delete-dashboard-card!]]) [metabase.models.revision :as revision])) (defendpoint GET "/" @@ -79,7 +79,7 @@ :series (or series [])} dashboard-card (-> (merge dashboard-card defaults) (update :series #(filter identity (map :id %))))] - (let-500 [result (create-dashboard-card dashboard-card)] + (let-500 [result (create-dashboard-card! dashboard-card)] (events/publish-event :dashboard-add-cards {:id id :actor_id *current-user-id* :dashcards [result]}) result))) @@ -99,7 +99,7 @@ (doseq [{dashcard-id :id :as dashboard-card} cards] ;; ensure the dashcard we are updating is part of the given dashboard (when (contains? dashcard-ids dashcard-id) - (update-dashboard-card (update dashboard-card :series #(filter identity (map :id %))))))) + (update-dashboard-card! (update dashboard-card :series #(filter identity (map :id %))))))) (events/publish-event :dashboard-reposition-cards {:id id :actor_id *current-user-id* :dashcards cards}) {:status :ok}) @@ -110,7 +110,7 @@ (check-404 (db/exists? Dashboard :id id)) (write-check Dashboard id) (when-let [dashboard-card (DashboardCard dashcardId)] - (check-500 (delete-dashboard-card dashboard-card *current-user-id*)) + (check-500 (delete-dashboard-card! dashboard-card *current-user-id*)) {:success true})) (defendpoint GET "/:id/revisions" @@ -126,7 +126,7 @@ {revision_id [Required Integer]} (check-404 (db/exists? Dashboard :id id)) (write-check Dashboard id) - (revision/revert + (revision/revert! :entity Dashboard :id id :user-id *current-user-id* diff --git a/src/metabase/api/field.clj b/src/metabase/api/field.clj index 077b441cd31b1e52ac6830cd278dbf53ed36677e..1c22399945cb2db9abefb50e3a81557a1fe7feba 100644 --- a/src/metabase/api/field.clj +++ b/src/metabase/api/field.clj @@ -5,7 +5,7 @@ [metabase.db.metadata-queries :as metadata] (metabase.models [hydrate :refer [hydrate]] [field :refer [Field] :as field] - [field-values :refer [FieldValues create-field-values-if-needed field-should-have-field-values?]]))) + [field-values :refer [FieldValues create-field-values-if-needed! field-should-have-field-values?]]))) (defannotation FieldSpecialType "Param must be a valid `Field` special type." @@ -75,7 +75,7 @@ (read-check field) (if-not (field-should-have-field-values? field) {:values {} :human_readable_values {}} - (create-field-values-if-needed field)))) + (create-field-values-if-needed! field)))) (defendpoint POST "/:id/value_map_update" @@ -90,7 +90,7 @@ (if-let [field-values-id (db/select-one-id FieldValues, :field_id id)] (check-500 (db/update! FieldValues field-values-id :human_readable_values values_map)) - (create-field-values-if-needed field values_map))) + (create-field-values-if-needed! field values_map))) {:status :success}) diff --git a/src/metabase/api/metric.clj b/src/metabase/api/metric.clj index 1122ff3e30e4249305ab3ee54ee645a7a2fddabb..95b911534ecaa6ca8f42a6d484042c2d6f51af36 100644 --- a/src/metabase/api/metric.clj +++ b/src/metabase/api/metric.clj @@ -33,7 +33,7 @@ revision_message [Required NonEmptyString] definition [Required Dict]} (check-superuser) - (check-404 (metric/exists-metric? id)) + (check-404 (metric/exists? id)) (metric/update-metric! {:id id :name name @@ -48,7 +48,7 @@ [id revision_message] {revision_message [Required NonEmptyString]} (check-superuser) - (check-404 (metric/exists-metric? id)) + (check-404 (metric/exists? id)) (metric/delete-metric! id *current-user-id* revision_message) {:success true}) @@ -57,7 +57,7 @@ "Fetch `Revisions` for `Metric` with ID." [id] (check-superuser) - (check-404 (metric/exists-metric? id)) + (check-404 (metric/exists? id)) (revision/revisions+details Metric id)) @@ -66,8 +66,8 @@ [id :as {{:keys [revision_id]} :body}] {revision_id [Required Integer]} (check-superuser) - (check-404 (metric/exists-metric? id)) - (revision/revert + (check-404 (metric/exists? id)) + (revision/revert! :entity Metric :id id :user-id *current-user-id* diff --git a/src/metabase/api/pulse.clj b/src/metabase/api/pulse.clj index 1cdc32d7cbf40a0c0a54cd9cd0781ad570bfa027..04a70bc99ecfc9e765917681a53b6ef09da36953 100644 --- a/src/metabase/api/pulse.clj +++ b/src/metabase/api/pulse.clj @@ -49,10 +49,10 @@ (check-404 (db/exists? Pulse :id id)) ;; prevent more than 5 cards ;; limit channel types to :email and :slack - (pulse/update-pulse {:id id - :name name - :cards (filter identity (map :id cards)) - :channels channels}) + (pulse/update-pulse! {:id id + :name name + :cards (filter identity (map :id cards)) + :channels channels}) (pulse/retrieve-pulse id)) diff --git a/src/metabase/api/revision.clj b/src/metabase/api/revision.clj index f21c542a5822b3d70fc3fbac4b8bdb8c96ae2ac6..50e18c13f13514bf1ed0826adcbc126e4935b46c 100644 --- a/src/metabase/api/revision.clj +++ b/src/metabase/api/revision.clj @@ -29,7 +29,7 @@ [:as {{:keys [entity id revision_id]} :body}] {entity Entity, id Integer, revision_id Integer} (check-404 (db/exists? revision/Revision :model (:name entity) :model_id id :id revision_id)) - (revision/revert + (revision/revert! :entity entity :id id :user-id *current-user-id* diff --git a/src/metabase/api/segment.clj b/src/metabase/api/segment.clj index 9974282d487e35da851f45490fc08fe6662375d1..5a423f1e2564fb7189a0ea015c13f165d278e741 100644 --- a/src/metabase/api/segment.clj +++ b/src/metabase/api/segment.clj @@ -16,7 +16,7 @@ definition [Required Dict]} (check-superuser) (checkp #(db/exists? Table :id table_id) "table_id" "Table does not exist.") - (check-500 (segment/create-segment table_id name description *current-user-id* definition))) + (check-500 (segment/create-segment! table_id name description *current-user-id* definition))) (defendpoint GET "/:id" @@ -34,7 +34,7 @@ definition [Required Dict]} (check-superuser) (check-404 (segment/exists-segment? id)) - (segment/update-segment + (segment/update-segment! {:id id :name name :description description @@ -49,7 +49,7 @@ {revision_message [Required NonEmptyString]} (check-superuser) (check-404 (segment/exists-segment? id)) - (segment/delete-segment id *current-user-id* revision_message) + (segment/delete-segment! id *current-user-id* revision_message) {:success true}) @@ -67,7 +67,7 @@ {revision_id [Required Integer]} (check-superuser) (check-404 (segment/exists-segment? id)) - (revision/revert + (revision/revert! :entity Segment :id id :user-id *current-user-id* diff --git a/src/metabase/api/session.clj b/src/metabase/api/session.clj index 4bb9d472e596d0064d7031ff4020a36c342ce9de..98ec89dd234cdeb058bd47ae2c4a4a33f3948328 100644 --- a/src/metabase/api/session.clj +++ b/src/metabase/api/session.clj @@ -8,7 +8,7 @@ [metabase.db :as db] [metabase.email.messages :as email] [metabase.events :as events] - (metabase.models [user :refer [User set-user-password set-user-password-reset-token]] + (metabase.models [user :refer [User set-user-password! set-user-password-reset-token!]] [session :refer [Session]] [setting :as setting]) [metabase.util :as u] @@ -74,7 +74,7 @@ (throttle/check (forgot-password-throttlers :email) email) ;; Don't leak whether the account doesn't exist, just pretend everything is ok (when-let [user-id (db/select-one-id User, :email email)] - (let [reset-token (set-user-password-reset-token user-id) + (let [reset-token (set-user-password-reset-token! user-id) password-reset-url (str (@(ns-resolve 'metabase.core 'site-url) request) "/auth/reset_password/" reset-token)] (email/send-password-reset-email email server-name password-reset-url) (log/info password-reset-url)))) @@ -104,7 +104,7 @@ {token Required password [Required ComplexPassword]} (or (when-let [{user-id :id, :as user} (valid-reset-token->user token)] - (set-user-password user-id password) + (set-user-password! user-id password) ;; after a successful password update go ahead and offer the client a new session that they can use (let [session-id (create-session user-id)] (events/publish-event :user-login {:user_id user-id, :session_id session-id, :first_login (not (boolean (:last_login user)))}) diff --git a/src/metabase/api/setup.clj b/src/metabase/api/setup.clj index 3bde8714ac1635cecba6ef305b45cb0120576881..d579f23839a251dcd8ba630a43163a039389937c 100644 --- a/src/metabase/api/setup.clj +++ b/src/metabase/api/setup.clj @@ -10,7 +10,7 @@ (metabase.models [database :refer [Database]] [session :refer [Session]] [setting :as setting] - [user :refer [User set-user-password]]) + [user :refer [User set-user-password!]]) [metabase.setup :as setup] [metabase.util :as u])) @@ -41,7 +41,7 @@ :password (str (java.util.UUID/randomUUID)) :is_superuser true)] ;; this results in a second db call, but it avoids redundant password code so figure it's worth it - (set-user-password (:id new-user) password) + (set-user-password! (:id new-user) password) ;; set a couple preferences (setting/set :site-name site_name) (setting/set :admin-email email) diff --git a/src/metabase/api/user.clj b/src/metabase/api/user.clj index f1d9ef7511cf7982b7457f39e2df3d9fbeb0048b..ef822306a61dffd2ac23bdea6c79866ef129a6b6 100644 --- a/src/metabase/api/user.clj +++ b/src/metabase/api/user.clj @@ -4,7 +4,7 @@ [metabase.api.common :refer :all] [metabase.db :as db] [metabase.email.messages :as email] - [metabase.models.user :refer [User create-user set-user-password set-user-password-reset-token form-password-reset-url]])) + [metabase.models.user :refer [User create-user! set-user-password! set-user-password-reset-token! form-password-reset-url]])) (defn- check-self-or-superuser "Check that USER-ID is `*current-user-id*` or that `*current-user*` is a superuser, or throw a 403." @@ -37,7 +37,7 @@ ;; now return the existing user whether they were originally active or not (User (:id existing-user))) ;; new user account, so create it - (create-user first_name last_name email, :password password, :send-welcome true, :invitor @*current-user*))) + (create-user! first_name last_name email, :password password, :send-welcome true, :invitor @*current-user*))) (defendpoint GET "/current" @@ -80,7 +80,7 @@ (let-404 [user (db/select-one [User :password_salt :password], :id id, :is_active true)] (when (= id *current-user-id*) (checkp (creds/bcrypt-verify (str (:password_salt user) old_password) (:password user)) "old_password" "Invalid password"))) - (set-user-password id password) + (set-user-password! id password) (User id)) @@ -97,7 +97,7 @@ [id] (check-superuser) (when-let [user (User :id id, :is_active true)] - (let [reset-token (set-user-password-reset-token id) + (let [reset-token (set-user-password-reset-token! id) ;; NOTE: the new user join url is just a password reset with an indicator that this is a first time user join-url (str (form-password-reset-url reset-token) "#new")] (email/send-new-user-email user @*current-user* join-url)))) diff --git a/src/metabase/events.clj b/src/metabase/events.clj index 59ded13336badb05e862fc96bd00be4c8e5ddbe6..763e23e692b7c6e46ae66c884125848accec1bcc 100644 --- a/src/metabase/events.clj +++ b/src/metabase/events.clj @@ -10,6 +10,7 @@ needed and add your events subscribers to the bus as usual via `start-event-listener`." (:require [clojure.core.async :as async] [clojure.java.classpath :as classpath] + [clojure.string :as s] [clojure.tools.logging :as log] [clojure.tools.namespace.find :as ns-find] (metabase [config :as config] @@ -79,9 +80,8 @@ "Convenience method for subscribing to a series of topics against a single channel." [topics channel] {:pre [(coll? topics)]} - (loop [[topic & rest] (vec topics)] - (subscribe-to-topic topic channel) - (when rest (recur rest)))) + (doseq [topic topics] + (subscribe-to-topic topic channel))) (defn start-event-listener "Initialize an event listener which runs on a background thread via `go-loop`." @@ -104,13 +104,13 @@ (defn topic->model - "Determine a valid `model` identifier for the given `topic`." + "Determine a valid `model` identifier for the given TOPIC." [topic] ;; just take the first part of the topic name after splitting on dashes. - (first (clojure.string/split (name topic) #"-"))) + (first (s/split (name topic) #"-"))) (defn object->model-id - "Determine the appropriate `model_id` (if possible) for a given `object`." + "Determine the appropriate `model_id` (if possible) for a given OBJECT." [topic object] (if (contains? (set (keys object)) :id) (:id object) @@ -118,6 +118,6 @@ (get object (keyword (format "%s_id" model)))))) (defn object->user-id - "Determine the appropriate `user_id` (if possible) for a given `object`." + "Determine the appropriate `user_id` (if possible) for a given OBJECT." [object] (or (:actor_id object) (:user_id object) (:creator_id object))) diff --git a/src/metabase/events/activity_feed.clj b/src/metabase/events/activity_feed.clj index ab0e44c52312dceac6a227bc53918378f9585592..337c75bd1da6bcf1675539bdda82d51c4be145da 100644 --- a/src/metabase/events/activity_feed.clj +++ b/src/metabase/events/activity_feed.clj @@ -43,7 +43,7 @@ (let [details-fn #(select-keys % [:name :description :public_perms]) database-id (get-in object [:dataset_query :database]) table-id (get-in object [:dataset_query :query :source_table])] - (activity/record-activity + (activity/record-activity! :topic topic :object object :details-fn details-fn @@ -60,7 +60,7 @@ (-> (db/select-one [Card :name :description :public_perms], :id card_id) (assoc :id id) (assoc :card_id card_id))))))] - (activity/record-activity + (activity/record-activity! :topic topic :object object :details-fn (case topic @@ -73,7 +73,7 @@ (let [details-fn #(select-keys % [:name :description :revision_message]) table-id (:table_id object) database-id (table/table-id->database-id table-id)] - (activity/record-activity + (activity/record-activity! :topic topic :object object :details-fn details-fn @@ -82,7 +82,7 @@ (defn- process-pulse-activity [topic object] (let [details-fn #(select-keys % [:name :public_perms])] - (activity/record-activity + (activity/record-activity! :topic topic :object object :details-fn details-fn))) @@ -91,7 +91,7 @@ (let [details-fn #(select-keys % [:name :description :revision_message]) table-id (:table_id object) database-id (table/table-id->database-id table-id)] - (activity/record-activity + (activity/record-activity! :topic topic :object object :details-fn details-fn @@ -102,7 +102,7 @@ ;; we only care about login activity when its the users first session (a.k.a. new user!) (when (and (= :user-login topic) (:first_login object)) - (activity/record-activity + (activity/record-activity! :topic :user-joined :user-id (:user_id object) :model-id (:user_id object)))) diff --git a/src/metabase/events/dependencies.clj b/src/metabase/events/dependencies.clj index addad4a8c28150db6ef55c43580122f0150114ee..32c0016b91cd5753689d1defb455f00383fbf141 100644 --- a/src/metabase/events/dependencies.clj +++ b/src/metabase/events/dependencies.clj @@ -38,7 +38,7 @@ ;; entity must support dependency tracking to continue (when (satisfies? IDependent entity) (when-let [deps (dependency/dependencies entity id object)] - (dependency/update-dependencies entity id deps))))) + (dependency/update-dependencies! entity id deps))))) (catch Throwable e (log/warn (format "Failed to process dependencies event. %s" (:topic dependency-event)) e)))) diff --git a/src/metabase/events/revision.clj b/src/metabase/events/revision.clj index d77c94dbf6d0341e8806b19f6c96b68ddb76dee7..7b49d831281be8a6609acd6947241926441d31e6 100644 --- a/src/metabase/events/revision.clj +++ b/src/metabase/events/revision.clj @@ -5,7 +5,7 @@ (metabase.models [card :refer [Card]] [dashboard :refer [Dashboard]] [metric :refer [Metric]] - [revision :refer [push-revision]] + [revision :refer [push-revision!]] [segment :refer [Segment]]))) @@ -45,30 +45,30 @@ revision-message (:revision_message object)] ;; TODO: seems unnecessary to select each entity again, is there a reason we aren't using `object` directly? (case model - "card" (push-revision :entity Card, - :id id, - :object (Card id), - :user-id user-id, - :is-creation? (= :card-create topic) - :message revision-message) - "dashboard" (push-revision :entity Dashboard, - :id id, - :object (Dashboard id), - :user-id user-id, - :is-creation? (= :dashboard-create topic) - :message revision-message) - "metric" (push-revision :entity Metric, - :id id, - :object (Metric id), - :user-id user-id, - :is-creation? (= :metric-create topic) - :message revision-message) - "segment" (push-revision :entity Segment, - :id id, - :object (Segment id), - :user-id user-id, - :is-creation? (= :segment-create topic) - :message revision-message)))) + "card" (push-revision! :entity Card, + :id id, + :object (Card id), + :user-id user-id, + :is-creation? (= :card-create topic) + :message revision-message) + "dashboard" (push-revision! :entity Dashboard, + :id id, + :object (Dashboard id), + :user-id user-id, + :is-creation? (= :dashboard-create topic) + :message revision-message) + "metric" (push-revision! :entity Metric, + :id id, + :object (Metric id), + :user-id user-id, + :is-creation? (= :metric-create topic) + :message revision-message) + "segment" (push-revision! :entity Segment, + :id id, + :object (Segment id), + :user-id user-id, + :is-creation? (= :segment-create topic) + :message revision-message)))) (catch Throwable e (log/warn (format "Failed to process revision event. %s" (:topic revision-event)) e)))) diff --git a/src/metabase/models/activity.clj b/src/metabase/models/activity.clj index 1669713d8e5af9c1afb8ad66782a5cbcbc8a5978..03c9b0395f7396ed6d296ec41de44f2a3164f1b8 100644 --- a/src/metabase/models/activity.clj +++ b/src/metabase/models/activity.clj @@ -40,7 +40,7 @@ ;; ## Persistence Functions -(defn record-activity +(defn record-activity! "Inserts a new `Activity` entry. Takes the following kwargs: @@ -53,12 +53,13 @@ :model Optional. name of the model representing the activity. defaults to (events/topic->model topic) :model-id Optional. ID of the model representing the activity. defaults to (events/object->model-id topic object) - ex: (record-activity + ex: (record-activity! :topic :segment-update :object segment :database-id 1 :table-id 13 :details-fn #(dissoc % :some-key))" + {:style/indent 0} [& {:keys [topic object details-fn database-id table-id user-id model model-id]}] {:pre [(keyword? topic)]} (let [object (or object {})] diff --git a/src/metabase/models/card.clj b/src/metabase/models/card.clj index c27cf9f5bc4298430da7c99d331b0d04d25f980a..987c036823cdef47d970ca484d7711135f476b72 100644 --- a/src/metabase/models/card.clj +++ b/src/metabase/models/card.clj @@ -26,8 +26,7 @@ "Return the number of Dashboards this Card is in." {:hydrate :dashboard_count} [{:keys [id]}] - (:count (db/select-one ['DashboardCard [:%count.* :count]] - :card_id id))) + (db/select-one-count 'DashboardCard, :card_id id)) (defn labels "Return `Labels` for CARD." diff --git a/src/metabase/models/dashboard.clj b/src/metabase/models/dashboard.clj index ebe27facce0985433483380c0783d81562a260a4..1a2ea6afb724a906aaf82ea469ea342a8420a2aa 100644 --- a/src/metabase/models/dashboard.clj +++ b/src/metabase/models/dashboard.clj @@ -43,7 +43,7 @@ (-> (select-keys dashboard-card [:sizeX :sizeY :row :col :id :card_id]) (assoc :series (mapv :id (dashboard-card/series dashboard-card))))))))) -(defn revert-dashboard +(defn- revert-dashboard! "Revert a `Dashboard` to the state defined by SERIALIZED-DASHBOARD." [dashboard-id user-id serialized-dashboard] ;; Update the dashboard description / name / permissions @@ -60,15 +60,15 @@ current-card (id->current-card dashcard-id)] (cond ;; If card is in current-cards but not serialized-cards then we need to delete it - (not serialized-card) (dashboard-card/delete-dashboard-card current-card user-id) + (not serialized-card) (dashboard-card/delete-dashboard-card! current-card user-id) ;; If card is in serialized-cards but not current-cards we need to add it - (not current-card) (dashboard-card/create-dashboard-card (assoc serialized-card - :dashboard_id dashboard-id - :creator_id user-id)) + (not current-card) (dashboard-card/create-dashboard-card! (assoc serialized-card + :dashboard_id dashboard-id + :creator_id user-id)) ;; If card is in both we need to change :sizeX, :sizeY, :row, and :col to match serialized-card as needed - :else (dashboard-card/update-dashboard-card serialized-card))))) + :else (dashboard-card/update-dashboard-card! serialized-card))))) serialized-dashboard) @@ -112,6 +112,6 @@ (u/strict-extend (class Dashboard) revision/IRevisioned (merge revision/IRevisionedDefaults - {:serialize-instance (fn [_ _ dashboard] (serialize-dashboard dashboard)) - :revert-to-revision (u/drop-first-arg revert-dashboard) - :diff-str (u/drop-first-arg diff-dashboards-str)})) + {:serialize-instance (fn [_ _ dashboard] (serialize-dashboard dashboard)) + :revert-to-revision! (u/drop-first-arg revert-dashboard!) + :diff-str (u/drop-first-arg diff-dashboards-str)})) diff --git a/src/metabase/models/dashboard_card.clj b/src/metabase/models/dashboard_card.clj index e849867be4afb954b6df6b083cab88e4f95b6036..1e5d177539900f0c34017c5f5bf0f191fdcb14f3 100644 --- a/src/metabase/models/dashboard_card.clj +++ b/src/metabase/models/dashboard_card.clj @@ -57,7 +57,7 @@ (-> (DashboardCard id) (hydrate :series))) -(defn update-dashboard-card-series +(defn update-dashboard-card-series! "Update the `DashboardCardSeries` for a given `DashboardCard`. CARD-IDS should be a definitive collection of *all* IDs of cards for the dashboard card in the desired order. @@ -78,7 +78,7 @@ card-ids)] (db/insert-many! DashboardCardSeries cards)))) -(defn update-dashboard-card +(defn update-dashboard-card! "Update an existing `DashboardCard`, including all `DashboardCardSeries`. Returns the updated `DashboardCard` or throws an Exception." [{:keys [id series] :as dashboard-card}] @@ -91,12 +91,12 @@ (db/update! DashboardCard id, :sizeX sizeX, :sizeY sizeY, :row row, :col col)) ;; update series (only if they changed) (when (not= series (map :card_id (db/select [DashboardCardSeries :card_id], :dashboardcard_id id, {:order-by [[:position :asc]]}))) - (update-dashboard-card-series dashboard-card series)) + (update-dashboard-card-series! dashboard-card series)) ;; fetch the fully updated dashboard card then return it (and fire off an event) (->> (retrieve-dashboard-card id) (events/publish-event :dashboard-card-update))))) -(defn create-dashboard-card +(defn create-dashboard-card! "Create a new `DashboardCard` by inserting it into the database along with all associated pieces of data such as `DashboardCardSeries`. Returns the newly created `DashboardCard` or throws an Exception." [{:keys [dashboard_id card_id creator_id] :as dashboard-card}] @@ -114,14 +114,14 @@ :row row :col col)] ;; add series to the DashboardCard - (update-dashboard-card-series dashboard-card series) + (update-dashboard-card-series! dashboard-card series) ;; return the full DashboardCard (and record our create event) (-> (retrieve-dashboard-card id) (assoc :actor_id creator_id) (->> (events/publish-event :dashboard-card-create)) (dissoc :actor_id)))))) -(defn delete-dashboard-card +(defn delete-dashboard-card! "Delete a `DashboardCard`." [dashboard-card user-id] {:pre [(map? dashboard-card) diff --git a/src/metabase/models/dependency.clj b/src/metabase/models/dependency.clj index da3346866555346935ad73f9b562e163978b0ea2..b4179e9746b64bc37d6fa299c3fd4f17562a0f7f 100644 --- a/src/metabase/models/dependency.clj +++ b/src/metabase/models/dependency.clj @@ -31,7 +31,7 @@ (integer? id)]} (db/select Dependency, :model (:name entity), :model_id id)) -(defn update-dependencies +(defn update-dependencies! "Update the set of `Dependency` objects for a given entity." [entity id deps] {:pre [(i/metabase-entity? entity) diff --git a/src/metabase/models/field.clj b/src/metabase/models/field.clj index c48594d9d13d7a2ea4e0af6f66b6e52514f878ed..f8ab4f13cb65915cb37820b76d61aa2c01ad26a4 100644 --- a/src/metabase/models/field.clj +++ b/src/metabase/models/field.clj @@ -89,7 +89,7 @@ (defn- pre-cascade-delete [{:keys [id]}] (db/cascade-delete! Field :parent_id id) (db/cascade-delete! ForeignKey {:where [:or [:= :origin_id id] - [:= :destination_id id]]}) + [:= :destination_id id]]}) (db/cascade-delete! 'FieldValues :field_id id)) (defn ^:hydrate target @@ -137,69 +137,71 @@ :pre-insert pre-insert :pre-cascade-delete pre-cascade-delete})) +(def ^:private ^:const pattern+base-types+special-type + "Tuples of [pattern set-of-valid-base-types special-type] -(def ^{:arglists '([field-name base-type])} infer-field-special-type - "If `name` and `base-type` matches a known pattern, return the `special_type` we should assign to it." + * Convert field name to lowercase before matching against a pattern + * Consider a nil set-of-valid-base-types to mean \"match any base type\"" (let [bool-or-int #{:BooleanField :BigIntegerField :IntegerField} float #{:DecimalField :FloatField} int-or-text #{:BigIntegerField :IntegerField :CharField :TextField} - text #{:CharField :TextField} - ;; tuples of [pattern set-of-valid-base-types special-type - ;; * Convert field name to lowercase before matching against a pattern - ;; * consider a nil set-of-valid-base-types to mean "match any base type" - pattern+base-types+special-type [[#"^.*_lat$" float :latitude] - [#"^.*_lon$" float :longitude] - [#"^.*_lng$" float :longitude] - [#"^.*_long$" float :longitude] - [#"^.*_longitude$" float :longitude] - [#"^.*_rating$" int-or-text :category] - [#"^.*_type$" int-or-text :category] - [#"^.*_url$" text :url] - [#"^_latitude$" float :latitude] - [#"^active$" bool-or-int :category] - [#"^city$" text :city] - [#"^country$" text :country] - [#"^countryCode$" text :country] - [#"^currency$" int-or-text :category] - [#"^first_name$" text :name] - [#"^full_name$" text :name] - [#"^gender$" int-or-text :category] - [#"^last_name$" text :name] - [#"^lat$" float :latitude] - [#"^latitude$" float :latitude] - [#"^lon$" float :longitude] - [#"^lng$" float :longitude] - [#"^long$" float :longitude] - [#"^longitude$" float :longitude] - [#"^name$" text :name] - [#"^postalCode$" int-or-text :zip_code] - [#"^postal_code$" int-or-text :zip_code] - [#"^rating$" int-or-text :category] - [#"^role$" int-or-text :category] - [#"^sex$" int-or-text :category] - [#"^state$" text :state] - [#"^status$" int-or-text :category] - [#"^type$" int-or-text :category] - [#"^url$" text :url] - [#"^zip_code$" int-or-text :zip_code] - [#"^zipcode$" int-or-text :zip_code]]] - ;; Check that all the pattern tuples are valid - (doseq [[name-pattern base-types special-type] pattern+base-types+special-type] - (assert (= (type name-pattern) java.util.regex.Pattern)) - (assert (every? (partial contains? base-types) base-types)) - (assert (contains? special-types special-type))) - - (fn [field-name base_type] - (when (and (string? field-name) - (keyword? base_type)) - (or (when (= "id" (s/lower-case field-name)) :id) - (when-let [matching-pattern (m/find-first (fn [[name-pattern valid-base-types _]] - (and (or (nil? valid-base-types) - (contains? valid-base-types base_type)) - (re-matches name-pattern (s/lower-case field-name)))) - pattern+base-types+special-type)] - ;; the actual special-type is the last element of the pattern - (last matching-pattern))))))) + text #{:CharField :TextField}] + [[#"^.*_lat$" float :latitude] + [#"^.*_lon$" float :longitude] + [#"^.*_lng$" float :longitude] + [#"^.*_long$" float :longitude] + [#"^.*_longitude$" float :longitude] + [#"^.*_rating$" int-or-text :category] + [#"^.*_type$" int-or-text :category] + [#"^.*_url$" text :url] + [#"^_latitude$" float :latitude] + [#"^active$" bool-or-int :category] + [#"^city$" text :city] + [#"^country$" text :country] + [#"^countryCode$" text :country] + [#"^currency$" int-or-text :category] + [#"^first_name$" text :name] + [#"^full_name$" text :name] + [#"^gender$" int-or-text :category] + [#"^last_name$" text :name] + [#"^lat$" float :latitude] + [#"^latitude$" float :latitude] + [#"^lon$" float :longitude] + [#"^lng$" float :longitude] + [#"^long$" float :longitude] + [#"^longitude$" float :longitude] + [#"^name$" text :name] + [#"^postalCode$" int-or-text :zip_code] + [#"^postal_code$" int-or-text :zip_code] + [#"^rating$" int-or-text :category] + [#"^role$" int-or-text :category] + [#"^sex$" int-or-text :category] + [#"^state$" text :state] + [#"^status$" int-or-text :category] + [#"^type$" int-or-text :category] + [#"^url$" text :url] + [#"^zip_code$" int-or-text :zip_code] + [#"^zipcode$" int-or-text :zip_code]])) + +;; Check that all the pattern tuples are valid +(doseq [[name-pattern base-types special-type] pattern+base-types+special-type] + (assert (instance? java.util.regex.Pattern name-pattern)) + (assert (every? (partial contains? base-types) base-types)) + (assert (contains? special-types special-type))) + +(defn infer-field-special-type + "If `name` and `base-type` matches a known pattern, return the `special_type` we should assign to it." + [field-name base_type] + (when (and (string? field-name) + (keyword? base_type)) + (or (when (= "id" (s/lower-case field-name)) :id) + (when-let [matching-pattern (m/find-first (fn [[name-pattern valid-base-types]] + (and (or (nil? valid-base-types) + (contains? valid-base-types base_type)) + (re-matches name-pattern (s/lower-case field-name)))) + pattern+base-types+special-type)] + ;; the actual special-type is the last element of the pattern + (last matching-pattern))))) (defn update-field! diff --git a/src/metabase/models/field_values.clj b/src/metabase/models/field_values.clj index 0acf075d33bd34d0152828f8e77ec9d17a97e91c..6c843b0d5ef6ad2aab88ce895143192a221724e1 100644 --- a/src/metabase/models/field_values.clj +++ b/src/metabase/models/field_values.clj @@ -37,7 +37,7 @@ (or (contains? #{:category :city :state :country :name} (keyword special_type)) (= (keyword base_type) :BooleanField)))) -(defn- create-field-values +(defn- create-field-values! "Create `FieldValues` for a `Field`." {:arglists '([field] [field human-readable-values])} [{field-id :id, field-name :name, :as field} & [human-readable-values]] @@ -56,9 +56,9 @@ (if-let [field-values (FieldValues :field_id field-id)] (db/update! FieldValues (:id field-values) :values ((resolve 'metabase.db.metadata-queries/field-distinct-values) field)) - (create-field-values field))) + (create-field-values! field))) -(defn create-field-values-if-needed +(defn create-field-values-if-needed! "Create `FieldValues` for a `Field` if they *should* exist but don't already exist. Returns the existing or newly created `FieldValues` for `Field`." {:arglists '([field] @@ -67,9 +67,9 @@ {:pre [(integer? field-id)]} (when (field-should-have-field-values? field) (or (FieldValues :field_id field-id) - (create-field-values field human-readable-values)))) + (create-field-values! field human-readable-values)))) -(defn save-field-values +(defn save-field-values! "Save the `FieldValues` for FIELD-ID, creating them if needed, otherwise updating them." [field-id values] {:pre [(integer? field-id) @@ -78,7 +78,7 @@ (db/update! FieldValues (:id field-values), :values values) (db/insert! FieldValues :field_id field-id, :values values))) -(defn clear-field-values +(defn clear-field-values! "Remove the `FieldValues` for FIELD-ID." [field-id] {:pre [(integer? field-id)]} diff --git a/src/metabase/models/foreign_key.clj b/src/metabase/models/foreign_key.clj index de548de908f222a5f80b248c35c60fa4ee81fe9b..20629d6a67b6bd26aeeae7ba2ddab3c8c4880e15 100644 --- a/src/metabase/models/foreign_key.clj +++ b/src/metabase/models/foreign_key.clj @@ -1,12 +1,10 @@ -(ns metabase.models.foreign-key +(ns ^:deprecated metabase.models.foreign-key (:require [metabase.models.interface :as i] [metabase.util :as u])) -(def ^:const relationships - "Valid values for `ForeginKey.relationship`." - #{:1t1 :Mt1 :MtM}) +;; This namespace is no longer used. We need to keep it around for the time being because some data migrations still refere -(i/defentity ForeignKey :metabase_foreignkey) +(i/defentity ^:deprecated ForeignKey :metabase_foreignkey) (u/strict-extend (class ForeignKey) i/IEntity diff --git a/src/metabase/models/metric.clj b/src/metabase/models/metric.clj index 6cef41cdbecd928002f377b4a6a259ab1e70eddd..36b1d3cbf489fb5a236b1b748568b591cc0980b4 100644 --- a/src/metabase/models/metric.clj +++ b/src/metabase/models/metric.clj @@ -84,7 +84,7 @@ (-> (events/publish-event :metric-create metric) (hydrate :creator)))) -(defn exists-metric? +(defn exists? "Predicate function which checks for a given `Metric` with ID. Returns true if `Metric` exists and is active, false otherwise." [id] diff --git a/src/metabase/models/pulse.clj b/src/metabase/models/pulse.clj index e8c562194fa2960c304edb37ba246398504c9b67..c0bbd4b899d719c05a1c47b21e27e3a1bed1d2e5 100644 --- a/src/metabase/models/pulse.clj +++ b/src/metabase/models/pulse.clj @@ -48,8 +48,7 @@ ;; ## Persistence Functions -;; TODO - this should be renamed `update-pulse-cards!` -(defn update-pulse-cards +(defn update-pulse-cards! "Update the `PulseCards` for a given PULSE. CARD-IDS should be a definitive collection of *all* IDs of cards for the pulse in the desired order. @@ -68,8 +67,8 @@ (let [cards (map-indexed (fn [idx itm] {:pulse_id id :card_id itm :position idx}) card-ids)] (db/insert-many! PulseCard cards)))) -;; TODO - Rename to `create-update-delete-channel!` -(defn- create-update-delete-channel + +(defn- create-update-delete-channel! "Utility function which determines how to properly update a single pulse channel." [pulse-id new-channel existing-channel] ;; NOTE that we force the :id of the channel being updated to the :id we *know* from our @@ -82,16 +81,15 @@ :schedule_frame (keyword (:schedule_frame new-channel))))] (cond ;; 1. in channels, NOT in db-channels = CREATE - (and channel (not existing-channel)) (pulse-channel/create-pulse-channel channel) + (and channel (not existing-channel)) (pulse-channel/create-pulse-channel! channel) ;; 2. NOT in channels, in db-channels = DELETE (and (nil? channel) existing-channel) (db/cascade-delete! PulseChannel :id (:id existing-channel)) ;; 3. in channels, in db-channels = UPDATE - (and channel existing-channel) (pulse-channel/update-pulse-channel channel) + (and channel existing-channel) (pulse-channel/update-pulse-channel! channel) ;; 4. NOT in channels, NOT in db-channels = NO-OP :else nil))) -;; TODO - Rename to `update-pulse-channels!` -(defn update-pulse-channels +(defn update-pulse-channels! "Update the `PulseChannels` for a given PULSE. CHANNELS should be a definitive collection of *all* of the channels for the the pulse. @@ -105,7 +103,7 @@ (every? map? channels)]} (let [new-channels (group-by (comp keyword :channel_type) channels) old-channels (group-by (comp keyword :channel_type) (db/select PulseChannel :pulse_id id)) - handle-channel #(create-update-delete-channel id (first (get new-channels %)) (first (get old-channels %)))] + handle-channel #(create-update-delete-channel! id (first (get new-channels %)) (first (get old-channels %)))] (assert (= 0 (count (get new-channels nil))) "Cannot have channels without a :channel_type attribute") ;; for each of our possible channel types call our handler function (dorun (map handle-channel (vec (keys pulse-channel/channel-types)))))) @@ -125,8 +123,7 @@ (hydrate :creator :cards [:channels :recipients]))] (m/dissoc-in pulse [:details :emails]))) -;; TODO - rename to `update-pulse!` -(defn update-pulse +(defn update-pulse! "Update an existing `Pulse`, including all associated data such as: `PulseCards`, `PulseChannels`, and `PulseChannelRecipients`. Returns the updated `Pulse` or throws an Exception." @@ -143,9 +140,9 @@ (db/update! Pulse id, :name name) ;; update cards (only if they changed) (when (not= cards (map :card_id (db/select [PulseCard :card_id], :pulse_id id, {:order-by [[:position :asc]]}))) - (update-pulse-cards pulse cards)) + (update-pulse-cards! pulse cards)) ;; update channels - (update-pulse-channels pulse channels) + (update-pulse-channels! pulse channels) ;; fetch the fully updated pulse and return it (and fire off an event) (->> (retrieve-pulse id) (events/publish-event :pulse-update)))) @@ -169,8 +166,8 @@ :creator_id creator-id :name pulse-name)] ;; add card-ids to the Pulse - (update-pulse-cards pulse card-ids) + (update-pulse-cards! pulse card-ids) ;; add channels to the Pulse - (update-pulse-channels pulse channels) + (update-pulse-channels! pulse channels) ;; return the full Pulse (and record our create event) (events/publish-event :pulse-create (retrieve-pulse id))))) diff --git a/src/metabase/models/pulse_channel.clj b/src/metabase/models/pulse_channel.clj index 55bd2ae05faa9d373b70d454136f83bc3aa5b070..905e3c4eb64c30cc6892885d007177afa6ddc85b 100644 --- a/src/metabase/models/pulse_channel.clj +++ b/src/metabase/models/pulse_channel.clj @@ -172,6 +172,7 @@ ;; this is here specifically to allow for cases where day doesn't have to match [:= :schedule_day monthly-schedule-day-or-nil]]]]]}))) + (defn update-recipients! "Update the `PulseChannelRecipients` for PULSE-CHANNEL. USER-IDS should be a definitive collection of *all* IDs of users who should receive the pulse. @@ -194,8 +195,8 @@ :pulse_channel_id id :user_id [:in recipients-])))) -;; TODO - rename -> `update-pulse-channel!` -(defn update-pulse-channel + +(defn update-pulse-channel! "Updates an existing `PulseChannel` along with all related data associated with the channel such as `PulseChannelRecipients`." [{:keys [id channel_type enabled details recipients schedule_type schedule_day schedule_hour schedule_frame] :or {details {} @@ -222,8 +223,8 @@ (when (supports-recipients? channel_type) (update-recipients! id (or (get recipients-by-type true) []))))) -;; TODO - rename -> `create-pulse-channel!` -(defn create-pulse-channel + +(defn create-pulse-channel! "Create a new `PulseChannel` along with all related data associated with the channel such as `PulseChannelRecipients`." [{:keys [channel_type details pulse_id recipients schedule_type schedule_day schedule_hour schedule_frame] :or {details {} diff --git a/src/metabase/models/revision.clj b/src/metabase/models/revision.clj index 7d75aaf05e544c9c49ea296324cb8ccaf67f7b29..164d0030546968c7fe232d8d8a7ef9613dfe462c 100644 --- a/src/metabase/models/revision.clj +++ b/src/metabase/models/revision.clj @@ -19,7 +19,7 @@ All of these methods except for `serialize-instance` have a default implementation in `IRevisionedDefaults`." (serialize-instance [this id instance] "Prepare an instance for serialization in a `Revision`.") - (revert-to-revision [this id user-id serialized-instance] + (revert-to-revision! [this id user-id serialized-instance] "Return an object to the state recorded by SERIALIZED-INSTANCE.") (diff-map [this object1 object2] "Return a map describing the difference between OBJECT1 and OBJECT2.") @@ -31,8 +31,8 @@ ;; NOTE that we do not provide a base implementation for `serialize-instance`, that should be done per entity. -(defn default-revert-to-revision - "Default implementation of `revert-to-revision` which simply does an update using the values from `serialized-instance`." +(defn default-revert-to-revision! + "Default implementation of `revert-to-revision!` which simply does an update using the values from `serialized-instance`." [entity id user-id serialized-instance] (db/update! entity id, serialized-instance)) @@ -52,7 +52,7 @@ (def IRevisionedDefaults "Default implementations for `IRevisioned`." - {:revert-to-revision default-revert-to-revision + {:revert-to-revision! default-revert-to-revision! :diff-map default-diff-map :diff-str default-diff-str}) @@ -79,7 +79,7 @@ :description (diff-str entity (:object prev-revision) (:object revision))) ;; add revision user details (hydrate :user) - (update :user (fn [u] (select-keys u [:id :first_name :last_name :common_name]))) + (update :user (u/rpartial select-keys [:id :first_name :last_name :common_name])) ;; Filter out irrelevant info (dissoc :model :model_id :user_id :object))) @@ -100,17 +100,17 @@ (recur (conj acc (add-revision-details entity r1 r2)) (conj more r2)))))) -(defn- delete-old-revisions +(defn- delete-old-revisions! "Delete old revisions of ENTITY with ID when there are more than `max-revisions` in the DB." [entity id] {:pre [(i/metabase-entity? entity) (integer? id)]} (when-let [old-revisions (seq (drop max-revisions (map :id (db/select [Revision :id], :model (:name entity), :model_id id, {:order-by [[:timestamp :desc]]}))))] (db/cascade-delete! Revision :id [:in old-revisions]))) -(defn push-revision +(defn push-revision! "Record a new `Revision` for ENTITY with ID. Returns OBJECT." - {:arglists '([& {:keys [object entity id user-id is-creation? message]}])} + {:arglists '([& {:keys [object entity id user-id is-creation? message]}]), :style/indent 0} [& {object :object, :keys [entity id user-id is-creation? message], :or {id (:id object), is-creation? false}}] @@ -120,8 +120,7 @@ (integer? id) (db/exists? entity :id id) (map? object)]} - (let [object (dissoc object :message) - object (serialize-instance entity id object)] + (let [object (serialize-instance entity id (dissoc object :message))] ;; make sure we still have a map after calling out serialization function (assert (map? object)) (db/insert! Revision @@ -132,11 +131,12 @@ :is_creation is-creation? :is_reversion false :message message)) - (delete-old-revisions entity id) + (delete-old-revisions! entity id) object) -(defn revert +(defn revert! "Revert ENTITY with ID to a given `Revision`." + {:style/indent 0} [& {:keys [entity id user-id revision-id]}] {:pre [(i/metabase-entity? entity) (integer? id) @@ -147,7 +147,7 @@ (let [serialized-instance (db/select-one-field :object Revision, :model (:name entity), :model_id id, :id revision-id)] (kdb/transaction ;; Do the reversion of the object - (revert-to-revision entity id user-id serialized-instance) + (revert-to-revision! entity id user-id serialized-instance) ;; Push a new revision to record this change (let [last-revision (Revision :model (:name entity), :model_id id, {:order-by [[:id :desc]]}) new-revision (db/insert! Revision diff --git a/src/metabase/models/segment.clj b/src/metabase/models/segment.clj index d79f792a36da340f982f969c41de0b2ec3b79b61..d5a575e09d52fbb2f4acf5fa92e1964246ec88fe 100644 --- a/src/metabase/models/segment.clj +++ b/src/metabase/models/segment.clj @@ -51,7 +51,7 @@ ;; ## Persistence Functions -(defn create-segment +(defn create-segment! "Create a new `Segment`. Returns the newly created `Segment` or throws an Exception." @@ -96,7 +96,7 @@ (db/select Segment, :table_id table-id, :is_active (= :active state), {:order-by [[:name :asc]]})) (hydrate :creator)))) -(defn update-segment +(defn update-segment! "Update an existing `Segment`. Returns the updated `Segment` or throws an Exception." @@ -114,7 +114,7 @@ (u/prog1 (retrieve-segment id) (events/publish-event :segment-update (assoc <> :actor_id user-id, :revision_message revision_message)))) -(defn delete-segment +(defn delete-segment! "Delete a `Segment`. This does a soft delete and simply marks the `Segment` as deleted but does not actually remove the diff --git a/src/metabase/models/setting.clj b/src/metabase/models/setting.clj index 1c287a82f7befcfd462dcdf8140e2b90cead6b0d..c5c3691c87e419c4cb592067bb935addc473ca1a 100644 --- a/src/metabase/models/setting.clj +++ b/src/metabase/models/setting.clj @@ -90,6 +90,7 @@ ;;; ### SET / DELETE +;; TODO - rename this `set!` (defn set "Set the value of a `Setting`. @@ -110,6 +111,7 @@ (swap! cached-setting->value assoc k v) v) +;; TODO - this should be renamed `delete!` (defn delete "Clear the value of a `Setting`." [k] @@ -119,6 +121,7 @@ (db/delete! Setting, :key (name k)) {:status 204, :body nil}) +;; TODO - rename this `set-all!` (defn set-all "Set the value of several `Settings` at once. @@ -131,6 +134,7 @@ (delete k))) (events/publish-event :settings-update settings)) +;; TODO - Rename to `set!*` (defn set* "Set the value of a `Setting`, deleting it if VALUE is `nil` or an empty string." [setting-key value] diff --git a/src/metabase/models/user.clj b/src/metabase/models/user.clj index e19d633f27f62831ed29875733a47386d36ad9ff..afe3549fc12c8e8c07de2c7d216e0b2d0e77e1f4 100644 --- a/src/metabase/models/user.clj +++ b/src/metabase/models/user.clj @@ -65,9 +65,9 @@ ;; ## Related Functions (declare form-password-reset-url - set-user-password-reset-token) + set-user-password-reset-token!) -(defn create-user +(defn create-user! "Convenience function for creating a new `User` and sending out the welcome email." [first-name last-name email-address & {:keys [send-welcome invitor password] :or {send-welcome false}}] @@ -82,17 +82,17 @@ password (str (java.util.UUID/randomUUID))))] (when send-welcome - (let [reset-token (set-user-password-reset-token (:id new-user)) + (let [reset-token (set-user-password-reset-token! (:id new-user)) ;; NOTE: the new user join url is just a password reset with an indicator that this is a first time user join-url (str (form-password-reset-url reset-token) "#new")] (email/send-new-user-email new-user invitor join-url))) ;; return the newly created user new-user)) -(defn set-user-password +(defn set-user-password! "Updates the stored password for a specified `User` by hashing the password with a random salt." [user-id password] - (let [salt (.toString (java.util.UUID/randomUUID)) + (let [salt (.toString (java.util.UUID/randomUUID)) password (creds/hash-bcrypt (str salt password))] ;; NOTE: any password change expires the password reset token (db/update! User user-id @@ -101,8 +101,8 @@ :reset_token nil :reset_triggered nil))) -(defn set-user-password-reset-token - "Updates a given `User` and generates a password reset token for them to use. Returns the url for password reset." +(defn set-user-password-reset-token! + "Updates a given `User` and generates a password reset token for them to use. Returns the URL for password reset." [user-id] {:pre [(integer? user-id)]} (u/prog1 (str user-id \_ (java.util.UUID/randomUUID)) @@ -117,6 +117,6 @@ (str (setting/get :-site-url) "/auth/reset_password/" reset-token)) (defn instance-created-at - "The date the instance was created. We use the :date_joined of the first user to determine this." + "The date this Metabase instance was created. We use the `:date_joined` of the first `User` to determine this." [] (db/select-one-field :date_joined User, {:order-by [[:date_joined :asc]]})) diff --git a/src/metabase/query_processor/annotate.clj b/src/metabase/query_processor/annotate.clj index b7a4f7a7ac4dcd948a9e69c5e42ecb6be0e89725..d52a70f8355fa78a864790f17ad4deebb9517be8 100644 --- a/src/metabase/query_processor/annotate.clj +++ b/src/metabase/query_processor/annotate.clj @@ -210,13 +210,13 @@ :when (= special_type :fk)] id)) (constantly nil))) - ;; Fetch the ForeignKey objects whose origin is in the returned Fields, create a map of origin-field-id->destination-field-id + ;; Fetch the foreign key fields whose origin is in the returned Fields, create a map of origin-field-id->destination-field-id ([fields fk-ids] (when (seq fk-ids) (fk-field->dest-fn fields fk-ids (db/select-id->field :fk_target_field_id Field :id [:in fk-ids] :fk_target_field_id [:not= nil])))) - ;; Fetch the destination Fields referenced by the ForeignKeys + ;; Fetch the destination Fields referenced by the foreign keys ([fields fk-ids id->dest-id] (when (seq id->dest-id) (fk-field->dest-fn fields fk-ids id->dest-id (u/key-by :id (db/select [Field :id :name :display_name :table_id :description :base_type :special_type :visibility_type] @@ -227,7 +227,7 @@ (some-> id id->dest-id dest-id->field)))) (defn- add-extra-info-to-fk-fields - "Add `:extra_info` about `ForeignKeys` to `Fields` whose `special_type` is `:fk`." + "Add `:extra_info` about foreign keys to `Fields` whose `special_type` is `:fk`." [fields] (let [field->dest (fk-field->dest-fn fields)] (for [field fields] diff --git a/src/metabase/sync_database/analyze.clj b/src/metabase/sync_database/analyze.clj index a94d30c4f017782acbab7f14245ee03c76ebbe3b..1936071e3eb622a76a79ec16e183770dc1255af5 100644 --- a/src/metabase/sync_database/analyze.clj +++ b/src/metabase/sync_database/analyze.clj @@ -194,8 +194,8 @@ :special_type special-type)) ;; handle field values, setting them if applicable otherwise clearing them (if (and id values (< 0 (count (filter identity values)))) - (field-values/save-field-values id values) - (field-values/clear-field-values id)))) + (field-values/save-field-values! id values) + (field-values/clear-field-values! id)))) ;; update :last_analyzed for all fields in the table (db/update-where! field/Field {:table_id table-id diff --git a/test/metabase/api/revision_test.clj b/test/metabase/api/revision_test.clj index 21d9cd7f365db38273d910803bd5059c32b9a9f0..778efc3cccd7cbcf0a422b8a910cbfd44ad02f6d 100644 --- a/test/metabase/api/revision_test.clj +++ b/test/metabase/api/revision_test.clj @@ -5,7 +5,7 @@ (metabase.models [card :refer [Card serialize-instance]] [dashboard :refer [Dashboard]] [dashboard-card :refer [DashboardCard]] - [revision :refer [Revision push-revision revert revisions]]) + [revision :refer [Revision push-revision! revert! revisions]]) [metabase.test.data :refer :all] [metabase.test.data.users :refer :all] [metabase.test.util :refer [expect-eval-actual-first random-name expect-with-temp with-temp]])) @@ -18,15 +18,15 @@ (dissoc revision :timestamp :id))) (defn- create-card-revision [card is-creation?] - (push-revision + (push-revision! :object card :entity Card :id (:id card) :user-id (user->id :rasta) :is-creation? is-creation?)) -(defn- create-dashboard-revision [dash is-creation?] - (push-revision +(defn- create-dashboard-revision! [dash is-creation?] + (push-revision! :object (Dashboard (:id dash)) :entity Dashboard :id (:id dash) @@ -131,11 +131,11 @@ :diff nil :description nil}] (do - (create-dashboard-revision dash true) + (create-dashboard-revision! dash true) (let [dashcard (db/insert! DashboardCard :dashboard_id id :card_id (:id card))] - (create-dashboard-revision dash false) + (create-dashboard-revision! dash false) (db/delete! DashboardCard, :id (:id dashcard))) - (create-dashboard-revision dash false) + (create-dashboard-revision! dash false) (let [[_ {previous-revision-id :id}] (revisions Dashboard id)] ;; Revert to the previous revision ((user->client :rasta) :post 200 "revision/revert", {:entity :dashboard, :id id, :revision_id previous-revision-id})) diff --git a/test/metabase/api/session_test.clj b/test/metabase/api/session_test.clj index 799c3b0b051eaa875c32ae6659be0570de30e30b..20ef46ab43a021ed04347372bb81cb95fa21d6fb 100644 --- a/test/metabase/api/session_test.clj +++ b/test/metabase/api/session_test.clj @@ -90,7 +90,7 @@ (let [user-last-name (random-name) password {:old "password" :new "whateverUP12!!"} - {:keys [email id]} (create-user :password (:old password), :last_name user-last-name, :reset_triggered (System/currentTimeMillis)) + {:keys [email id]} (create-user! :password (:old password), :last_name user-last-name, :reset_triggered (System/currentTimeMillis)) token (u/prog1 (str id "_" (java.util.UUID/randomUUID)) (db/update! User id, :reset_token <>)) creds {:old {:password (:old password) @@ -118,7 +118,7 @@ session-id (db/select-one-id Session, :user_id id)] {:success true :session_id session-id}) - (let [{:keys [email id]} (create-user :password "password", :last_name user-last-name, :reset_triggered (System/currentTimeMillis)) + (let [{:keys [email id]} (create-user! :password "password", :last_name user-last-name, :reset_triggered (System/currentTimeMillis)) token (u/prog1 (str id "_" (java.util.UUID/randomUUID)) (db/update! User id, :reset_token <>))] ;; run the password reset diff --git a/test/metabase/api/user_test.clj b/test/metabase/api/user_test.clj index 96cd746929f48d4382256f02343ef18e99798038..f5be2993d8bfd59cf182e8f6559bac013247767b 100644 --- a/test/metabase/api/user_test.clj +++ b/test/metabase/api/user_test.clj @@ -182,7 +182,7 @@ ;; ## PUT /api/user/:id ;; Test that we can edit a User -(expect-let [{old-first :first_name, last-name :last_name, old-email :email, id :id, :as user} (create-user) +(expect-let [{old-first :first_name, last-name :last_name, old-email :email, id :id, :as user} (create-user!) new-first (random-name) new-email (.toLowerCase ^String (str new-first "@metabase.com")) fetch-user (fn [] (dissoc (into {} (db/select-one [User :first_name :last_name :is_superuser :email], :id id)) diff --git a/test/metabase/models/dashboard_card_test.clj b/test/metabase/models/dashboard_card_test.clj index cea3af4bbf1d2ba0adf93bf457516bcb7b31172d..59bc32cd26a2e1da0b7ab5b95eee73a621956d6f 100644 --- a/test/metabase/models/dashboard_card_test.clj +++ b/test/metabase/models/dashboard_card_test.clj @@ -65,7 +65,7 @@ (remove-ids-and-timestamps (retrieve-dashboard-card dashcard-id)))) -;; update-dashboard-card-series +;; update-dashboard-card-series! (expect [#{} #{"card1"} @@ -81,7 +81,7 @@ Card [{card-id-2 :id} {:name "card2"}] Card [{card-id3 :id} {:name "card3"}]] (let [upd-series (fn [series] - (update-dashboard-card-series {:id dashcard-id} series) + (update-dashboard-card-series! {:id dashcard-id} series) (set (for [card-id (db/select-field :card_id DashboardCardSeries, :dashboardcard_id dashcard-id)] (db/select-one-field :name Card, :id card-id))))] [(upd-series []) @@ -91,7 +91,7 @@ (upd-series [card-id-1 card-id3])]))) -;; create-dashboard-card +;; create-dashboard-card! ;; simple example with a single card (expect [{:sizeX 4 @@ -114,19 +114,19 @@ :visualization_settings {}}]}] (tu/with-temp* [Dashboard [{dashboard-id :id}] Card [{card-id :id} {:name "Test Card"}]] - (let [dashboard-card (create-dashboard-card {:creator_id (user->id :rasta) - :dashboard_id dashboard-id - :card_id card-id - :sizeX 4 - :sizeY 3 - :row 1 - :col 1 - :series [card-id]})] + (let [dashboard-card (create-dashboard-card! {:creator_id (user->id :rasta) + :dashboard_id dashboard-id + :card_id card-id + :sizeX 4 + :sizeY 3 + :row 1 + :col 1 + :series [card-id]})] ;; first result is return value from function, second is to validate db captured everything [(remove-ids-and-timestamps dashboard-card) (remove-ids-and-timestamps (retrieve-dashboard-card (:id dashboard-card)))]))) -;; update-dashboard-card +;; update-dashboard-card! ;; basic update. we are testing multiple things here ;; 1. ability to update all the normal attributes for size/position ;; 2. ability to update series and ensure proper ordering @@ -175,13 +175,13 @@ ;; second is the return value from the update call ;; third is to validate db captured everything [(remove-ids-and-timestamps (retrieve-dashboard-card dashcard-id)) - (remove-ids-and-timestamps (update-dashboard-card {:id dashcard-id - :actor_id (user->id :rasta) - :dashboard_id nil - :card_id nil - :sizeX 4 - :sizeY 3 - :row 1 - :col 1 - :series [card-id-2 card-id-1]})) + (remove-ids-and-timestamps (update-dashboard-card! {:id dashcard-id + :actor_id (user->id :rasta) + :dashboard_id nil + :card_id nil + :sizeX 4 + :sizeY 3 + :row 1 + :col 1 + :series [card-id-2 card-id-1]})) (remove-ids-and-timestamps (retrieve-dashboard-card dashcard-id))])) diff --git a/test/metabase/models/dashboard_test.clj b/test/metabase/models/dashboard_test.clj index d225abcec3bf54ed0fd394c7430ed5c9d8a5b47b..83b98c0ae7d63a1840192fe6858eff3440ed8742 100644 --- a/test/metabase/models/dashboard_test.clj +++ b/test/metabase/models/dashboard_test.clj @@ -108,7 +108,10 @@ :series [3 4 5]}]})) -;; revert-dashboard +;;; revert-dashboard! + +(tu/resolve-private-fns metabase.models.dashboard revert-dashboard!) + (expect [{:name "Test Dashboard" :description nil @@ -148,14 +151,14 @@ :series (= [series-id-1 series-id-2] series))]) serialized-dashboard (serialize-dashboard dashboard)] ;; delete the dashcard and modify the dash attributes - (dashboard-card/delete-dashboard-card dashboard-card (user->id :rasta)) + (dashboard-card/delete-dashboard-card! dashboard-card (user->id :rasta)) (db/update! Dashboard dashboard-id :name "Revert Test" :description "something") ;; capture our updated dashboard state (let [serialized-dashboard2 (serialize-dashboard (Dashboard dashboard-id))] ;; now do the reversion - (revert-dashboard dashboard-id (user->id :crowberto) serialized-dashboard) + (revert-dashboard! dashboard-id (user->id :crowberto) serialized-dashboard) ;; final output is original-state, updated-state, reverted-state [(update serialized-dashboard :cards check-ids) serialized-dashboard2 diff --git a/test/metabase/models/dependency_test.clj b/test/metabase/models/dependency_test.clj index 107a09da42332c08a65bd3e424d3500846212d17..4faa38ae787ebe03b76a41a8c9c611442e0be3e2 100644 --- a/test/metabase/models/dependency_test.clj +++ b/test/metabase/models/dependency_test.clj @@ -58,13 +58,13 @@ (format-dependencies (retrieve-dependencies Mock 4)))) -;; update-dependencies +;; update-dependencies! ;; we skip over values which aren't integers (expect #{} (do - (update-dependencies Mock 2 {:test ["a" "b" "c"]}) + (update-dependencies! Mock 2 {:test ["a" "b" "c"]}) (set (db/select Dependency, :model "Mock", :model_id 2)))) ;; valid working dependencies list @@ -82,7 +82,7 @@ :dependent_on_model "test" :dependent_on_id 3}} (do - (update-dependencies Mock 7 {:test [1 2 3]}) + (update-dependencies! Mock 7 {:test [1 2 3]}) (format-dependencies (db/select Dependency, :model "Mock", :model_id 7)))) ;; delete dependencies that are no longer in the list @@ -102,5 +102,5 @@ :dependent_on_model "test" :dependent_on_id 5 :created_at (u/new-sql-timestamp)) - (update-dependencies Mock 1 {:test [1 2]}) + (update-dependencies! Mock 1 {:test [1 2]}) (format-dependencies (db/select Dependency, :model "Mock", :model_id 1)))) diff --git a/test/metabase/models/field_values_test.clj b/test/metabase/models/field_values_test.clj index 4ca72000be210f3c176dd366dcd139823c2b03b8..ebcf37885cc567010d1fd90ca9359dfbd3cedc73 100644 --- a/test/metabase/models/field_values_test.clj +++ b/test/metabase/models/field_values_test.clj @@ -52,5 +52,5 @@ Field [{field-id :id} {:table_id table-id}] FieldValues [_ {:field_id field-id, :values "[1,2,3]"}]] [(db/select-one-field :values FieldValues, :field_id field-id) - (clear-field-values field-id) + (clear-field-values! field-id) (db/select-one-field :values FieldValues, :field_id field-id)])) diff --git a/test/metabase/models/metric_test.clj b/test/metabase/models/metric_test.clj index 23422e1f81f26ec68180b7f50beab5c8709508d5..bd8e49addd34abee9f77373fca6840cfd9643a9a 100644 --- a/test/metabase/models/metric_test.clj +++ b/test/metabase/models/metric_test.clj @@ -2,7 +2,7 @@ (:require [expectations :refer :all] (metabase.models [database :refer [Database]] [hydrate :refer :all] - [metric :refer :all] + [metric :refer :all, :as metric] [table :refer [Table]]) [metabase.test.data :refer :all] [metabase.test.data.users :refer :all] @@ -40,7 +40,7 @@ (create-metric-then-select! id "I only want *these* things" nil (user->id :rasta) {:clause ["a" "b"]}))) -;; exists-metric? +;; exists? (expect [true false] @@ -49,8 +49,8 @@ Metric [{metric-id :id} {:table_id table-id :definition {:database 45 :query {:filter ["yay"]}}}]] - [(exists-metric? metric-id) - (exists-metric? 34)])) + [(metric/exists? metric-id) + (metric/exists? 34)])) ;; retrieve-metric diff --git a/test/metabase/models/pulse_channel_test.clj b/test/metabase/models/pulse_channel_test.clj index 6c39ca59db0e7e140241c0dd594545d5a5366351..5ec8fd4ee044b58cda42c7cd5edf9a07293f93a8 100644 --- a/test/metabase/models/pulse_channel_test.clj +++ b/test/metabase/models/pulse_channel_test.clj @@ -103,7 +103,7 @@ ;; create a channel then select its details (defn- create-channel-then-select! [channel] - (when-let [new-channel-id (create-pulse-channel channel)] + (when-let [new-channel-id (create-pulse-channel! channel)] (-> (PulseChannel new-channel-id) (hydrate :recipients) (update :recipients #(sort-by :email %)) @@ -112,13 +112,13 @@ (defn- update-channel-then-select! [{:keys [id] :as channel}] - (update-pulse-channel channel) + (update-pulse-channel! channel) (-> (PulseChannel id) (hydrate :recipients) (dissoc :id :pulse_id :created_at :updated_at) (m/dissoc-in [:details :emails]))) -;; create-pulse-channel +;; create-pulse-channel! (expect {:enabled true :channel_type :email @@ -155,7 +155,7 @@ :recipients [{:email "foo@bar.com"} {:id (user->id :rasta)} {:id (user->id :crowberto)}]}))) -;; update-pulse-channel +;; update-pulse-channel! ;; simple starting case where we modify the schedule hour and add a recipient (expect {:enabled true diff --git a/test/metabase/models/pulse_test.clj b/test/metabase/models/pulse_test.clj index 5b192e4075ed8424a6185de1b98f75c82e564830..88cfe49bbc9ace4cd07248f66202fe12b207be1b 100644 --- a/test/metabase/models/pulse_test.clj +++ b/test/metabase/models/pulse_test.clj @@ -30,7 +30,7 @@ (defn- update-pulse-then-select! [pulse] - (let [{:keys [cards channels] :as pulse} (update-pulse pulse)] + (let [{:keys [cards channels] :as pulse} (update-pulse! pulse)] (-> pulse (dissoc :id :creator :pulse_id :created_at :updated_at) (assoc :cards (mapv #(dissoc % :id) cards)) @@ -74,7 +74,7 @@ (m/dissoc-in [:details :emails])))))))) -;; update-pulse-cards +;; update-pulse-cards! (expect [#{} #{"card1"} @@ -86,7 +86,7 @@ Card [{card-id-2 :id} {:name "card2"}] Card [{card-id-3 :id} {:name "card3"}]] (let [upd-cards! (fn [cards] - (update-pulse-cards {:id pulse-id} cards) + (update-pulse-cards! {:id pulse-id} cards) (set (for [card-id (db/select-field :card_id PulseCard, :pulse_id pulse-id)] (db/select-one-field :name Card, :id card-id))))] [(upd-cards! []) @@ -95,7 +95,7 @@ (upd-cards! [card-id-2 card-id-1]) (upd-cards! [card-id-1 card-id-3])]))) -;; update-pulse-channels +;; update-pulse-channels! (expect {:enabled true :channel_type :email @@ -106,11 +106,11 @@ :recipients [{:email "foo@bar.com"} (dissoc (user-details :rasta) :is_superuser :is_qbnewb)]} (tu/with-temp Pulse [{:keys [id]}] - (update-pulse-channels {:id id} [{:enabled true - :channel_type :email - :schedule_type :daily - :schedule_hour 4 - :recipients [{:email "foo@bar.com"} {:id (user->id :rasta)}]}]) + (update-pulse-channels! {:id id} [{:enabled true + :channel_type :email + :schedule_type :daily + :schedule_hour 4 + :recipients [{:email "foo@bar.com"} {:id (user->id :rasta)}]}]) (-> (PulseChannel :pulse_id id) (hydrate :recipients) (dissoc :id :pulse_id :created_at :updated_at) @@ -137,7 +137,7 @@ :schedule_hour 18 :recipients [{:email "foo@bar.com"}]}]))) -;; update-pulse +;; update-pulse! ;; basic update. we are testing several things here ;; 1. ability to update the Pulse name ;; 2. creator_id cannot be changed diff --git a/test/metabase/models/revision_test.clj b/test/metabase/models/revision_test.clj index 24a8d9727bcf328faa78de8551b591ca41e47c69..c0eb072caf10bbeb7588b6c9b15859d2222fda31 100644 --- a/test/metabase/models/revision_test.clj +++ b/test/metabase/models/revision_test.clj @@ -18,7 +18,7 @@ IRevisioned (serialize-instance [_ _ obj] (assoc obj :serialized true)) - (revert-to-revision [_ _ _ serialized-instance] + (revert-to-revision! [_ _ _ serialized-instance] (reset! reverted-to (dissoc serialized-instance :serialized))) (diff-map [_ o1 o2] {:o1 o1, :o2 o2}) @@ -27,7 +27,7 @@ (str "BEFORE=" o1 ",AFTER=" o2)))) (defn- push-fake-revision [card-id & {:keys [message] :as object}] - (push-revision + (push-revision! :entity FakedCard :id card-id :user-id (user->id :rasta) @@ -68,7 +68,7 @@ {:name "Spots by State", :private true, :priority "Regular"})) -;;; # REVISIONS + PUSH-REVISION +;;; # REVISIONS + PUSH-REVISION! ;; Test that a newly created Card doesn't have any revisions (expect @@ -177,23 +177,23 @@ ;;; # REVERT -;; Check that revert defers to revert-to-revision +;; Check that revert defers to revert-to-revision! (expect {:name "Tips Created by Day"} (with-temp Card [{card-id :id}] (push-fake-revision card-id, :name "Tips Created by Day") (let [[{revision-id :id}] (revisions FakedCard card-id)] - (revert :entity FakedCard, :id card-id, :user-id (user->id :rasta), :revision-id revision-id) + (revert! :entity FakedCard, :id card-id, :user-id (user->id :rasta), :revision-id revision-id) @reverted-to))) -;; Check default impl of revert-to-revision just does mapply upd +;; Check default impl of revert-to-revision! just does mapply upd (expect ["Spots Created By Day" "Tips Created by Day"] (with-temp Card [{card-id :id} {:name "Spots Created By Day"}] - (push-revision :entity Card, :id card-id, :user-id (user->id :rasta), :object {:name "Tips Created by Day"}) - (push-revision :entity Card, :id card-id, :user-id (user->id :rasta), :object {:name "Spots Created by Day"}) + (push-revision! :entity Card, :id card-id, :user-id (user->id :rasta), :object {:name "Tips Created by Day"}) + (push-revision! :entity Card, :id card-id, :user-id (user->id :rasta), :object {:name "Spots Created by Day"}) [(:name (Card card-id)) (let [[_ {old-revision-id :id}] (revisions Card card-id)] - (revert :entity Card, :id card-id, :user-id (user->id :rasta), :revision-id old-revision-id) + (revert! :entity Card, :id card-id, :user-id (user->id :rasta), :revision-id old-revision-id) (:name (Card card-id)))])) ;; Check that reverting to a previous revision adds an appropriate revision @@ -222,6 +222,6 @@ (push-fake-revision card-id, :name "Tips Created by Day") (push-fake-revision card-id, :name "Spots Created by Day") (let [[_ {old-revision-id :id}] (revisions FakedCard card-id)] - (revert :entity FakedCard, :id card-id, :user-id (user->id :rasta), :revision-id old-revision-id) + (revert! :entity FakedCard, :id card-id, :user-id (user->id :rasta), :revision-id old-revision-id) (->> (revisions FakedCard card-id) (map (u/rpartial dissoc :timestamp :id :model_id)))))) diff --git a/test/metabase/models/segment_test.clj b/test/metabase/models/segment_test.clj index d2d95dd4b04054de7bb63b9144f4a78d521c9a77..fe394d88386cb801a5a773e7d740007a5063916e 100644 --- a/test/metabase/models/segment_test.clj +++ b/test/metabase/models/segment_test.clj @@ -21,14 +21,14 @@ (defn- create-segment-then-select! [table name description creator definition] - (segment-details (create-segment table name description creator definition))) + (segment-details (create-segment! table name description creator definition))) (defn- update-segment-then-select! [segment] - (segment-details (update-segment segment (user->id :crowberto)))) + (segment-details (update-segment! segment (user->id :crowberto)))) -;; create-segment +;; create-segment! (expect {:creator_id (user->id :rasta) :creator (user-details :rasta) @@ -92,7 +92,7 @@ (update :creator (u/rpartial dissoc :date_joined :last_login))))))) -;; update-segment +;; update-segment! ;; basic update. we are testing several things here ;; 1. ability to update the Segment name ;; 2. creator_id cannot be changed @@ -119,7 +119,7 @@ :query {:filter ["not" "the toucans you're looking for"]}} :revision_message "Just horsing around"}))) -;; delete-segment +;; delete-segment! (expect {:creator_id (user->id :rasta) :creator (user-details :rasta) @@ -130,7 +130,7 @@ (tu/with-temp* [Database [{database-id :id}] Table [{:keys [id]} {:db_id database-id}] Segment [{:keys [id]} {:table_id id}]] - (delete-segment id (user->id :crowberto) "revision message") + (delete-segment! id (user->id :crowberto) "revision message") (segment-details (retrieve-segment id)))) diff --git a/test/metabase/test/data/users.clj b/test/metabase/test/data/users.clj index 8d1c69ea78c741d1371442b8d09fceb082861762..65368d88a57105f97cc1ef7f712f1d02a4a7c263 100644 --- a/test/metabase/test/data/users.clj +++ b/test/metabase/test/data/users.clj @@ -7,7 +7,7 @@ [metabase.util :as u] [metabase.test.util :refer [random-name]])) -(declare fetch-or-create-user) +(declare fetch-or-create-user!) ;; ## User definitions @@ -45,7 +45,7 @@ ;; ## Public functions for working with Users -(defn create-user +(defn create-user! "Create a new `User` with random names + password." [& {:as kwargs}] (let [first-name (random-name) @@ -61,7 +61,7 @@ (fetch-user :rasta) -> {:id 100 :first_name \"Rasta\" ...}" [username] {:pre [(contains? usernames username)]} - (m/mapply fetch-or-create-user (user->info username))) + (m/mapply fetch-or-create-user! (user->info username))) (def user->id "Memoized fn that returns the ID of User associated with USERNAME. @@ -118,7 +118,7 @@ ;; ## Implementation -(defn- fetch-or-create-user +(defn- fetch-or-create-user! "Create User if they don't already exist and return User." [& {:keys [email first last password superuser active] :or {superuser false