diff --git a/.gitignore b/.gitignore index 22ae73c022..b7f152cd6b 100644 --- a/.gitignore +++ b/.gitignore @@ -23,6 +23,7 @@ /backend/resources/public/assets /backend/resources/public/media /backend/target/ +/backend/builtin-templates /bundle* /cd.md /clj-profiler/ diff --git a/backend/resources/emails-mjml/change-email/en.mjml b/backend/resources/app/emails-mjml/change-email/en.mjml similarity index 100% rename from backend/resources/emails-mjml/change-email/en.mjml rename to backend/resources/app/emails-mjml/change-email/en.mjml diff --git a/backend/resources/emails-mjml/invite-to-team/en.mjml b/backend/resources/app/emails-mjml/invite-to-team/en.mjml similarity index 100% rename from backend/resources/emails-mjml/invite-to-team/en.mjml rename to backend/resources/app/emails-mjml/invite-to-team/en.mjml diff --git a/backend/resources/emails-mjml/password-recovery/en.mjml b/backend/resources/app/emails-mjml/password-recovery/en.mjml similarity index 100% rename from backend/resources/emails-mjml/password-recovery/en.mjml rename to backend/resources/app/emails-mjml/password-recovery/en.mjml diff --git a/backend/resources/emails-mjml/register/en.mjml b/backend/resources/app/emails-mjml/register/en.mjml similarity index 100% rename from backend/resources/emails-mjml/register/en.mjml rename to backend/resources/app/emails-mjml/register/en.mjml diff --git a/backend/resources/emails/change-email/en.html b/backend/resources/app/emails/change-email/en.html similarity index 100% rename from backend/resources/emails/change-email/en.html rename to backend/resources/app/emails/change-email/en.html diff --git a/backend/resources/emails/change-email/en.subj b/backend/resources/app/emails/change-email/en.subj similarity index 100% rename from backend/resources/emails/change-email/en.subj rename to backend/resources/app/emails/change-email/en.subj diff --git a/backend/resources/emails/change-email/en.txt b/backend/resources/app/emails/change-email/en.txt similarity index 100% rename from backend/resources/emails/change-email/en.txt rename to backend/resources/app/emails/change-email/en.txt diff --git a/backend/resources/emails/feedback/en.html b/backend/resources/app/emails/feedback/en.html similarity index 100% rename from backend/resources/emails/feedback/en.html rename to backend/resources/app/emails/feedback/en.html diff --git a/backend/resources/emails/feedback/en.subj b/backend/resources/app/emails/feedback/en.subj similarity index 100% rename from backend/resources/emails/feedback/en.subj rename to backend/resources/app/emails/feedback/en.subj diff --git a/backend/resources/emails/feedback/en.txt b/backend/resources/app/emails/feedback/en.txt similarity index 100% rename from backend/resources/emails/feedback/en.txt rename to backend/resources/app/emails/feedback/en.txt diff --git a/backend/resources/emails/invite-to-team/en.html b/backend/resources/app/emails/invite-to-team/en.html similarity index 100% rename from backend/resources/emails/invite-to-team/en.html rename to backend/resources/app/emails/invite-to-team/en.html diff --git a/backend/resources/emails/invite-to-team/en.subj b/backend/resources/app/emails/invite-to-team/en.subj similarity index 100% rename from backend/resources/emails/invite-to-team/en.subj rename to backend/resources/app/emails/invite-to-team/en.subj diff --git a/backend/resources/emails/invite-to-team/en.txt b/backend/resources/app/emails/invite-to-team/en.txt similarity index 100% rename from backend/resources/emails/invite-to-team/en.txt rename to backend/resources/app/emails/invite-to-team/en.txt diff --git a/backend/resources/emails/password-recovery/en.html b/backend/resources/app/emails/password-recovery/en.html similarity index 100% rename from backend/resources/emails/password-recovery/en.html rename to backend/resources/app/emails/password-recovery/en.html diff --git a/backend/resources/emails/password-recovery/en.subj b/backend/resources/app/emails/password-recovery/en.subj similarity index 100% rename from backend/resources/emails/password-recovery/en.subj rename to backend/resources/app/emails/password-recovery/en.subj diff --git a/backend/resources/emails/password-recovery/en.txt b/backend/resources/app/emails/password-recovery/en.txt similarity index 100% rename from backend/resources/emails/password-recovery/en.txt rename to backend/resources/app/emails/password-recovery/en.txt diff --git a/backend/resources/emails/register/en.html b/backend/resources/app/emails/register/en.html similarity index 100% rename from backend/resources/emails/register/en.html rename to backend/resources/app/emails/register/en.html diff --git a/backend/resources/emails/register/en.subj b/backend/resources/app/emails/register/en.subj similarity index 100% rename from backend/resources/emails/register/en.subj rename to backend/resources/app/emails/register/en.subj diff --git a/backend/resources/emails/register/en.txt b/backend/resources/app/emails/register/en.txt similarity index 100% rename from backend/resources/emails/register/en.txt rename to backend/resources/app/emails/register/en.txt diff --git a/backend/resources/app/onboarding.edn b/backend/resources/app/onboarding.edn new file mode 100644 index 0000000000..e2075d35b2 --- /dev/null +++ b/backend/resources/app/onboarding.edn @@ -0,0 +1,28 @@ +[{:id "penpot-design-system" + :name "Penpot Design System" + :thumbnail-uri "https://penpot.app/images/libraries/cover-ds-penpot.jpg" + :file-uri "https://github.com/penpot/penpot-files/raw/main/Penpot-Design-system.penpot"} + {:id "wireframing-kit" + :name "Wireframing Kit" + :thumbnail-uri "https://penpot.app/images/libraries/cover-wireframes.jpg" + :file-uri "https://github.com/penpot/penpot-files/raw/main/wireframing-kit.penpot"} + {:id "ant-design" + :name "Ant Design UI Kit (lite)" + :thumbnail-uri "https://penpot.app/images/libraries/cover-ant-design.jpg" + :file-uri "https://github.com/penpot/penpot-files/raw/main/Ant-Design-UI-Kit-Lite.penpot"} + {:id "cocomaterial" + :name "Cocomaterial" + :thumbnail-uri "https://penpot.app/images/libraries/cover-cocomaterial.jpg" + :file-uri "https://github.com/penpot/penpot-files/raw/main/Cocomaterial.penpot"} + {:id "circum-icons" + :name "Circum Icons pack" + :thumbnail-uri "https://penpot.app/images/libraries/cover-circum.jpg" + :file-uri "https://github.com/penpot/penpot-files/raw/main/CircumIcons.penpot"} + {:id "whiteboarding-kit" + :name "Whiteboarding Kit" + :thumbnail-uri "https://penpot.app/images/libraries/cover-whiteboards.jpg" + :file-uri "https://github.com/penpot/penpot-files/raw/main/Whiteboarding-mapping-kit.penpot"} + {:id "material-design-baseline" + :name "Material Design (baseline)" + :thumbnail-uri "https://penpot.app/images/libraries/cover-material.jpg" + :file-uri "https://github.com/penpot/penpot-files/raw/main/Material-Design-Kit.penpot"}] diff --git a/backend/resources/api-doc-entry.tmpl b/backend/resources/app/templates/api-doc-entry.tmpl similarity index 100% rename from backend/resources/api-doc-entry.tmpl rename to backend/resources/app/templates/api-doc-entry.tmpl diff --git a/backend/resources/api-doc.css b/backend/resources/app/templates/api-doc.css similarity index 100% rename from backend/resources/api-doc.css rename to backend/resources/app/templates/api-doc.css diff --git a/backend/resources/api-doc.js b/backend/resources/app/templates/api-doc.js similarity index 100% rename from backend/resources/api-doc.js rename to backend/resources/app/templates/api-doc.js diff --git a/backend/resources/api-doc.tmpl b/backend/resources/app/templates/api-doc.tmpl similarity index 78% rename from backend/resources/api-doc.tmpl rename to backend/resources/app/templates/api-doc.tmpl index c7c447b4d3..4ad521a8b3 100644 --- a/backend/resources/api-doc.tmpl +++ b/backend/resources/app/templates/api-doc.tmpl @@ -10,10 +10,10 @@ @@ -26,21 +26,21 @@

RPC COMMAND METHODS:

RPC QUERY METHODS:

RPC MUTATION METHODS:

diff --git a/backend/resources/templates/base.tmpl b/backend/resources/app/templates/base.tmpl similarity index 89% rename from backend/resources/templates/base.tmpl rename to backend/resources/app/templates/base.tmpl index 7f8709dd9f..893b6bc066 100644 --- a/backend/resources/templates/base.tmpl +++ b/backend/resources/app/templates/base.tmpl @@ -7,7 +7,7 @@ {% block title %}{% endblock %} diff --git a/backend/resources/templates/debug.tmpl b/backend/resources/app/templates/debug.tmpl similarity index 99% rename from backend/resources/templates/debug.tmpl rename to backend/resources/app/templates/debug.tmpl index 7479dd9827..e23e7b5470 100644 --- a/backend/resources/templates/debug.tmpl +++ b/backend/resources/app/templates/debug.tmpl @@ -1,4 +1,4 @@ -{% extends "templates/base.tmpl" %} +{% extends "app/templates/base.tmpl" %} {% block title %} Debug Main Page diff --git a/backend/resources/templates/error-list.tmpl b/backend/resources/app/templates/error-list.tmpl similarity index 88% rename from backend/resources/templates/error-list.tmpl rename to backend/resources/app/templates/error-list.tmpl index 66835867a7..d86d983d35 100644 --- a/backend/resources/templates/error-list.tmpl +++ b/backend/resources/app/templates/error-list.tmpl @@ -1,4 +1,4 @@ -{% extends "templates/base.tmpl" %} +{% extends "app/templates/base.tmpl" %} {% block title %} penpot - error list diff --git a/backend/resources/templates/error-report.tmpl b/backend/resources/app/templates/error-report.tmpl similarity index 98% rename from backend/resources/templates/error-report.tmpl rename to backend/resources/app/templates/error-report.tmpl index a3fcc158d0..60905c689a 100644 --- a/backend/resources/templates/error-report.tmpl +++ b/backend/resources/app/templates/error-report.tmpl @@ -1,4 +1,4 @@ -{% extends "templates/base.tmpl" %} +{% extends "app/templates/base.tmpl" %} {% block title %} penpot - error report {{id}} diff --git a/backend/resources/templates/styles.css b/backend/resources/app/templates/styles.css similarity index 100% rename from backend/resources/templates/styles.css rename to backend/resources/app/templates/styles.css diff --git a/backend/scripts/build b/backend/scripts/build index 1537b7f91e..98174a5c16 100755 --- a/backend/scripts/build +++ b/backend/scripts/build @@ -17,5 +17,6 @@ cp scripts/manage.template.sh target/dist/manage.sh; chmod +x target/dist/run.sh; chmod +x target/dist/manage.sh; - - +# Prefetch +bb ./scripts/prefetch-templates.clj resources/app/onboarding.edn builtin-templates/ +cp -r builtin-templates target/dist/ diff --git a/backend/scripts/prefetch-templates.clj b/backend/scripts/prefetch-templates.clj new file mode 100755 index 0000000000..68936b1589 --- /dev/null +++ b/backend/scripts/prefetch-templates.clj @@ -0,0 +1,40 @@ +#!/usr/bin/env bb + +(require '[babashka.curl :as curl] + '[babashka.fs :as fs]) + +(defn download-if-needed! + [dest data] + (doseq [{:keys [id file-uri] :as item} data] + (let [file (fs/file dest id) + rsp (curl/get file-uri {:as :stream})] + + (when (not= 200 (:status rsp)) + (println (format "unable to download %s (uri: %s)" id file-uri)) + (System/exit -1)) + + (when-not (fs/exists? (str file)) + (println (format "=> downloading %s" id)) + (with-open [output (io/output-stream file)] + (io/copy (:body rsp) output)))))) + +(defn read-defs-file + [path] + (with-open [content (io/reader path)] + (edn/read-string (slurp content)))) + +(let [[path dest] *command-line-args*] + (when (or (nil? path) + (nil? dest)) + (println "invalid arguments") + (System/exit -1)) + + (when-not (fs/exists? path) + (println (format "file %s does not exists" path)) + (System/exit -1)) + + (when-not (fs/exists? dest) + (fs/create-dirs dest)) + + (let [data (read-defs-file path)] + (download-if-needed! dest data))) diff --git a/backend/src/app/http/client.clj b/backend/src/app/http/client.clj index bf52b61e25..8718606b1d 100644 --- a/backend/src/app/http/client.clj +++ b/backend/src/app/http/client.clj @@ -12,6 +12,8 @@ [integrant.core :as ig] [java-http-clj.core :as http])) +(s/def ::client fn?) + (defmethod ig/pre-init-spec :app.http/client [_] (s/keys :req-un [::wrk/executor])) @@ -28,3 +30,12 @@ (http/send req {:client client :as response-type}) (http/send-async req {:client client :as response-type})))) {::client client}))) + + +(defn req! + "A convencience toplevel function for gradual migration to a new API + convention." + ([client request] + (client request)) + ([client request options] + (client request options))) diff --git a/backend/src/app/http/debug.clj b/backend/src/app/http/debug.clj index 11a29f892b..21dcb50f02 100644 --- a/backend/src/app/http/debug.clj +++ b/backend/src/app/http/debug.clj @@ -66,7 +66,7 @@ :code :only-admins-allowed)) (yrs/response :status 200 :headers {"content-type" "text/html"} - :body (-> (io/resource "templates/debug.tmpl") + :body (-> (io/resource "app/templates/debug.tmpl") (tmpl/render {})))) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; @@ -225,7 +225,7 @@ :trace (or (:trace report) (some-> report :error :trace)) :params (:params report)}] - (-> (io/resource "templates/error-report.tmpl") + (-> (io/resource "app/templates/error-report.tmpl") (tmpl/render params))))] (when-not (authorized? pool request) @@ -253,7 +253,7 @@ (let [items (db/exec! pool [sql:error-reports]) items (map #(update % :created-at dt/format-instant :rfc1123) items)] (yrs/response :status 200 - :body (-> (io/resource "templates/error-list.tmpl") + :body (-> (io/resource "app/templates/error-list.tmpl") (tmpl/render {:items items})) :headers {"content-type" "text/html; charset=utf-8" "x-robots-tag" "noindex"}))) diff --git a/backend/src/app/main.clj b/backend/src/app/main.clj index cc20cc7e5f..ed26617640 100644 --- a/backend/src/app/main.clj +++ b/backend/src/app/main.clj @@ -235,7 +235,8 @@ :audit (ig/ref :app.loggers.audit/collector) :ldap (ig/ref :app.auth.ldap/provider) :http-client (ig/ref :app.http/client) - :executors (ig/ref :app.worker/executors)} + :executors (ig/ref :app.worker/executors) + :templates (ig/ref :app.setup/builtin-templates)} :app.rpc.doc/routes {:methods (ig/ref :app.rpc/methods)} @@ -352,6 +353,9 @@ {:port (cf/get :srepl-port) :host (cf/get :srepl-host)} + :app.setup/builtin-templates + {:http-client (ig/ref :app.http/client)} + :app.setup/props {:pool (ig/ref :app.db/pool) :key (cf/get :secret-key)} diff --git a/backend/src/app/rpc.clj b/backend/src/app/rpc.clj index 477b0a6339..7c59f964f7 100644 --- a/backend/src/app/rpc.clj +++ b/backend/src/app/rpc.clj @@ -242,6 +242,7 @@ (let [cfg (assoc cfg ::type "command" ::metrics-id :rpc-command-timing)] (->> (sv/scan-ns 'app.rpc.commands.binfile 'app.rpc.commands.comments + 'app.rpc.commands.management 'app.rpc.commands.auth 'app.rpc.commands.ldap 'app.rpc.commands.demo) diff --git a/backend/src/app/rpc/commands/binfile.clj b/backend/src/app/rpc/commands/binfile.clj index 36c51b0e63..c7bdf54dfa 100644 --- a/backend/src/app/rpc/commands/binfile.clj +++ b/backend/src/app/rpc/commands/binfile.clj @@ -536,7 +536,7 @@ :or {overwrite? false migrate? false timestamp (dt/now)} :as options}] - (us/assert! ::read-import-options options) + (us/verify! ::read-import-options options) (letfn [(lookup-index [id] (if ignore-index-errors? @@ -752,7 +752,11 @@ (case section :v1/rels (read-rels-section! input) :v1/files (read-files-section! input files) - :v1/sobjects (read-sobjects-section! input)))))))))) + :v1/sobjects (read-sobjects-section! input))) + + ;; Knowing that the ids of the created files are in + ;; index, just lookup them and return it as a set + (into #{} (keep #(get @*index* %)) files)))))))) (defn export! [cfg] diff --git a/backend/src/app/rpc/commands/management.clj b/backend/src/app/rpc/commands/management.clj new file mode 100644 index 0000000000..4e43d79610 --- /dev/null +++ b/backend/src/app/rpc/commands/management.clj @@ -0,0 +1,403 @@ +;; 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) UXBOX Labs SL + +(ns app.rpc.commands.management + "A collection of RPC methods for manage the files, projects and team organization." + (:require + [app.common.data :as d] + [app.common.exceptions :as ex] + [app.common.pages.migrations :as pmg] + [app.common.spec :as us] + [app.common.uuid :as uuid] + [app.db :as db] + [app.rpc.commands.binfile :as binfile] + [app.rpc.doc :as-alias doc] + [app.rpc.mutations.projects :refer [create-project-role create-project]] + [app.rpc.queries.projects :as proj] + [app.rpc.queries.teams :as teams] + [app.util.blob :as blob] + [app.util.services :as sv] + [app.util.time :as dt] + [clojure.spec.alpha :as s] + [clojure.walk :as walk])) + +;; --- COMMAND: Duplicate File + +(declare duplicate-file) + +(s/def ::id ::us/uuid) +(s/def ::profile-id ::us/uuid) +(s/def ::project-id ::us/uuid) +(s/def ::file-id ::us/uuid) +(s/def ::team-id ::us/uuid) +(s/def ::name ::us/string) + +(s/def ::duplicate-file + (s/keys :req-un [::profile-id ::file-id] + :opt-un [::name])) + +(sv/defmethod ::duplicate-file + "Duplicate a single file in the same team." + {::doc/added "1.16"} + [{:keys [pool] :as cfg} params] + (db/with-atomic [conn pool] + (duplicate-file conn params))) + +(defn- remap-id + [item index key] + (cond-> item + (contains? item key) + (assoc key (get index (get item key) (get item key))))) + +(defn- process-file + [file index] + (letfn [(process-form [form] + (cond-> form + ;; Relink library items + (and (map? form) + (uuid? (:component-file form))) + (update :component-file #(get index % %)) + + (and (map? form) + (uuid? (:fill-color-ref-file form))) + (update :fill-color-ref-file #(get index % %)) + + (and (map? form) + (uuid? (:stroke-color-ref-file form))) + (update :stroke-color-ref-file #(get index % %)) + + (and (map? form) + (uuid? (:typography-ref-file form))) + (update :typography-ref-file #(get index % %)) + + ;; Relink Image Shapes + (and (map? form) + (map? (:metadata form)) + (= :image (:type form))) + (update-in [:metadata :id] #(get index % %)))) + + ;; A function responsible to analyze all file data and + ;; replace the old :component-file reference with the new + ;; ones, using the provided file-index + (relink-shapes [data] + (walk/postwalk process-form data)) + + ;; A function responsible of process the :media attr of file + ;; data and remap the old ids with the new ones. + (relink-media [media] + (reduce-kv (fn [res k v] + (let [id (get index k)] + (if (uuid? id) + (-> res + (assoc id (assoc v :id id)) + (dissoc k)) + res))) + media + media))] + + (update file :data + (fn [data] + (-> data + (blob/decode) + (assoc :id (:id file)) + (pmg/migrate-data) + (update :pages-index relink-shapes) + (update :components relink-shapes) + (update :media relink-media) + (d/without-nils) + (blob/encode)))))) + +(def sql:retrieve-used-libraries + "select flr.* + from file_library_rel as flr + inner join file as l on (flr.library_file_id = l.id) + where flr.file_id = ? + and l.deleted_at is null") + +(def sql:retrieve-used-media-objects + "select fmo.* + from file_media_object as fmo + inner join storage_object as so on (fmo.media_id = so.id) + where fmo.file_id = ? + and so.deleted_at is null") + +(defn duplicate-file* + [conn {:keys [profile-id file index project-id name flibs fmeds]} {:keys [reset-shared-flag] :as opts}] + (let [flibs (or flibs (db/exec! conn [sql:retrieve-used-libraries (:id file)])) + fmeds (or fmeds (db/exec! conn [sql:retrieve-used-media-objects (:id file)])) + + ;; memo uniform creation/modification date + now (dt/now) + ignore (dt/plus now (dt/duration {:seconds 5})) + + ;; add to the index all file media objects. + index (reduce #(assoc %1 (:id %2) (uuid/next)) index fmeds) + + flibs-xf (comp + (map #(remap-id % index :file-id)) + (map #(remap-id % index :library-file-id)) + (map #(assoc % :synced-at now)) + (map #(assoc % :created-at now))) + + ;; remap all file-library-rel row + flibs (sequence flibs-xf flibs) + + fmeds-xf (comp + (map #(assoc % :id (get index (:id %)))) + (map #(assoc % :created-at now)) + (map #(remap-id % index :file-id))) + + ;; remap all file-media-object rows + fmeds (sequence fmeds-xf fmeds) + + file (cond-> file + (some? project-id) + (assoc :project-id project-id) + + (some? name) + (assoc :name name) + + (true? reset-shared-flag) + (assoc :is-shared false)) + + file (-> file + (assoc :created-at now) + (assoc :modified-at now) + (assoc :ignore-sync-until ignore) + (update :id #(get index %)) + (process-file index))] + + (db/insert! conn :file file) + (db/insert! conn :file-profile-rel + {:file-id (:id file) + :profile-id profile-id + :is-owner true + :is-admin true + :can-edit true}) + + (doseq [params flibs] + (db/insert! conn :file-library-rel params)) + + (doseq [params fmeds] + (db/insert! conn :file-media-object params)) + + file)) + +(defn duplicate-file + [conn {:keys [profile-id file-id] :as params}] + (let [file (db/get-by-id conn :file file-id) + index {file-id (uuid/next)} + params (assoc params :index index :file file)] + (proj/check-edition-permissions! conn profile-id (:project-id file)) + (db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"]) + (-> (duplicate-file* conn params {:reset-shared-flag true}) + (update :data blob/decode)))) + +;; --- COMMAND: Duplicate Project + +(declare duplicate-project) + +(s/def ::duplicate-project + (s/keys :req-un [::profile-id ::project-id] + :opt-un [::name])) + +(sv/defmethod ::duplicate-project + "Duplicate an entire project with all the files" + {::doc/added "1.16"} + [{:keys [pool] :as cfg} params] + (db/with-atomic [conn pool] + (duplicate-project conn params))) + +(defn duplicate-project + [conn {:keys [profile-id project-id name] :as params}] + + ;; Defer all constraints + (db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"]) + + (let [project (db/get-by-id conn :project project-id) + + files (db/query conn :file + {:project-id (:id project) + :deleted-at nil} + {:columns [:id]}) + + project (cond-> project + (string? name) + (assoc :name name) + + :always + (assoc :id (uuid/next)))] + + ;; Check if the source team-id allow creating new project for current user + (teams/check-edition-permissions! conn profile-id (:team-id project)) + + ;; create the duplicated project and assign the current profile as + ;; a project owner + (create-project conn project) + (create-project-role conn {:project-id (:id project) + :profile-id profile-id + :role :owner}) + + ;; duplicate all files + (let [index (reduce #(assoc %1 (:id %2) (uuid/next)) {} files) + params (-> params + (dissoc :name) + (assoc :project-id (:id project)) + (assoc :index index))] + (doseq [{:keys [id]} files] + (let [file (db/get-by-id conn :file id) + params (assoc params :file file) + opts {:reset-shared-flag false}] + (duplicate-file* conn params opts)))) + + ;; return the created project + project)) + +;; --- COMMAND: Move file + +(def sql:retrieve-files + "select id, project_id from file where id = ANY(?)") + +(def sql:move-files + "update file set project_id = ? where id = ANY(?)") + +(def sql:delete-broken-relations + "with broken as ( + (select * from file_library_rel as flr + inner join file as f on (flr.file_id = f.id) + inner join project as p on (f.project_id = p.id) + inner join file as lf on (flr.library_file_id = lf.id) + inner join project as lp on (lf.project_id = lp.id) + where p.id = ANY(?) + and lp.team_id != p.team_id) + ) + delete from file_library_rel as rel + using broken as br + where rel.file_id = br.file_id + and rel.library_file_id = br.library_file_id") + +(defn move-files + [conn {:keys [profile-id ids project-id] :as params}] + + (let [fids (db/create-array conn "uuid" ids) + files (db/exec! conn [sql:retrieve-files fids]) + source (into #{} (map :project-id) files) + pids (->> (conj source project-id) + (db/create-array conn "uuid"))] + + ;; Check if we have permissions on the destination project + (proj/check-edition-permissions! conn profile-id project-id) + + ;; Check if we have permissions on all source projects + (doseq [project-id source] + (proj/check-edition-permissions! conn profile-id project-id)) + + (when (contains? source project-id) + (ex/raise :type :validation + :code :cant-move-to-same-project + :hint "Unable to move a file to the same project")) + + ;; move all files to the project + (db/exec-one! conn [sql:move-files project-id fids]) + + ;; delete possible broken relations on moved files + (db/exec-one! conn [sql:delete-broken-relations pids]) + + nil)) + +(s/def ::ids (s/every ::us/uuid :kind set?)) +(s/def ::move-files + (s/keys :req-un [::profile-id ::ids ::project-id])) + +(sv/defmethod ::move-files + "Move a set of files from one project to other." + {::doc/added "1.16"} + [{:keys [pool] :as cfg} params] + (db/with-atomic [conn pool] + (move-files conn params))) + + +;; --- COMMAND: Move project + +(defn move-project + [conn {:keys [profile-id team-id project-id] :as params}] + (let [project (db/get-by-id conn :project project-id {:columns [:id :team-id]}) + pids (->> (db/query conn :project {:team-id (:team-id project)} {:columns [:id]}) + (map :id) + (db/create-array conn "uuid"))] + + (teams/check-edition-permissions! conn profile-id (:team-id project)) + (teams/check-edition-permissions! conn profile-id team-id) + + (when (= team-id (:team-id project)) + (ex/raise :type :validation + :code :cant-move-to-same-team + :hint "Unable to move a project to same team")) + + ;; move project to the destination team + (db/update! conn :project + {:team-id team-id} + {:id project-id}) + + ;; delete possible broken relations on moved files + (db/exec-one! conn [sql:delete-broken-relations pids]) + + nil)) + + +(s/def ::move-project + (s/keys :req-un [::profile-id ::team-id ::project-id])) + +(sv/defmethod ::move-project + "Move projects between teams." + {::doc/added "1.16"} + [{:keys [pool] :as cfg} params] + (db/with-atomic [conn pool] + (move-project conn params))) + +;; --- COMMAND: Clone Template + +(declare clone-template) + +(s/def ::template-id ::us/not-empty-string) +(s/def ::clone-template + (s/keys :req-un [::profile-id ::project-id ::template-id])) + +(sv/defmethod ::clone-template + "Clone into the specified project the template by its id." + {::doc/added "1.16"} + [{:keys [pool] :as cfg} params] + (db/with-atomic [conn pool] + (-> (assoc cfg :conn conn) + (clone-template params)))) + +(defn- clone-template + [{:keys [conn templates] :as cfg} {:keys [profile-id template-id project-id]}] + (let [template (d/seek #(= (:id %) template-id) templates) + project (db/get-by-id conn :project project-id {:columns [:id :team-id]})] + + (teams/check-edition-permissions! conn profile-id (:team-id project)) + + (when-not template + (ex/raise :type :not-found + :code :template-not-found + :hint "template not found")) + + (-> cfg + (assoc ::binfile/input (:path template)) + (assoc ::binfile/project-id (:id project)) + (assoc ::binfile/ignore-index-errors? true) + (assoc ::binfile/migrate? true) + (binfile/import!)))) + + +;; --- COMMAND: Retrieve list of builtin templates + +(s/def ::retrieve-list-of-builtin-templates any?) + +(sv/defmethod ::retrieve-list-of-builtin-templates + [cfg _params] + (mapv #(select-keys % [:id :name]) (:templates cfg))) diff --git a/backend/src/app/rpc/doc.clj b/backend/src/app/rpc/doc.clj index 6afec5643c..499af1a053 100644 --- a/backend/src/app/rpc/doc.clj +++ b/backend/src/app/rpc/doc.clj @@ -61,7 +61,7 @@ (if (contains? cf/flags :backend-api-doc) (let [context (prepare-context methods)] (fn [_ respond _] - (respond (yrs/response 200 (-> (io/resource "api-doc.tmpl") + (respond (yrs/response 200 (-> (io/resource "app/templates/api-doc.tmpl") (tmpl/render context)))))) (fn [_ respond _] (respond (yrs/response 404))))) diff --git a/backend/src/app/rpc/mutations/management.clj b/backend/src/app/rpc/mutations/management.clj index 322fbdacd7..c2038868ae 100644 --- a/backend/src/app/rpc/mutations/management.clj +++ b/backend/src/app/rpc/mutations/management.clj @@ -7,329 +7,52 @@ (ns app.rpc.mutations.management "Move & Duplicate RPC methods for files and projects." (:require - [app.common.data :as d] - [app.common.exceptions :as ex] - [app.common.pages.migrations :as pmg] - [app.common.spec :as us] - [app.common.uuid :as uuid] [app.db :as db] - [app.rpc.mutations.projects :refer [create-project-role create-project]] - [app.rpc.queries.projects :as proj] - [app.rpc.queries.teams :as teams] - [app.util.blob :as blob] + [app.rpc.commands.management :as cmd.mgm] + [app.rpc.doc :as-alias doc] [app.util.services :as sv] - [app.util.time :as dt] - [clojure.spec.alpha :as s] - [clojure.walk :as walk])) - -(s/def ::id ::us/uuid) -(s/def ::profile-id ::us/uuid) -(s/def ::project-id ::us/uuid) -(s/def ::file-id ::us/uuid) -(s/def ::team-id ::us/uuid) -(s/def ::name ::us/string) - -(defn- remap-id - [item index key] - (cond-> item - (contains? item key) - (assoc key (get index (get item key) (get item key))))) - -(defn- process-file - [file index] - (letfn [(process-form [form] - (cond-> form - ;; Relink library items - (and (map? form) - (uuid? (:component-file form))) - (update :component-file #(get index % %)) - - (and (map? form) - (uuid? (:fill-color-ref-file form))) - (update :fill-color-ref-file #(get index % %)) - - (and (map? form) - (uuid? (:stroke-color-ref-file form))) - (update :stroke-color-ref-file #(get index % %)) - - (and (map? form) - (uuid? (:typography-ref-file form))) - (update :typography-ref-file #(get index % %)) - - ;; Relink Image Shapes - (and (map? form) - (map? (:metadata form)) - (= :image (:type form))) - (update-in [:metadata :id] #(get index % %)))) - - ;; A function responsible to analyze all file data and - ;; replace the old :component-file reference with the new - ;; ones, using the provided file-index - (relink-shapes [data] - (walk/postwalk process-form data)) - - ;; A function responsible of process the :media attr of file - ;; data and remap the old ids with the new ones. - (relink-media [media] - (reduce-kv (fn [res k v] - (let [id (get index k)] - (if (uuid? id) - (-> res - (assoc id (assoc v :id id)) - (dissoc k)) - res))) - media - media))] - - (update file :data - (fn [data] - (-> data - (blob/decode) - (assoc :id (:id file)) - (pmg/migrate-data) - (update :pages-index relink-shapes) - (update :components relink-shapes) - (update :media relink-media) - (d/without-nils) - (blob/encode)))))) - -(def sql:retrieve-used-libraries - "select flr.* - from file_library_rel as flr - inner join file as l on (flr.library_file_id = l.id) - where flr.file_id = ? - and l.deleted_at is null") - -(def sql:retrieve-used-media-objects - "select fmo.* - from file_media_object as fmo - inner join storage_object as so on (fmo.media_id = so.id) - where fmo.file_id = ? - and so.deleted_at is null") - -(defn duplicate-file - [conn {:keys [profile-id file index project-id name flibs fmeds]} {:keys [reset-shared-flag] :as opts}] - (let [flibs (or flibs (db/exec! conn [sql:retrieve-used-libraries (:id file)])) - fmeds (or fmeds (db/exec! conn [sql:retrieve-used-media-objects (:id file)])) - - ;; memo uniform creation/modification date - now (dt/now) - ignore (dt/plus now (dt/duration {:seconds 5})) - - ;; add to the index all file media objects. - index (reduce #(assoc %1 (:id %2) (uuid/next)) index fmeds) - - flibs-xf (comp - (map #(remap-id % index :file-id)) - (map #(remap-id % index :library-file-id)) - (map #(assoc % :synced-at now)) - (map #(assoc % :created-at now))) - - ;; remap all file-library-rel row - flibs (sequence flibs-xf flibs) - - fmeds-xf (comp - (map #(assoc % :id (get index (:id %)))) - (map #(assoc % :created-at now)) - (map #(remap-id % index :file-id))) - - ;; remap all file-media-object rows - fmeds (sequence fmeds-xf fmeds) - - file (cond-> file - (some? project-id) - (assoc :project-id project-id) - - (some? name) - (assoc :name name) - - (true? reset-shared-flag) - (assoc :is-shared false)) - - file (-> file - (assoc :created-at now) - (assoc :modified-at now) - (assoc :ignore-sync-until ignore) - (update :id #(get index %)) - (process-file index))] - - (db/insert! conn :file file) - (db/insert! conn :file-profile-rel - {:file-id (:id file) - :profile-id profile-id - :is-owner true - :is-admin true - :can-edit true}) - - (doseq [params flibs] - (db/insert! conn :file-library-rel params)) - - (doseq [params fmeds] - (db/insert! conn :file-media-object params)) - - file)) - + [clojure.spec.alpha :as s])) ;; --- MUTATION: Duplicate File -(declare duplicate-file) - -(s/def ::duplicate-file - (s/keys :req-un [::profile-id ::file-id] - :opt-un [::name])) +(s/def ::duplicate-file ::cmd.mgm/duplicate-file) (sv/defmethod ::duplicate-file - [{:keys [pool] :as cfg} {:keys [profile-id file-id] :as params}] + {::doc/added "1.2" + ::doc/deprecated "1.16"} + [{:keys [pool] :as cfg} params] (db/with-atomic [conn pool] - (let [file (db/get-by-id conn :file file-id) - index {file-id (uuid/next)} - params (assoc params :index index :file file)] - (proj/check-edition-permissions! conn profile-id (:project-id file)) - (db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"]) - (-> (duplicate-file conn params {:reset-shared-flag true}) - (update :data blob/decode))))) - + (cmd.mgm/duplicate-file conn params))) ;; --- MUTATION: Duplicate Project -(declare duplicate-project) - -(s/def ::duplicate-project - (s/keys :req-un [::profile-id ::project-id] - :opt-un [::name])) +(s/def ::duplicate-project ::cmd.mgm/duplicate-project) (sv/defmethod ::duplicate-project - [{:keys [pool] :as cfg} {:keys [profile-id project-id] :as params}] + {::doc/added "1.2" + ::doc/deprecated "1.16"} + [{:keys [pool] :as cfg} params] (db/with-atomic [conn pool] - (let [project (db/get-by-id conn :project project-id)] - (teams/check-edition-permissions! conn profile-id (:team-id project)) - (db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"]) - (duplicate-project conn (assoc params :project project))))) - -(defn duplicate-project - [conn {:keys [profile-id project name] :as params}] - (let [files (db/query conn :file - {:project-id (:id project) - :deleted-at nil} - {:columns [:id]}) - - project (cond-> project - (string? name) - (assoc :name name) - - :always - (assoc :id (uuid/next)))] - - ;; create the duplicated project and assign the current profile as - ;; a project owner - (create-project conn project) - (create-project-role conn {:project-id (:id project) - :profile-id profile-id - :role :owner}) - - ;; duplicate all files - (let [index (reduce #(assoc %1 (:id %2) (uuid/next)) {} files) - params (-> params - (dissoc :name) - (assoc :project-id (:id project)) - (assoc :index index))] - (doseq [{:keys [id]} files] - (let [file (db/get-by-id conn :file id) - params (assoc params :file file) - opts {:reset-shared-flag false}] - (duplicate-file conn params opts)))) - - ;; return the created project - project)) - + (cmd.mgm/duplicate-project conn params))) ;; --- MUTATION: Move file -(def sql:retrieve-files - "select id, project_id from file where id = ANY(?)") - -(def sql:move-files - "update file set project_id = ? where id = ANY(?)") - -(def sql:delete-broken-relations - "with broken as ( - (select * from file_library_rel as flr - inner join file as f on (flr.file_id = f.id) - inner join project as p on (f.project_id = p.id) - inner join file as lf on (flr.library_file_id = lf.id) - inner join project as lp on (lf.project_id = lp.id) - where p.id = ANY(?) - and lp.team_id != p.team_id) - ) - delete from file_library_rel as rel - using broken as br - where rel.file_id = br.file_id - and rel.library_file_id = br.library_file_id") - -(s/def ::ids (s/every ::us/uuid :kind set?)) -(s/def ::move-files - (s/keys :req-un [::profile-id ::ids ::project-id])) +(s/def ::move-files ::cmd.mgm/move-files) (sv/defmethod ::move-files - [{:keys [pool] :as cfg} {:keys [profile-id ids project-id] :as params}] + {::doc/added "1.2" + ::doc/deprecated "1.16"} + [{:keys [pool] :as cfg} params] (db/with-atomic [conn pool] - (let [fids (db/create-array conn "uuid" ids) - files (db/exec! conn [sql:retrieve-files fids]) - source (into #{} (map :project-id) files) - pids (->> (conj source project-id) - (db/create-array conn "uuid"))] - - ;; Check if we have permissions on the destination project - (proj/check-edition-permissions! conn profile-id project-id) - - ;; Check if we have permissions on all source projects - (doseq [project-id source] - (proj/check-edition-permissions! conn profile-id project-id)) - - (when (contains? source project-id) - (ex/raise :type :validation - :code :cant-move-to-same-project - :hint "Unable to move a file to the same project")) - - ;; move all files to the project - (db/exec-one! conn [sql:move-files project-id fids]) - - ;; delete possible broken relations on moved files - (db/exec-one! conn [sql:delete-broken-relations pids]) - - nil))) - + (cmd.mgm/move-files conn params))) ;; --- MUTATION: Move project -(declare move-project) - -(s/def ::move-project - (s/keys :req-un [::profile-id ::team-id ::project-id])) +(s/def ::move-project ::cmd.mgm/move-project) (sv/defmethod ::move-project - [{:keys [pool] :as cfg} {:keys [profile-id team-id project-id] :as params}] + {::doc/added "1.2" + ::doc/deprecated "1.16"} + [{:keys [pool] :as cfg} params] (db/with-atomic [conn pool] - (let [project (db/get-by-id conn :project project-id {:columns [:id :team-id]}) - - pids (->> (db/query conn :project {:team-id (:team-id project)} {:columns [:id]}) - (map :id) - (db/create-array conn "uuid"))] - - (teams/check-edition-permissions! conn profile-id (:team-id project)) - (teams/check-edition-permissions! conn profile-id team-id) - - (when (= team-id (:team-id project)) - (ex/raise :type :validation - :code :cant-move-to-same-team - :hint "Unable to move a project to same team")) - - ;; move project to the destination team - (db/update! conn :project - {:team-id team-id} - {:id project-id}) - - ;; delete possible broken relations on moved files - (db/exec-one! conn [sql:delete-broken-relations pids]) - - nil))) + (cmd.mgm/move-project conn params))) diff --git a/backend/src/app/setup.clj b/backend/src/app/setup.clj index b1b5e93138..7bc0960ba5 100644 --- a/backend/src/app/setup.clj +++ b/backend/src/app/setup.clj @@ -10,6 +10,7 @@ [app.common.logging :as l] [app.common.uuid :as uuid] [app.db :as db] + [app.setup.builtin-templates] [buddy.core.codecs :as bc] [buddy.core.nonce :as bn] [clojure.spec.alpha :as s] diff --git a/backend/src/app/setup/builtin_templates.clj b/backend/src/app/setup/builtin_templates.clj new file mode 100644 index 0000000000..d052550582 --- /dev/null +++ b/backend/src/app/setup/builtin_templates.clj @@ -0,0 +1,74 @@ +;; 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) UXBOX Labs SL + +(ns app.setup.builtin-templates + "A service/module that is reponsible for download, load & internally + expose a set of builtin penpot file templates." + (:require + [app.common.logging :as l] + [app.common.spec :as us] + [app.http.client :as http] + [clojure.edn :as edn] + [clojure.java.io :as io] + [clojure.spec.alpha :as s] + [datoteka.core :as fs] + [integrant.core :as ig])) + +(declare download-all!) + +(s/def ::id ::us/not-empty-string) +(s/def ::name ::us/not-empty-string) +(s/def ::thumbnail-uri ::us/not-empty-string) +(s/def ::file-uri ::us/not-empty-string) +(s/def ::path fs/path?) + +(s/def ::template + (s/keys :req-un [::id ::name ::thumbnail-uri ::file-uri] + :opt-un [::path])) + +(s/def ::http-client ::http/client) + +(defmethod ig/pre-init-spec :app.setup/builtin-templates [_] + (s/keys :req-un [::http-client])) + +(defmethod ig/init-key :app.setup/builtin-templates + [_ cfg] + (let [presets (-> "app/onboarding.edn" io/resource slurp edn/read-string)] + (l/info :hint "loading template files" :total (count presets)) + (let [result (download-all! cfg presets)] + (us/conform (s/coll-of ::template) result)))) + +(defn- download-preset! + [cfg {:keys [path file-uri] :as preset}] + (let [response (http/req! (:http-client cfg) + {:method :get + :uri file-uri} + {:response-type :input-stream + :sync? true})] + (us/verify! (= 200 (:status response)) "unexpected response found on fetching preset") + (with-open [output (io/output-stream path)] + (with-open [input (io/input-stream (:body response))] + (io/copy input output))))) + +(defn- download-all! + "Download presets to the default directory, if preset is already + downloaded, no action will be performed." + [cfg presets] + (let [dest (fs/join fs/*cwd* "builtin-templates")] + (when-not (fs/exists? dest) + (fs/create-dir dest)) + + (doall + (map (fn [item] + (let [path (fs/join dest (:id item)) + item (assoc item :path path)] + (if (fs/exists? path) + (l/trace :hint "template file already present" :id (:id item)) + (do + (l/trace :hint "downloading template file" :id (:id item) :dest (str path)) + (download-preset! cfg item))) + item)) + presets)))) diff --git a/backend/src/app/setup/initial_data.clj b/backend/src/app/setup/initial_data.clj deleted file mode 100644 index 6532a54e57..0000000000 --- a/backend/src/app/setup/initial_data.clj +++ /dev/null @@ -1,106 +0,0 @@ -;; This Source Code Form is subject to the terms of the Mozilla Public -;; License, v. 2.0. If a copy of the MPL was not distributed with this -;; file, You can obtain one at http://mozilla.org/MPL/2.0/. -;; -;; Copyright (c) UXBOX Labs SL - -(ns app.setup.initial-data - (:refer-clojure :exclude [load]) - (:require - [app.common.uuid :as uuid] - [app.config :as cf] - [app.db :as db] - [app.rpc.mutations.management :refer [duplicate-file]] - [app.rpc.mutations.projects :refer [create-project create-project-role]] - [app.rpc.queries.profile :as profile])) - -;; --- DUMP GENERATION - -(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)") - -(defn dump - ([system project-id] (dump system project-id nil)) - ([system project-id {:keys [skey project-name] - :or {project-name "Penpot Onboarding"}}] - (db/with-atomic [conn (:app.db/pool system)] - (let [skey (or skey (cf/get :initial-project-skey)) - files (db/exec! conn [sql:file project-id]) - flibs (db/exec! conn [sql:file-library-rel project-id]) - fmeds (db/exec! conn [sql:file-media-object project-id]) - data {:project-name project-name - :files files - :flibs flibs - :fmeds fmeds}] - - (db/delete! conn :server-prop {:id skey}) - (db/insert! conn :server-prop - {:id skey - :preload false - :content (db/tjson data)}) - skey)))) - - -;; --- DUMP LOADING - -(defn- retrieve-data - [conn skey] - (when-let [row (db/exec-one! conn ["select content from server_prop where id = ?" skey])] - (when-let [content (:content row)] - (when (db/pgobject? content) - (db/decode-transit-pgobject content))))) - -(defn load-initial-project! - ([conn profile] (load-initial-project! conn profile nil)) - ([conn profile opts] - (let [skey (or (:skey opts) (cf/get :initial-project-skey)) - data (retrieve-data conn skey)] - (when data - (let [index (reduce #(assoc %1 (:id %2) (uuid/next)) {} (:files data)) - project {:id (uuid/next) - :profile-id (:id profile) - :team-id (:default-team-id profile) - :name (:project-name data)}] - - (db/exec-one! conn ["SET CONSTRAINTS ALL DEFERRED"]) - - (create-project conn project) - (create-project-role conn {:project-id (:id project) - :profile-id (:id profile) - :role :owner}) - - (doseq [file (:files data)] - (let [flibs (filterv #(= (:id file) (:file-id %)) (:flibs data)) - fmeds (filterv #(= (:id file) (:file-id %)) (:fmeds data)) - - params {:profile-id (:id profile) - :project-id (:id project) - :file file - :index index - :flibs flibs - :fmeds fmeds} - - opts {:reset-shared-flag false}] - (duplicate-file conn params opts)))))))) - -(defn load - [system {:keys [email] :as opts}] - (db/with-atomic [conn (:app.db/pool system)] - (when-let [profile (some->> email - (profile/retrieve-profile-data-by-email conn) - (profile/populate-additional-data conn))] - (load-initial-project! conn profile opts) - true))) - diff --git a/backend/src/app/util/emails.clj b/backend/src/app/util/emails.clj index dca9b732ea..e0f42e49f4 100644 --- a/backend/src/app/util/emails.clj +++ b/backend/src/app/util/emails.clj @@ -178,7 +178,7 @@ ;; Template Email Building ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(def ^:private email-path "emails/%(id)s/%(lang)s.%(type)s") +(def ^:private email-path "app/emails/%(id)s/%(lang)s.%(type)s") (defn- render-email-template-part [type id context] diff --git a/backend/test/app/services_management_test.clj b/backend/test/app/services_management_test.clj index 2089537ea6..f738e52e75 100644 --- a/backend/test/app/services_management_test.clj +++ b/backend/test/app/services_management_test.clj @@ -19,6 +19,8 @@ (t/use-fixtures :once th/state-init) (t/use-fixtures :each th/database-reset) +;; TODO: migrate to commands + (t/deftest duplicate-file (let [storage (-> (:app.storage/storage th/*system*) (configure-storage-backend)) @@ -602,3 +604,31 @@ (t/is (= (:library-file-id item1) (:id file2)))) ))) + +(t/deftest clone-template + (let [prof (th/create-profile* 1 {:is-active true}) + data {::th/type :clone-template + :profile-id (:id prof) + :project-id (:default-project-id prof) + :template-id "test"} + + out (th/command! data)] + ;; (th/print-result! out) + + (t/is (nil? (:error out))) + (let [result (:result out)] + (t/is (set? result)) + (t/is (uuid? (first result))) + (t/is (= 1 (count result)))))) + +(t/deftest retrieve-list-of-buitin-templates + (let [prof (th/create-profile* 1 {:is-active true}) + data {::th/type :retrieve-list-of-builtin-templates + :profile-id (:id prof)} + out (th/command! data)] + ;; (th/print-result! out) + (t/is (nil? (:error out))) + (let [result (:result out)] + (t/is (vector? result)) + (t/is (= 1 (count result))) + (t/is (= "test" (:id (first result))))))) diff --git a/backend/test/app/test_files/template.penpot b/backend/test/app/test_files/template.penpot new file mode 100644 index 0000000000..1375c6d525 Binary files /dev/null and b/backend/test/app/test_files/template.penpot differ diff --git a/backend/test/app/test_helpers.clj b/backend/test/app/test_helpers.clj index 6768b2a3e2..ccdd5d6d37 100644 --- a/backend/test/app/test_helpers.clj +++ b/backend/test/app/test_helpers.clj @@ -52,11 +52,18 @@ (defn state-init [next] - (let [config (-> main/system-config + (let [templates [{:id "test" + :name "test" + :file-uri "test" + :thumbnail-uri "test" + :path (-> "app/test_files/template.penpot" io/resource fs/path)}] + + config (-> main/system-config (assoc-in [:app.msgbus/msgbus :redis-uri] (:redis-uri config)) (assoc-in [:app.db/pool :uri] (:database-uri config)) (assoc-in [:app.db/pool :username] (:database-username config)) (assoc-in [:app.db/pool :password] (:database-password config)) + (assoc-in [:app.rpc/methods :templates] templates) (dissoc :app.srepl/server :app.http/server :app.http/router @@ -66,6 +73,7 @@ :app.auth.oidc/gitlab-provider :app.auth.oidc/github-provider :app.auth.oidc/generic-provider + :app.setup/builtin-templates :app.auth.oidc/routes ;; :app.auth.ldap/provider :app.worker/executors-monitor diff --git a/frontend/src/app/main/data/dashboard.cljs b/frontend/src/app/main/data/dashboard.cljs index 33dce462a3..071cb4b3fe 100644 --- a/frontend/src/app/main/data/dashboard.cljs +++ b/frontend/src/app/main/data/dashboard.cljs @@ -568,8 +568,7 @@ new-name (str name " " (tr "dashboard.copy-suffix"))] - (->> (rp/mutation! :duplicate-project {:project-id id - :name new-name}) + (->> (rp/command! :duplicate-project {:project-id id :name new-name}) (rx/tap on-success) (rx/map project-duplicated) (rx/catch on-error)))))) @@ -589,8 +588,7 @@ :or {on-success identity on-error rx/throw}} (meta params)] - (->> (rp/mutation! :move-project {:project-id id - :team-id team-id}) + (->> (rp/command! :move-project {:project-id id :team-id team-id}) (rx/tap on-success) (rx/catch on-error)))))) @@ -771,8 +769,7 @@ new-name (str name " " (tr "dashboard.copy-suffix"))] - (->> (rp/mutation! :duplicate-file {:file-id id - :name new-name}) + (->> (rp/command! :duplicate-file {:file-id id :name new-name}) (rx/tap on-success) (rx/map file-created) (rx/catch on-error)))))) @@ -794,7 +791,7 @@ (let [{:keys [on-success on-error] :or {on-success identity on-error rx/throw}} (meta params)] - (->> (rp/mutation! :move-files {:ids ids :project-id project-id}) + (->> (rp/command! :move-files {:ids ids :project-id project-id}) (rx/tap on-success) (rx/catch on-error))))))