From ed3e40cfd8d6aada36c8b0cd16eb11a02c8fdd44 Mon Sep 17 00:00:00 2001 From: Lucas Sousa Date: Tue, 10 Oct 2023 18:12:46 +0200 Subject: [PATCH] Refactor invitation prst layer to use with plastic strategy [Re #1611] * Also refactor it to follow new conventions on the persistence layer side, handling errors at that level. --- backend/src/gpml/db/invitation.clj | 112 +++++++++++++++++- backend/src/gpml/db/invitation.sql | 14 ++- backend/src/gpml/handler/invitation.clj | 34 +++--- .../src/gpml/handler/stakeholder/expert.clj | 23 +++- 4 files changed, 151 insertions(+), 32 deletions(-) diff --git a/backend/src/gpml/db/invitation.clj b/backend/src/gpml/db/invitation.clj index e92e0b9fa..c3b4b2f7f 100644 --- a/backend/src/gpml/db/invitation.clj +++ b/backend/src/gpml/db/invitation.clj @@ -1,9 +1,113 @@ (ns gpml.db.invitation {:ns-tracker/resource-deps ["invitation.sql"]} - (:require [hugsql.core :as hugsql])) + (:require [gpml.db.jdbc-util :as jdbc-util] + [gpml.util :as util] + [gpml.util.postgresql :as util.pgsql] + [gpml.util.sql :as util.sql] + [hugsql.core :as hugsql])) -(declare create-invitations - get-invitations - accept-invitation) +(declare create-invitations* + get-invitations* + accept-invitation* + delete-invitation*) (hugsql/def-db-fns "gpml/db/invitation.sql" {:quoting :ansi}) + +(defn- p-invitation->invitation + [p-invitation] + (util/update-if-not-nil p-invitation :type keyword)) + +(defn- invitation->p-invitation + [invitation] + (util/update-if-not-nil invitation :type util.pgsql/->PGEnum "invitation_type")) + +(defn get-invitations + [conn opts] + (try + {:success? true + :invitations (->> (get-invitations* conn opts) + (jdbc-util/db-result-snake-kw->db-result-kebab-kw) + (map p-invitation->invitation))} + (catch Throwable t + {:success? false + :reason :exception + :error-details {:msg (ex-message t)}}))) + +(defn get-invitation + [conn opts] + (try + (let [invitations (get-invitations conn opts)] + (if (and (seq invitations) + (= (count invitations) 1)) + {:success? true + :invitation (first invitations)} + {:success? false + :reason :not-found})) + (catch Throwable t + {:success? false + :reason :exception + :error-details {:msg (ex-message t)}}))) + +(defn create-invitations + [conn invitations] + (try + (let [p-invitations (jdbc-util/db-params-kebab-kw->db-params-snake-kw + (map invitation->p-invitation invitations)) + cols (util.sql/get-insert-columns-from-entity-col p-invitations) + values (util.sql/entity-col->persistence-entity-col p-invitations) + created-invitations (create-invitations* conn {:cols cols + :values values})] + (if (= (count created-invitations) (count invitations)) + {:success? true + :invitations (map p-invitation->invitation created-invitations)} + {:success? false + :reason :unexpected-number-of-affected-rows + :error-details {:expected-affected-rows (count invitations) + :actual-affected-rows (count created-invitations)}})) + (catch Throwable t + {:success? false + :reason :exception + :error-details {:msg (ex-message t)}}))) + +(defn create-invitation + [conn invitation] + (try + (let [result (create-invitations conn [invitation])] + (if (:success? result) + {:success? true + :invitation (first (:invitations result))} + result)) + (catch Throwable t + {:success? false + :reason :exception + :error-details {:msg (ex-message t)}}))) + +(defn accept-invitation + [conn invitation-id] + (try + (let [affected (accept-invitation* conn {:id invitation-id})] + (if (= affected 1) + {:success? true} + {:success? false + :reason :unexpected-number-of-affected-rows + :error-details {:expected-affected-rows 1 + :actual-affected-rows affected}})) + (catch Throwable t + {:success? false + :reason :exception + :error-details {:msg (ex-message t)}}))) + +(defn delete-invitation + [conn invitation-id] + (try + (let [affected (delete-invitation* conn {:id invitation-id})] + (if (= affected 1) + {:success? true} + {:success? false + :reason :unexpected-number-of-affected-rows + :error-details {:expected-affected-rows 1 + :actual-affected-rows affected}})) + (catch Throwable t + {:success? false + :reason :exception + :error-details {:msg (ex-message t)}}))) diff --git a/backend/src/gpml/db/invitation.sql b/backend/src/gpml/db/invitation.sql index 948224c4f..c5f8ac145 100644 --- a/backend/src/gpml/db/invitation.sql +++ b/backend/src/gpml/db/invitation.sql @@ -1,8 +1,8 @@ --- :name create-invitations :returning-execute :many -INSERT INTO invitation(id, stakeholder_id, email) +-- :name create-invitations* :returning-execute :many +INSERT INTO invitation(:i*:cols) VALUES :t*:values RETURNING *; --- :name get-invitations :query :many +-- :name get-invitations* :query :many SELECT * FROM invitation WHERE 1=1 --~ (when (seq (get-in params [:filters :emails])) " AND email IN (:v*:filters.emails)") @@ -11,7 +11,11 @@ WHERE 1=1 --~ (when (true? (get-in params [:filters :pending?])) " AND accepted_at IS NULL") --~ (when (false? (get-in params [:filters :pending?])) " AND accepted_at IS NOT NULL") --- :name accept-invitation :execute :affected +-- :name accept-invitation* :execute :affected UPDATE invitation -SET accepted_at = :accepted-at +SET accepted_at = now() WHERE id = :id + +-- :name delete-invitation* :execute :affected +DELETE FROM invitation +WHERE id = :id; diff --git a/backend/src/gpml/handler/invitation.clj b/backend/src/gpml/handler/invitation.clj index 8e1207f7c..3c540001d 100644 --- a/backend/src/gpml/handler/invitation.clj +++ b/backend/src/gpml/handler/invitation.clj @@ -66,7 +66,7 @@ (if-not (h.r.permission/super-admin? config (:id user)) (r/forbidden {:message "Unauthorized"}) (let [opts (api-opts->opts query) - invitations (db.invitation/get-invitations (:spec db) opts) + invitations (db.invitation/get-invitations* (:spec db) opts) stakeholders (->> (db.stakeholder/get-stakeholders (:spec db) {:filters {:ids (map :stakeholder_id invitations)}}) (group-by :id))] @@ -94,24 +94,24 @@ {{:keys [query path]} :parameters user :user}] (try (let [opts (api-opts->opts (merge query path)) - invitation (db.invitation/get-invitations (:spec db) - {:filters {:ids [(:id opts)]}})] - (if-not (= (:stakeholder_id invitation) (:id user)) - (r/forbidden {:message "Unauthorized"}) - (let [accepted-at (-> (time/instant) (time-pre-j8/sql-timestamp "UTC")) - affected-rows (db.invitation/accept-invitation (:spec db) (merge opts - {:accepted-at accepted-at}))] - (if (= affected-rows 1) - (r/ok {:success? true}) - (r/server-error {:success? false - :reason :could-not-update-invitation}))))) + {:keys [success? invitation reason] :as get-invitation-result} + (db.invitation/get-invitation (:spec db) + {:filters {:ids [(:id opts)]}})] + (if-not success? + (if (= reason :not-found) + (r/not-found {}) + (r/server-error (dissoc get-invitation-result :success?))) + (if-not (= (:stakeholder-id invitation) (:id user)) + (r/forbidden {:message "Unauthorized"}) + (let [result (db.invitation/accept-invitation (:spec db) (:id invitation))] + (if (:success? result) + (r/ok {}) + (r/server-error (dissoc result :success?))))))) (catch Exception e - (log logger :error ::accept-invitation {:exception-message (.getMessage e)}) + (log logger :error ::failed-to-accept-invitation {:exception-message (.getMessage e)}) (if (instance? SQLException e) - (r/server-error {:success? false - :reason (pg-util/get-sql-state e)}) - (r/server-error {:success? false - :reason :could-not-update-invitation + (r/server-error {:reason (pg-util/get-sql-state e)}) + (r/server-error {:reason :could-not-update-invitation :error-details {:message (.getMessage e)}}))))) (defmethod ig/init-key :gpml.handler.invitation/get [_ config] diff --git a/backend/src/gpml/handler/stakeholder/expert.clj b/backend/src/gpml/handler/stakeholder/expert.clj index 4105f54cb..ebcdacfe4 100644 --- a/backend/src/gpml/handler/stakeholder/expert.clj +++ b/backend/src/gpml/handler/stakeholder/expert.clj @@ -1,5 +1,7 @@ (ns gpml.handler.stakeholder.expert - (:require [clojure.java.jdbc :as jdbc] + (:require [camel-snake-kebab.core :refer [->snake_case]] + [camel-snake-kebab.extras :as cske] + [clojure.java.jdbc :as jdbc] [clojure.string :as str] [duct.logger :refer [log]] [gpml.db.country-group :as db.country-group] @@ -12,6 +14,7 @@ [gpml.util :as util] [gpml.util.email :as email] [gpml.util.postgresql :as pg-util] + [gpml.util.sql :as util.sql] [integrant.core :as ig] [jsonista.core :as json] [malli.util :as mu] @@ -192,12 +195,20 @@ expert-values (util/apply-select-values experts expert-cols) expert-stakeholders (db.stakeholder/create-stakeholders conn {:cols (map name expert-cols) :values expert-values}) - invitation-values (map (fn [{:keys [id email]}] - (vector (util/uuid) id email)) - expert-stakeholders) + invitations-to-create (map (fn [{:keys [id email]}] + {:id (util/uuid) + :stakeholder-id id + :email email + :type :expert}) + expert-stakeholders) + invitation-cols (util.sql/get-insert-columns-from-entity-col invitations-to-create) + invitations-values (util.sql/entity-col->persistence-entity-col invitations-to-create) experts-by-email (group-by :email experts) - invitations (->> (db.invitation/create-invitations conn {:values invitation-values}) - (map #(merge % (get-in experts-by-email [(:email %) 0]))))] + invitations (->> (db.invitation/create-invitations conn {:cols invitation-cols + :values invitations-values}) + :invitations + (map #(merge % (get-in experts-by-email [(:email %) 0]))) + (cske/transform-keys ->snake_case))] (doseq [{:keys [email expertise]} body :let [stakeholder-id (get-in (group-by :email expert-stakeholders) [email 0 :id])]] (handler.stakeholder.tag/save-stakeholder-tags