From 66fe0048a5e22d8be602781728f2b808bbce3085 Mon Sep 17 00:00:00 2001 From: "alonso.torres" Date: Tue, 19 Jan 2021 15:04:28 +0100 Subject: [PATCH] :sparkles: Adds system to load initial project data --- backend/src/app/config.clj | 12 +- backend/src/app/db.clj | 12 ++ backend/src/app/db/profile_initial_data.clj | 138 ++++++++++++++++++++ backend/src/app/rpc/mutations/profile.clj | 6 +- backend/src/app/storage.clj | 17 ++- 5 files changed, 174 insertions(+), 11 deletions(-) create mode 100644 backend/src/app/db/profile_initial_data.clj diff --git a/backend/src/app/config.clj b/backend/src/app/config.clj index 8126de29a..08c569622 100644 --- a/backend/src/app/config.clj +++ b/backend/src/app/config.clj @@ -66,7 +66,11 @@ :ldap-auth-username-attribute "uid" :ldap-auth-email-attribute "mail" :ldap-auth-fullname-attribute "displayName" - :ldap-auth-avatar-attribute "jpegPhoto"}) + :ldap-auth-avatar-attribute "jpegPhoto" + + ;;:initial-data-project-id "5761a890-3b81-11eb-9e7d-556a2f641513" + ;;:initial-data-project-name "Penpot Onboarding" + }) (s/def ::http-server-port ::us/integer) (s/def ::http-server-debug ::us/boolean) @@ -135,6 +139,8 @@ (s/def ::telemetry-server-enabled ::us/boolean) (s/def ::telemetry-server-port ::us/integer) +(s/def ::initial-data-project-id ::us/uuid) +(s/def ::initial-data-project-name ::us/string) (s/def ::config (s/keys :opt-un [::allow-demo-users @@ -190,7 +196,9 @@ ::telemetry-enabled ::telemetry-server-enabled ::telemetry-server-port - ::telemetry-uri])) + ::telemetry-uri + ::initial-data-project-id + ::initial-data-project-name])) (defn- env->config [env] diff --git a/backend/src/app/db.clj b/backend/src/app/db.clj index a9402efd8..a5d2d0ca9 100644 --- a/backend/src/app/db.clj +++ b/backend/src/app/db.clj @@ -186,6 +186,18 @@ (sql/insert table params opts) (assoc opts :return-keys true)))) +(defn- select-values [map ks] + (reduce #(conj %1 (map %2)) [] ks)) + +(defn insert-multi! + [ds table param-list] + (doseq [params param-list] + (insert! ds table params)) + ;; FIXME: Won't work + #_(let [keys (->> param-list first keys (into [])) + params (->> param-list (mapv #(->> keys (select-values %) (into []))) )] + (jdbc-sql/insert-multi! ds table keys params default-options))) + (defn update! ([ds table params where] (update! ds table params where nil)) ([ds table params where opts] diff --git a/backend/src/app/db/profile_initial_data.clj b/backend/src/app/db/profile_initial_data.clj new file mode 100644 index 000000000..9982c6eeb --- /dev/null +++ b/backend/src/app/db/profile_initial_data.clj @@ -0,0 +1,138 @@ +;; 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/. +;; +;; This Source Code Form is "Incompatible With Secondary Licenses", as +;; defined by the Mozilla Public License, v. 2.0. +;; +;; Copyright (c) 2020-2021 UXBOX Labs SL + +(ns app.db.profile-initial-data + (:require + [app.common.uuid :as uuid] + [app.config :as cfg] + [app.db :as db] + [app.rpc.mutations.projects :as projects] + [app.storage :as storage] + [clojure.tools.logging :as log] + [cuerdas.core :as str])) + +(def sql:file + "select * from file where project_id = ?") + +(def sql:file-library-rel + "with file_ids as (select id from file where project_id = ?) + select * + from file_library_rel + where file_id in (select id from file_ids)") + +(def sql:file-media-object + "with file_ids as (select id from file where project_id = ?) + select * + from file_media_object + where file_id in (select id from file_ids)") + +(def sql:file-media-thumbnail + "with file_ids as (select id from file where project_id = ?), + media_ids as (select id from file_media_object where file_id in (select id from file_ids)) + select * + from file_media_thumbnail + where media_object_id in (select id from media_ids)") + +(def sql:storage-object + "with file_ids as (select id from file where project_id = ?), + media_ids as (select media_id as id from file_media_object where file_id in (select id from file_ids)), + thumbs_ids as (select thumbnail_id as id from file_media_object where file_id in (select id from file_ids)), + storage_ids as (select id from media_ids union select id from thumbs_ids) + select * + from storage_object + where id in (select id from storage_ids)") + +(defn change-ids + "Given a collection and a map from ID to ID. Changes all the `keys` properties + so they point to the new ID existing in `map-ids`" + [map-ids coll keys] + + (let [generate-id + (fn [map-ids {:keys [id]}] + (assoc map-ids id (uuid/next))) + + remap-key + (fn [obj map-ids key] + (cond-> obj + (contains? obj key) + (assoc key (get map-ids (get obj key) (get obj key))))) + + change-id + (fn [map-ids obj] + (reduce #(remap-key %1 map-ids %2) obj keys)) + + new-map-ids (reduce generate-id map-ids coll)] + + [new-map-ids (map (partial change-id new-map-ids) coll)])) + +(defn allocate-storage-objects + "Copies the storage data to a new object and stores the new id into `map-ids`" + [storage map-ids objects] + (let [clone-object + (fn [map-ids object] + (try + (let [object (storage/row->storage-object object) + new-obj (storage/clone-object storage object)] + (assoc map-ids (:id object) (:id new-obj))) + (catch Exception err + (log/errorf "Error cloning store object %s" (:id object)) + map-ids)))] + (->> objects + (reduce clone-object map-ids)))) + +(defn create-profile-initial-data + [conn storage profile] + + (when-let [sample-project-id (get cfg/config :initial-data-project-id)] + (let [sample-project-name (get cfg/config :initial-data-project-name "Penpot Onboarding") + + proj (projects/create-project conn {:profile-id (:id profile) + :team-id (:default-team-id profile) + :name sample-project-name}) + + _ (projects/create-project-profile conn {:project-id (:id proj) + :profile-id (:id profile)}) + + _ (projects/create-team-project-profile conn {:team-id (:default-team-id profile) + :project-id (:id proj) + :profile-id (:id profile)}) + + ;; Retrieve data from templates + file (db/exec! conn [sql:file, sample-project-id]) + file-library-rel (db/exec! conn [sql:file-library-rel, sample-project-id]) + file-media-object (db/exec! conn [sql:file-media-object, sample-project-id]) + file-media-thumbnail (db/exec! conn [sql:file-media-thumbnail, sample-project-id]) + storage-object (db/exec! conn [sql:storage-object, sample-project-id]) + + map-ids {} + + ;; Create new ID's and change the references + [map-ids file] (change-ids map-ids file #{:id}) + + map-ids (allocate-storage-objects storage map-ids storage-object) + + [map-ids file-library-rel] (change-ids map-ids file-library-rel #{:file-id :library-file-id}) + [map-ids file-media-object] (change-ids map-ids file-media-object #{:id :file-id :media-id :thumbnail-id}) + [map-ids file-media-thumbnail] (change-ids map-ids file-media-thumbnail #{:id :media-object-id}) + + file (->> file (map (fn [data] (assoc data :project-id (:id proj))))) + file-profile-rel (->> file (map (fn [data] + (hash-map :file-id (:id data) + :profile-id (:id profile) + :is-owner true + :is-admin true + :can-edit true))))] + + ;; Re-insert into the database + (db/insert-multi! conn :file file) + (db/insert-multi! conn :file-profile-rel file-profile-rel) + (db/insert-multi! conn :file-library-rel file-library-rel) + (db/insert-multi! conn :file-media-object file-media-object) + (db/insert-multi! conn :file-media-thumbnail file-media-thumbnail)) + {:result "OK"})) diff --git a/backend/src/app/rpc/mutations/profile.clj b/backend/src/app/rpc/mutations/profile.clj index 49b1ee1f2..48189798c 100644 --- a/backend/src/app/rpc/mutations/profile.clj +++ b/backend/src/app/rpc/mutations/profile.clj @@ -5,7 +5,7 @@ ;; This Source Code Form is "Incompatible With Secondary Licenses", as ;; defined by the Mozilla Public License, v. 2.0. ;; -;; Copyright (c) 2020 UXBOX Labs SL +;; Copyright (c) 2020-2021 UXBOX Labs SL (ns app.rpc.mutations.profile (:require @@ -14,6 +14,7 @@ [app.common.uuid :as uuid] [app.config :as cfg] [app.db :as db] + [app.db.profile-initial-data :refer [create-profile-initial-data]] [app.emails :as emails] [app.http.session :as session] [app.media :as media] @@ -54,7 +55,7 @@ :opt-un [::token])) (sv/defmethod ::register-profile {:auth false} - [{:keys [pool tokens session] :as cfg} {:keys [token] :as params}] + [{:keys [pool tokens session storage] :as cfg} {:keys [token] :as params}] (when-not (:registration-enabled cfg/config) (ex/raise :type :restriction :code :registration-disabled)) @@ -68,6 +69,7 @@ (check-profile-existence! conn params) (let [profile (->> (create-profile conn params) (create-profile-relations conn))] + (create-profile-initial-data conn storage profile) (if token ;; If token comes in params, this is because the user comes diff --git a/backend/src/app/storage.clj b/backend/src/app/storage.clj index 30064f913..32b9fbdf1 100644 --- a/backend/src/app/storage.clj +++ b/backend/src/app/storage.clj @@ -94,16 +94,19 @@ (def ^:private sql:retrieve-storage-object "select * from storage_object where id = ? and deleted_at is null") +(defn row->storage-object [res] + (let [mdata (some-> (:metadata res) (db/decode-transit-pgobject))] + (StorageObject. (:id res) + (:size res) + (:created-at res) + (keyword (:backend res)) + mdata + nil))) + (defn- retrieve-database-object [{:keys [conn] :as storage} id] (when-let [res (db/exec-one! conn [sql:retrieve-storage-object id])] - (let [mdata (some-> (:metadata res) (db/decode-transit-pgobject))] - (StorageObject. (:id res) - (:size res) - (:created-at res) - (keyword (:backend res)) - mdata - nil)))) + (row->storage-object res))) (def sql:delete-storage-object "update storage_object set deleted_at=now() where id=? and deleted_at is null")