diff --git a/backend/resources/migrations/0012-make-libraries-linked-to-a-file.sql b/backend/resources/migrations/0012-make-libraries-linked-to-a-file.sql new file mode 100644 index 000000000..398ca880c --- /dev/null +++ b/backend/resources/migrations/0012-make-libraries-linked-to-a-file.sql @@ -0,0 +1,29 @@ +-- ALTER TABLE color_library +-- DROP COLUMN team_id, +-- DROP COLUMN name, +-- ADD COLUMN file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE; +-- +-- CREATE INDEX color_library__file_id__idx +-- ON color_library(file_id); + +TRUNCATE TABLE color; +TRUNCATE TABLE color_library CASCADE; +TRUNCATE TABLE image; +TRUNCATE TABLE image_library CASCADE; +TRUNCATE TABLE icon; +TRUNCATE TABLE icon_library CASCADE; + +ALTER TABLE color + DROP COLUMN library_id, + ADD COLUMN file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE; + +CREATE INDEX color__file_id__idx + ON color(file_id); + +ALTER TABLE image + DROP COLUMN library_id, + ADD COLUMN file_id uuid NOT NULL REFERENCES file(id) ON DELETE CASCADE; + +CREATE INDEX image__file_id__idx + ON image(file_id); + diff --git a/backend/src/uxbox/images.clj b/backend/src/uxbox/images.clj index 3ae3fd03c..7b211debf 100644 --- a/backend/src/uxbox/images.clj +++ b/backend/src/uxbox/images.clj @@ -48,7 +48,7 @@ (s/def ::width integer?) (s/def ::height integer?) -(s/def ::format #{:jpeg :webp :png}) +(s/def ::format #{:jpeg :webp :png :svg}) (s/def ::quality #(< 0 % 101)) (s/def ::thumbnail-params @@ -62,21 +62,24 @@ (case format :png ".png" :jpeg ".jpg" - :webp ".webp")) + :webp ".webp" + :svg ".svg")) (defn format->mtype [format] (case format :png "image/png" :jpeg "image/jpeg" - :webp "image/webp")) + :webp "image/webp" + :svg "image/svg+xml")) (defn mtype->format [mtype] (case mtype - "image/jpeg" :jpeg - "image/webp" :webp - "image/png" :png + "image/png" :png + "image/jpeg" :jpeg + "image/webp" :webp + "image/svg+xml" :svg nil)) (defn- generic-process @@ -127,18 +130,21 @@ (defmethod process :info [{:keys [input] :as params}] (us/assert ::input input) - (let [{:keys [path mtype]} input - instance (Info. (str path)) - mtype' (.getProperty instance "Mime type")] - - (when (and (string? mtype) - (not= mtype mtype')) - (ex/raise :type :validation - :code :image-type-mismatch - :hint "Seems like you are uploading a file whose content does not match the extension.")) - {:width (.getImageWidth instance) - :height (.getImageHeight instance) - :mtype mtype'})) + (let [{:keys [path mtype]} input] + (if (= mtype "image/svg+xml") + {:width 100 + :height 100 + :mtype mtype} + (let [instance (Info. (str path)) + mtype' (.getProperty instance "Mime type")] + (when (and (string? mtype) + (not= mtype mtype')) + (ex/raise :type :validation + :code :image-type-mismatch + :hint "Seems like you are uploading a file whose content does not match the extension.")) + {:width (.getImageWidth instance) + :height (.getImageHeight instance) + :mtype mtype'})))) (defmethod process :default [{:keys [cmd] :as params}] @@ -164,13 +170,15 @@ (defn resolve-urls [row src dst] (s/assert map? row) - (let [src (if (vector? src) src [src]) - dst (if (vector? dst) dst [dst]) - value (get-in row src)] - (if (empty? value) - row - (let [url (ust/public-uri media/media-storage value)] - (assoc-in row dst (str url)))))) + (if (and src dst) + (let [src (if (vector? src) src [src]) + dst (if (vector? dst) dst [dst]) + value (get-in row src)] + (if (empty? value) + row + (let [url (ust/public-uri media/media-storage value)] + (assoc-in row dst (str url))))) + row)) (defn- resolve-uri [storage row src dst] diff --git a/backend/src/uxbox/media_loader.clj b/backend/src/uxbox/media_loader.clj index 988bffb66..e8809981e 100644 --- a/backend/src/uxbox/media_loader.clj +++ b/backend/src/uxbox/media_loader.clj @@ -29,6 +29,8 @@ [uxbox.util.blob :as blob] [uxbox.common.uuid :as uuid] [uxbox.util.data :as data] + [uxbox.services.mutations.projects :as projects] + [uxbox.services.mutations.files :as files] [uxbox.services.mutations.colors :as colors] [uxbox.services.mutations.icons :as icons] [uxbox.services.mutations.images :as images] @@ -49,120 +51,243 @@ (s/def ::path ::us/string) (s/def ::regex #(instance? java.util.regex.Pattern %)) -(s/def ::colors +;; (s/def ::colors +;; (s/* (s/cat :name ::us/string :color ::us/color))) +;; +;; (s/def ::import-item-media +;; (s/keys :req-un [::name ::path ::regex])) +;; +;; (s/def ::import-item-color +;; (s/keys :req-un [::name ::id ::colors])) + +(s/def ::import-images + (s/keys :req-un [::path ::regex])) + +(s/def ::import-color (s/* (s/cat :name ::us/string :color ::us/color))) -(s/def ::import-item-media - (s/keys :req-un [::name ::path ::regex])) +(s/def ::import-colors (s/coll-of ::import-color)) -(s/def ::import-item-color - (s/keys :req-un [::name ::id ::colors])) +(s/def ::import-library + (s/keys :req-un [::name] + :opt-un [::import-images ::import-colors])) (defn exit! ([] (exit! 0)) ([code] (System/exit code))) +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ;; Icons Libraries Importer +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; (defn- icon-library-exists? +;; [conn id] +;; (s/assert ::us/uuid id) +;; (let [row (db/get-by-id conn :icon-library id)] +;; (if row true false))) +;; +;; (defn- create-icons-library +;; [conn {:keys [name] :as item}] +;; (let [id (uuid/namespaced +icons-uuid-ns+ name)] +;; (log/info "Creating icons library:" name) +;; (icons/create-library conn {:team-id uuid/zero +;; :id id +;; :name name}))) +;; +;; (defn- create-icons-library-if-not-exists +;; [conn {:keys [name] :as item}] +;; (let [id (uuid/namespaced +icons-uuid-ns+ name)] +;; (when-not (icon-library-exists? conn id) +;; (create-icons-library conn item)) +;; id)) +;; +;; (defn- create-icon +;; [conn library-id icon-id localpath] +;; (s/assert fs/path? localpath) +;; (s/assert ::us/uuid library-id) +;; (s/assert ::us/uuid icon-id) +;; (let [filename (fs/name localpath) +;; extension (second (fs/split-ext filename)) +;; data (svg/parse localpath) +;; mdata (select-keys data [:width :height :view-box])] +;; +;; (log/info "Creating or updating icon" filename icon-id) +;; (icons/create-icon conn {:id icon-id +;; :library-id library-id +;; :name (:name data filename) +;; :content (:content data) +;; :metadata mdata}))) +;; +;; (defn- icon-exists? +;; [conn id] +;; (s/assert ::us/uuid id) +;; (let [row (db/get-by-id conn :icon id)] +;; (if row true false))) +;; +;; (defn- import-icon-if-not-exists +;; [conn library-id fpath] +;; (s/assert ::us/uuid library-id) +;; (s/assert fs/path? fpath) +;; (let [icon-id (uuid/namespaced +icons-uuid-ns+ (str library-id (fs/name fpath)))] +;; (when-not (icon-exists? conn icon-id) +;; (create-icon conn library-id icon-id fpath)) +;; icon-id)) +;; +;; (defn- import-icons +;; [conn library-id {:keys [path regex] :as item}] +;; (run! (fn [fpath] +;; (when (re-matches regex (str fpath)) +;; (import-icon-if-not-exists conn library-id fpath))) +;; (->> (fs/list-dir path) +;; (filter fs/regular-file?)))) +;; +;; (defn- process-icons-library +;; [conn basedir {:keys [path regex] :as item}] +;; (s/assert ::import-item-media item) +;; (let [library-id (create-icons-library-if-not-exists conn item)] +;; (->> (assoc item :path (fs/join basedir path)) +;; (import-icons conn library-id)))) +;; +;; +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ;; --- Images Libraries Importer +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; (defn- image-library-exists? +;; [conn id] +;; (s/assert ::us/uuid id) +;; (let [row (db/get-by-id conn :image-library id)] +;; (if row true false))) +;; +;; (defn- create-images-library +;; [conn {:keys [name] :as item}] +;; (let [id (uuid/namespaced +images-uuid-ns+ name)] +;; (log/info "Creating image library:" name) +;; (images/create-library conn {:id id +;; :team-id uuid/zero +;; :name name}))) +;; +;; (defn- create-images-library-if-not-exists +;; [conn {:keys [name] :as item}] +;; (let [id (uuid/namespaced +images-uuid-ns+ name)] +;; (when-not (image-library-exists? conn id) +;; (create-images-library conn item) +;; id))) +;; +;; (defn- create-image +;; [conn library-id image-id localpath] +;; (s/assert fs/path? localpath) +;; (s/assert ::us/uuid library-id) +;; (s/assert ::us/uuid image-id) +;; (let [filename (fs/name localpath) +;; extension (second (fs/split-ext filename)) +;; file (io/as-file localpath) +;; mtype (case extension +;; ".jpg" "image/jpeg" +;; ".png" "image/png" +;; ".webp" "image/webp")] +;; (log/info "Creating image" filename image-id) +;; (images/create-image conn {:content {:tempfile localpath +;; :filename filename +;; :content-type mtype +;; :size (.length file)} +;; :id image-id +;; :library-id library-id +;; :user uuid/zero +;; :name filename}))) +;; +;; (defn- image-exists? +;; [conn id] +;; (s/assert ::us/uuid id) +;; (let [row (db/get-by-id conn :image id)] +;; (if row true false))) +;; +;; (defn- import-image-if-not-exists +;; [conn library-id fpath] +;; (s/assert ::us/uuid library-id) +;; (s/assert fs/path? fpath) +;; (let [image-id (uuid/namespaced +images-uuid-ns+ (str library-id (fs/name fpath)))] +;; (when-not (image-exists? conn image-id) +;; (create-image conn library-id image-id fpath)) +;; image-id)) +;; +;; (defn- import-images +;; [conn library-id {:keys [path regex] :as item}] +;; (run! (fn [fpath] +;; (when (re-matches regex (str fpath)) +;; (import-image-if-not-exists conn library-id fpath))) +;; (->> (fs/list-dir path) +;; (filter fs/regular-file?)))) +;; +;; (defn- process-images-library +;; [conn basedir {:keys [path regex] :as item}] +;; (s/assert ::import-item-media item) +;; (let [library-id (create-images-library-if-not-exists conn item)] +;; (->> (assoc item :path (fs/join basedir path)) +;; (import-images conn library-id)))) +;; +;; +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; ;; Colors Libraries Importer +;; ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; +;; (defn- color-library-exists? +;; [conn id] +;; (s/assert ::us/uuid id) +;; (let [row (db/get-by-id conn :file id)] +;; (if row true false))) +;; +;; (defn- create-colors-library +;; [conn {:keys [name] :as item}] +;; (let [id (uuid/namespaced +colors-uuid-ns+ name)] +;; (log/info "Creating color library:" name) +;; (colors/create-library conn {:id id +;; :team-id uuid/zero +;; :name name}))) +;; +;; +;; (defn- create-colors-library-if-not-exists +;; [conn {:keys [name] :as item}] +;; (let [id (uuid/namespaced +colors-uuid-ns+ name)] +;; (when-not (color-library-exists? conn id) +;; (create-colors-library conn item)) +;; id)) +;; +;; (defn- create-color +;; [conn library-id name content] +;; (s/assert ::us/uuid library-id) +;; (s/assert ::us/color content) +;; (let [color-id (uuid/namespaced +colors-uuid-ns+ (str library-id content))] +;; (log/info "Creating color" color-id "-" name content) +;; (colors/create-color conn {:id color-id +;; :library-id library-id +;; :name name +;; :content content}) +;; color-id)) +;; +;; (defn- import-colors +;; [conn library-id {:keys [colors] :as item}] +;; (db/delete! conn :color {:library-id library-id}) +;; (run! (fn [[name content]] +;; (create-color conn library-id name content)) +;; (partition-all 2 colors))) +;; +;; (defn- process-colors-library +;; [conn {:keys [name id colors] :as item}] +;; (us/verify ::import-item-color item) +;; (let [library-id (create-colors-library-if-not-exists conn item)] +;; (import-colors conn library-id item))) + + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Icons Libraries Importer +;; Images Importer ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn- icon-library-exists? - [conn id] - (s/assert ::us/uuid id) - (let [row (db/get-by-id conn :icon-library id)] - (if row true false))) - -(defn- create-icons-library - [conn {:keys [name] :as item}] - (let [id (uuid/namespaced +icons-uuid-ns+ name)] - (log/info "Creating icons library:" name) - (icons/create-library conn {:team-id uuid/zero - :id id - :name name}))) - -(defn- create-icons-library-if-not-exists - [conn {:keys [name] :as item}] - (let [id (uuid/namespaced +icons-uuid-ns+ name)] - (when-not (icon-library-exists? conn id) - (create-icons-library conn item)) - id)) - -(defn- create-icon - [conn library-id icon-id localpath] - (s/assert fs/path? localpath) - (s/assert ::us/uuid library-id) - (s/assert ::us/uuid icon-id) - (let [filename (fs/name localpath) - extension (second (fs/split-ext filename)) - data (svg/parse localpath) - mdata (select-keys data [:width :height :view-box])] - - (log/info "Creating or updating icon" filename icon-id) - (icons/create-icon conn {:id icon-id - :library-id library-id - :name (:name data filename) - :content (:content data) - :metadata mdata}))) - -(defn- icon-exists? - [conn id] - (s/assert ::us/uuid id) - (let [row (db/get-by-id conn :icon id)] - (if row true false))) - -(defn- import-icon-if-not-exists - [conn library-id fpath] - (s/assert ::us/uuid library-id) - (s/assert fs/path? fpath) - (let [icon-id (uuid/namespaced +icons-uuid-ns+ (str library-id (fs/name fpath)))] - (when-not (icon-exists? conn icon-id) - (create-icon conn library-id icon-id fpath)) - icon-id)) - -(defn- import-icons - [conn library-id {:keys [path regex] :as item}] - (run! (fn [fpath] - (when (re-matches regex (str fpath)) - (import-icon-if-not-exists conn library-id fpath))) - (->> (fs/list-dir path) - (filter fs/regular-file?)))) - -(defn- process-icons-library - [conn basedir {:keys [path regex] :as item}] - (s/assert ::import-item-media item) - (let [library-id (create-icons-library-if-not-exists conn item)] - (->> (assoc item :path (fs/join basedir path)) - (import-icons conn library-id)))) - - -;; --- Images Libraries Importer - -(defn- image-library-exists? - [conn id] - (s/assert ::us/uuid id) - (let [row (db/get-by-id conn :image-library id)] - (if row true false))) - -(defn- create-images-library - [conn {:keys [name] :as item}] - (let [id (uuid/namespaced +images-uuid-ns+ name)] - (log/info "Creating image library:" name) - (images/create-library conn {:id id - :team-id uuid/zero - :name name}))) - -(defn- create-images-library-if-not-exists - [conn {:keys [name] :as item}] - (let [id (uuid/namespaced +images-uuid-ns+ name)] - (when-not (image-library-exists? conn id) - (create-images-library conn item) - id))) - (defn- create-image - [conn library-id image-id localpath] + [conn file-id image-id localpath] (s/assert fs/path? localpath) - (s/assert ::us/uuid library-id) + (s/assert ::us/uuid file-id) (s/assert ::us/uuid image-id) (let [filename (fs/name localpath) extension (second (fs/split-ext filename)) @@ -170,14 +295,15 @@ mtype (case extension ".jpg" "image/jpeg" ".png" "image/png" - ".webp" "image/webp")] + ".webp" "image/webp" + ".svg" "image/svg+xml")] (log/info "Creating image" filename image-id) (images/create-image conn {:content {:tempfile localpath :filename filename :content-type mtype :size (.length file)} :id image-id - :library-id library-id + :file-id file-id :user uuid/zero :name filename}))) @@ -188,85 +314,100 @@ (if row true false))) (defn- import-image-if-not-exists - [conn library-id fpath] - (s/assert ::us/uuid library-id) + [conn file-id fpath] + (s/assert ::us/uuid file-id) (s/assert fs/path? fpath) - (let [image-id (uuid/namespaced +images-uuid-ns+ (str library-id (fs/name fpath)))] + (let [image-id (uuid/namespaced +images-uuid-ns+ (str file-id (fs/name fpath)))] (when-not (image-exists? conn image-id) - (create-image conn library-id image-id fpath)) + (create-image conn file-id image-id fpath)) image-id)) (defn- import-images - [conn library-id {:keys [path regex] :as item}] + [conn file-id {:keys [path regex] :as images}] (run! (fn [fpath] (when (re-matches regex (str fpath)) - (import-image-if-not-exists conn library-id fpath))) + (import-image-if-not-exists conn file-id fpath))) (->> (fs/list-dir path) (filter fs/regular-file?)))) -(defn- process-images-library - [conn basedir {:keys [path regex] :as item}] - (s/assert ::import-item-media item) - (let [library-id (create-images-library-if-not-exists conn item)] - (->> (assoc item :path (fs/join basedir path)) - (import-images conn library-id)))) - - ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -;; Colors Libraries Importer +;; Colors Importer ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; -(defn- color-library-exists? - [conn id] - (s/assert ::us/uuid id) - (let [row (db/get-by-id conn :color-library id)] - (if row true false))) - -(defn- create-colors-library - [conn {:keys [name] :as item}] - (let [id (uuid/namespaced +colors-uuid-ns+ name)] - (log/info "Creating color library:" name) - (colors/create-library conn {:id id - :team-id uuid/zero - :name name}))) - - -(defn- create-colors-library-if-not-exists - [conn {:keys [name] :as item}] - (let [id (uuid/namespaced +colors-uuid-ns+ name)] - (when-not (color-library-exists? conn id) - (create-colors-library conn item)) - id)) - (defn- create-color - [conn library-id name content] - (s/assert ::us/uuid library-id) + [conn file-id name content] + (s/assert ::us/uuid file-id) (s/assert ::us/color content) - (let [color-id (uuid/namespaced +colors-uuid-ns+ (str library-id content))] + (let [color-id (uuid/namespaced +colors-uuid-ns+ (str file-id content))] (log/info "Creating color" color-id "-" name content) (colors/create-color conn {:id color-id - :library-id library-id + :file-id file-id :name name :content content}) color-id)) (defn- import-colors - [conn library-id {:keys [colors] :as item}] - (db/delete! conn :color {:library-id library-id}) + [conn file-id colors] + (db/delete! conn :color {:file-id file-id}) (run! (fn [[name content]] - (create-color conn library-id name content)) + (create-color conn file-id name content)) (partition-all 2 colors))) -(defn- process-colors-library - [conn {:keys [name id colors] :as item}] - (us/verify ::import-item-color item) - (let [library-id (create-colors-library-if-not-exists conn item)] - (import-colors conn library-id item))) + +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +;; Libraries Importer +;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; + +(defn- library-file-exists? + [conn id] + (s/assert ::us/uuid id) + (let [row (db/get-by-id conn :file id)] + (if row true false))) + +(defn- create-library-file-if-not-exists + [conn project-id {:keys [name] :as library-file}] + (let [id (uuid/namespaced +colors-uuid-ns+ name)] + (when-not (library-file-exists? conn id) + (log/info "Creating library-file:" name) + (files/create-file conn {:id id + :profile-id uuid/zero + :project-id project-id + :name name}) + (files/create-page conn {:file-id id})) + id)) + +(defn- process-library + [conn basedir project-id {:keys [name images colors] :as library}] + (us/verify ::import-library library) + (let [library-file-id (create-library-file-if-not-exists conn project-id library)] + (when images + (->> (assoc images :path (fs/join basedir (:path images))) + (import-images conn library-file-id))) + (when colors + (import-colors conn library-file-id colors)))) + ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Entry Point ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; +(defn- project-exists? + [conn id] + (s/assert ::us/uuid id) + (let [row (db/get-by-id conn :project id)] + (if row true false))) + +(defn- create-project-if-not-exists + [conn {:keys [name] :as project}] + (let [id (uuid/namespaced +colors-uuid-ns+ name)] + (when-not (project-exists? conn id) + (log/info "Creating project" name) + (projects/create-project conn {:id id + :team-id uuid/zero + :name name + :default? false})) + id)) + (defn- validate-path [path] (when-not path @@ -295,20 +436,21 @@ [] (mount/stop)) -(defn- importer - [conn basedir data] - (let [images (:images data) - icons (:icons data) - colors (:colors data)] - (run! #(process-images-library conn basedir %) images) - (run! #(process-icons-library conn basedir %) icons) - (run! #(process-colors-library conn %) colors))) +;; (defn- importer +;; [conn basedir data] +;; (let [images (:images data) +;; icons (:icons data) +;; colors (:colors data)] +;; (run! #(process-images-library conn basedir %) images) +;; (run! #(process-icons-library conn basedir %) icons) +;; (run! #(process-colors-library conn %) colors))) (defn run [path] - (let [[basedir data] (read-file path)] + (let [[basedir libraries] (read-file path)] (db/with-atomic [conn db/pool] - (importer conn basedir data)))) + (let [project-id (create-project-if-not-exists conn {:name "Media loader"})] + (run! #(process-library conn basedir project-id %) libraries))))) (defn -main [& [path]] diff --git a/backend/src/uxbox/migrations.clj b/backend/src/uxbox/migrations.clj index 1fdf5c5fa..781b27db4 100644 --- a/backend/src/uxbox/migrations.clj +++ b/backend/src/uxbox/migrations.clj @@ -59,7 +59,11 @@ {:desc "Add session_id field to page_change table" :name "0011-add-session-id-field-to-page-change-table" - :fn (mg/resource "migrations/0011-add-session-id-field-to-page-change-table.sql")}]}) + :fn (mg/resource "migrations/0011-add-session-id-field-to-page-change-table.sql")} + + {:desc "Make libraries linked to a file" + :name "0012-make-libraries-linked-to-a-file" + :fn (mg/resource "migrations/0012-make-libraries-linked-to-a-file.sql")}]}) ;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;; ;; Entry point diff --git a/backend/src/uxbox/services/mutations/colors.clj b/backend/src/uxbox/services/mutations/colors.clj index 82428854f..2d5329462 100644 --- a/backend/src/uxbox/services/mutations/colors.clj +++ b/backend/src/uxbox/services/mutations/colors.clj @@ -119,26 +119,27 @@ (declare create-color) (s/def ::create-color - (s/keys :req-un [::profile-id ::name ::content ::library-id] + (s/keys :req-un [::profile-id ::name ::content ::file-id] :opt-un [::id])) (sm/defmutation ::create-color - [{:keys [profile-id library-id] :as params}] + [{:keys [profile-id file-id] :as params}] (db/with-atomic [conn db/pool] - (let [lib (select-library-for-update conn library-id)] - (teams/check-edition-permissions! conn profile-id (:team-id lib)) - (create-color conn params)))) + (create-color conn params))) + ;; (let [lib (select-library-for-update conn library-id)] + ;; (teams/check-edition-permissions! conn profile-id (:team-id lib)) + ;; (create-color conn params)))) (def ^:private sql:create-color - "insert into color (id, name, library_id, content) + "insert into color (id, name, file_id, content) values ($1, $2, $3, $4) returning *") (defn create-color - [conn {:keys [id name library-id content]}] + [conn {:keys [id name file-id content]}] (let [id (or id (uuid/next))] (db/insert! conn :color {:id id :name name - :library-id library-id + :file-id file-id :content content}))) diff --git a/backend/src/uxbox/services/mutations/files.clj b/backend/src/uxbox/services/mutations/files.clj index 272427d80..cd076c803 100644 --- a/backend/src/uxbox/services/mutations/files.clj +++ b/backend/src/uxbox/services/mutations/files.clj @@ -62,7 +62,7 @@ :is-admin true :can-edit true})) -(defn- create-file +(defn create-file [conn {:keys [id profile-id name project-id] :as params}] (let [id (or id (uuid/next)) file (db/insert! conn :file {:id id :project-id project-id :name name})] @@ -70,7 +70,7 @@ (create-file-profile conn)) file)) -(defn- create-page +(defn create-page [conn {:keys [file-id] :as params}] (let [id (uuid/next)] (db/insert! conn :page diff --git a/backend/src/uxbox/services/mutations/images.clj b/backend/src/uxbox/services/mutations/images.clj index af5941190..849b6e935 100644 --- a/backend/src/uxbox/services/mutations/images.clj +++ b/backend/src/uxbox/services/mutations/images.clj @@ -33,7 +33,7 @@ (s/def ::id ::us/uuid) (s/def ::name ::us/string) (s/def ::profile-id ::us/uuid) -(s/def ::library-id ::us/uuid) +(s/def ::file-id ::us/uuid) (s/def ::team-id ::us/uuid) (s/def ::url ::us/url) @@ -112,7 +112,7 @@ (declare persist-image-thumbnail-on-fs) (def valid-image-types? - #{"image/jpeg", "image/png", "image/webp"}) + #{"image/jpeg", "image/png", "image/webp", "image/svg+xml"}) (s/def :uxbox$upload/filename ::us/string) (s/def :uxbox$upload/size ::us/integer) @@ -128,17 +128,17 @@ (s/def ::content ::upload) (s/def ::add-image-from-url - (s/keys :req-un [::profile-id ::library-id ::url] + (s/keys :req-un [::profile-id ::file-id ::url] :opt-un [::id])) (s/def ::upload-image - (s/keys :req-un [::profile-id ::library-id ::name ::content] + (s/keys :req-un [::profile-id ::file-id ::name ::content] :opt-un [::id])) (sm/defmutation ::add-image-from-url - [{:keys [profile-id library-id url] :as params}] + [{:keys [profile-id file-id url] :as params}] (db/with-atomic [conn db/pool] - (let [lib (select-library-for-update conn library-id)] + (let [lib (select-library-for-update conn file-id)] (teams/check-edition-permissions! conn profile-id (:team-id lib)) (let [content (images/download-image url) params' (merge params {:content content @@ -146,14 +146,14 @@ (create-image conn params'))))) (sm/defmutation ::upload-image - [{:keys [profile-id library-id] :as params}] + [{:keys [profile-id file-id] :as params}] (db/with-atomic [conn db/pool] - (let [lib (select-library-for-update conn library-id)] + (let [lib (select-library-for-update conn file-id)] (teams/check-edition-permissions! conn profile-id (:team-id lib)) (create-image conn params)))) (defn create-image - [conn {:keys [id content library-id name]}] + [conn {:keys [id content file-id name]}] (when-not (valid-image-types? (:content-type content)) (ex/raise :type :validation :code :image-type-not-allowed @@ -165,11 +165,15 @@ opts (assoc thumbnail-options :input {:mtype (:mtype info) :path path}) - thumb (persist-image-thumbnail-on-fs opts)] + thumb (if-not (= (:mtype info) "image/svg+xml") + (persist-image-thumbnail-on-fs opts) + (assoc info + :path path + :quality 0))] (-> (db/insert! conn :image {:id (or id (uuid/next)) - :library-id library-id + :file-id file-id :name name :path (str path) :width (:width info) diff --git a/backend/src/uxbox/services/queries/colors.clj b/backend/src/uxbox/services/queries/colors.clj index e03c3276c..965e091b2 100644 --- a/backend/src/uxbox/services/queries/colors.clj +++ b/backend/src/uxbox/services/queries/colors.clj @@ -28,9 +28,10 @@ (s/def ::id ::us/uuid) (s/def ::profile-id ::us/uuid) (s/def ::team-id ::us/uuid) +(s/def ::file-id ::us/uuid) (s/def ::library-id (s/nilable ::us/uuid)) -;; --- Query: Colors Librarys +;; --- Query: Colors Libraries (def ^:private sql:libraries "select lib.*, @@ -78,31 +79,31 @@ (ex/raise :type :not-found)) row)) -;; --- Query: Colors (by library) +;; --- Query: Colors (by file) (declare retrieve-colors) (s/def ::colors - (s/keys :req-un [::profile-id ::library-id])) + (s/keys :req-un [::profile-id ::file-id])) (sq/defquery ::colors - [{:keys [profile-id library-id] :as params}] + [{:keys [profile-id file-id] :as params}] (db/with-atomic [conn db/pool] - (let [lib (retrieve-library conn library-id)] - (teams/check-read-permissions! conn profile-id (:team-id lib)) - (retrieve-colors conn library-id)))) + (retrieve-colors conn file-id))) + ;; (let [lib (retrieve-library conn library-id)] + ;; (teams/check-read-permissions! conn profile-id (:team-id lib)) + ;; (retrieve-colors conn library-id)))) (def ^:private sql:colors - "select color.* - from color as color - inner join color_library as lib on (lib.id = color.library_id) + "select * + from color where color.deleted_at is null - and color.library_id = ? + and color.file_id = ? order by created_at desc") (defn- retrieve-colors - [conn library-id] - (db/exec! conn [sql:colors library-id])) + [conn file-id] + (db/exec! conn [sql:colors file-id])) ;; --- Query: Color (by ID) diff --git a/backend/src/uxbox/services/queries/files.clj b/backend/src/uxbox/services/queries/files.clj index 40c7a74a7..3991dc4c6 100644 --- a/backend/src/uxbox/services/queries/files.clj +++ b/backend/src/uxbox/services/queries/files.clj @@ -53,6 +53,11 @@ and (ppr.is_admin = true or ppr.is_owner = true or ppr.can_edit = true) + union + select p.* + from project as p + where p.team_id = uuid_nil() + and p.deleted_at is null ) select distinct file.*, @@ -80,23 +85,52 @@ (mapv decode-row rows))) -;; --- Query: Draft Files +;; --- Query: Project Files (def ^:private sql:files - "select distinct + "with projects as ( + select p.* + from project as p + inner join team_profile_rel as tpr on (tpr.team_id = p.team_id) + where tpr.profile_id = ? + and p.deleted_at is null + and (tpr.is_admin = true or + tpr.is_owner = true or + tpr.can_edit = true) + union + select p.* + from project as p + inner join project_profile_rel as ppr on (ppr.project_id = p.id) + where ppr.profile_id = ? + and p.deleted_at is null + and (ppr.is_admin = true or + ppr.is_owner = true or + ppr.can_edit = true) + union + select p.* + from project as p + where p.team_id = uuid_nil() + and p.deleted_at is null + ) + select distinct f.*, array_agg(pg.id) over pages_w as pages, first_value(pg.data) over pages_w as data from file as f - inner join file_profile_rel as fp_r on (fp_r.file_id = f.id) left join page as pg on (f.id = pg.file_id) - where fp_r.profile_id = ? - and f.project_id = ? + where f.project_id = ? + and (exists (select * + from file_profile_rel as fp_r + where fp_r.profile_id = ? + and fp_r.file_id = f.id + and (fp_r.is_admin = true or + fp_r.is_owner = true or + fp_r.can_edit = true)) + or exists (select * + from projects as p + where p.id = f.project_id)) and f.deleted_at is null and pg.deleted_at is null - and (fp_r.is_admin = true or - fp_r.is_owner = true or - fp_r.can_edit = true) window pages_w as (partition by f.id order by pg.ordering range between unbounded preceding and unbounded following) @@ -108,7 +142,9 @@ (sq/defquery ::files [{:keys [profile-id project-id] :as params}] - (->> (db/exec! db/pool [sql:files profile-id project-id]) + (->> (db/exec! db/pool [sql:files + profile-id profile-id + project-id profile-id]) (mapv decode-row))) ;; --- Query: File Permissions @@ -136,7 +172,12 @@ from project_profile_rel as ppr inner join file as f on (f.project_id = ppr.project_id) where f.id = ? - and ppr.profile_id = ?;") + and ppr.profile_id = ? + union all + select true, true, true + from file as f + inner join project as p on (f.project_id = p.id) + and p.team_id = uuid_nil();") (defn check-edition-permissions! [conn profile-id file-id] diff --git a/backend/src/uxbox/services/queries/images.clj b/backend/src/uxbox/services/queries/images.clj index 7563138d9..4653819d5 100644 --- a/backend/src/uxbox/services/queries/images.clj +++ b/backend/src/uxbox/services/queries/images.clj @@ -21,7 +21,7 @@ (s/def ::name ::us/string) (s/def ::profile-id ::us/uuid) (s/def ::team-id ::us/uuid) -(s/def ::library-id ::us/uuid) +(s/def ::file-id ::us/uuid) ;; --- Query: Image Librarys @@ -77,32 +77,34 @@ (declare retrieve-images) (s/def ::images - (s/keys :req-un [::profile-id ::library-id])) + (s/keys :req-un [::profile-id ::file-id])) ;; TODO: check if we can resolve url with transducer for reduce ;; garbage generation for each request (sq/defquery ::images - [{:keys [profile-id library-id] :as params}] + [{:keys [profile-id file-id] :as params}] (db/with-atomic [conn db/pool] - (let [lib (retrieve-library conn library-id)] - (teams/check-read-permissions! conn profile-id (:team-id lib)) - (->> (retrieve-images conn library-id) - (mapv #(images/resolve-urls % :path :uri)) - (mapv #(images/resolve-urls % :thumb-path :thumb-uri)))))) + (->> (retrieve-images conn file-id) + (mapv #(images/resolve-urls % :path :uri)) + (mapv #(images/resolve-urls % :thumb-path :thumb-uri))))) + ;; (let [lib (retrieve-library conn file-id)] + ;; (teams/check-read-permissions! conn profile-id (:team-id lib)) + ;; (->> (retrieve-images conn file-id) + ;; (mapv #(images/resolve-urls % :path :uri)) + ;; (mapv #(images/resolve-urls % :thumb-path :thumb-uri)))))) (def ^:private sql:images - "select img.* + "select * from image as img - inner join image_library as lib on (lib.id = img.library_id) where img.deleted_at is null - and img.library_id = ? + and img.file_id = ? order by created_at desc") (defn- retrieve-images - [conn library-id] - (db/exec! conn [sql:images library-id])) + [conn file-id] + (db/exec! conn [sql:images file-id])) @@ -125,9 +127,9 @@ (def ^:private sql:single-image "select img.*, - lib.team_id as team_id + file.team_id as team_id from image as img - inner join image_library as lib on (lib.id = img.library_id) + inner join file on (file.id = img.file_id) where img.deleted_at is null and img.id = ? order by created_at desc") diff --git a/backend/src/uxbox/services/queries/projects.clj b/backend/src/uxbox/services/queries/projects.clj index 6636c4b92..d7b254cde 100644 --- a/backend/src/uxbox/services/queries/projects.clj +++ b/backend/src/uxbox/services/queries/projects.clj @@ -48,10 +48,19 @@ and (ppr.is_admin = true or ppr.is_owner = true or ppr.can_edit = true) + union + select p.*, + (select count(*) from file as f + where f.project_id = p.id + and deleted_at is null) + from project as p + where p.team_id = uuid_nil() + and p.deleted_at is null ) select * from projects where team_id = ? + or team_id = uuid_nil() order by modified_at desc") (def ^:private sql:project-by-id diff --git a/frontend/resources/styles/main/partials/sidebar-assets.scss b/frontend/resources/styles/main/partials/sidebar-assets.scss index 7624e8aef..6196864f0 100644 --- a/frontend/resources/styles/main/partials/sidebar-assets.scss +++ b/frontend/resources/styles/main/partials/sidebar-assets.scss @@ -134,6 +134,31 @@ } } + .group-list { + overflow-y: scroll; + } + + .group-list-item { + display: flex; + align-items: center; + margin-top: $x-small; + font-size: $fs11; + color: $color-white; + + & .color-block { + width: 20px; + height: 20px; + border-radius: 10px; + margin-right: $x-small; + } + + & span { + margin-left: $x-small; + color: $color-gray-30; + text-transform: uppercase; + } + } + .context-menu { position: absolute; top: 10px; diff --git a/frontend/src/uxbox/main/data/workspace.cljs b/frontend/src/uxbox/main/data/workspace.cljs index 176d8e00e..84416ca49 100644 --- a/frontend/src/uxbox/main/data/workspace.cljs +++ b/frontend/src/uxbox/main/data/workspace.cljs @@ -75,10 +75,9 @@ (s/def ::layout-flags (s/coll-of ::layout-flag)) (def default-layout - #{;; :sitemap - ;; :sitemap-pages - ;; :layers - :assets + #{:sitemap + :sitemap-pages + :layers :element-options :rules :display-grid @@ -1441,6 +1440,7 @@ (def add-image-from-url dwp/add-image-from-url) (def upload-image dwp/upload-image) (def delete-file-image dwp/delete-file-image) +(def fetch-colors dwp/fetch-colors) (def rename-page dwp/rename-page) (def delete-page dwp/delete-page) (def create-empty-page dwp/create-empty-page) diff --git a/frontend/src/uxbox/main/data/workspace/persistence.cljs b/frontend/src/uxbox/main/data/workspace/persistence.cljs index 39a6e60b8..aaa00c970 100644 --- a/frontend/src/uxbox/main/data/workspace/persistence.cljs +++ b/frontend/src/uxbox/main/data/workspace/persistence.cljs @@ -289,7 +289,7 @@ (ptk/reify ::fetch-images ptk/WatchEvent (watch [_ state stream] - (->> (rp/query :file-images {:file-id file-id}) + (->> (rp/query :images {:file-id file-id}) (rx/map images-fetched))))) (defn images-fetched @@ -300,6 +300,26 @@ (let [images (d/index-by :id images)] (assoc state :workspace-images images))))) +;; --- Fetch Workspace Colors + +(declare colors-fetched) + +(defn fetch-colors + [file-id] + (ptk/reify ::fetch-colors + ptk/WatchEvent + (watch [_ state stream] + (->> (rp/query :colors {:file-id file-id}) + (rx/map colors-fetched))))) + +(defn colors-fetched + [colors] + (ptk/reify ::colors-fetched + ptk/UpdateEvent + (update [_ state] + (let [colors (d/index-by :id colors)] + (assoc state :workspace-colors colors))))) + ;; --- Upload Image diff --git a/frontend/src/uxbox/main/refs.cljs b/frontend/src/uxbox/main/refs.cljs index 23a9394f2..edff25f5b 100644 --- a/frontend/src/uxbox/main/refs.cljs +++ b/frontend/src/uxbox/main/refs.cljs @@ -60,6 +60,9 @@ (def workspace-images (l/derived :workspace-images st/state)) +(def workspace-colors + (l/derived :workspace-colors st/state)) + (def workspace-users (l/derived :workspace-users st/state)) diff --git a/frontend/src/uxbox/main/ui/workspace/sidebar/assets.cljs b/frontend/src/uxbox/main/ui/workspace/sidebar/assets.cljs index bcbef1d51..02925c80f 100644 --- a/frontend/src/uxbox/main/ui/workspace/sidebar/assets.cljs +++ b/frontend/src/uxbox/main/ui/workspace/sidebar/assets.cljs @@ -89,10 +89,24 @@ [:div.asset-group [:div.group-title (tr "workspace.assets.colors") - [:div.group-button {:on-click add-color} i/plus]]])) + [:span (str "\u00A0(") (count colors) ")"] ;; Unicode 00A0 is non-breaking space + [:div.group-button {:on-click add-color} i/plus]] + [:div.group-list + (for [color (sort-by :name colors)] + [:div.group-list-item {:key (:name color) + :on-context-menu #(println "context")} + [:div.color-block {:style {:background-color (:content color)}}] + (:name color) + (when-not (= (:name color) (:content color)) + [:span (:content color)])])]])) (mf/defc library-toolbox - [{:keys [library-id images initial-open? search-term box-filter] :as props}] + [{:keys [library-id + images + colors + initial-open? + search-term + box-filter] :as props}] (let [open? (mf/use-state initial-open?) toggle-open #(swap! open? not)] [:div.tool-window @@ -107,13 +121,14 @@ (when (or (= box-filter :all) (= box-filter :graphics)) [:& graphics-box {:library-id library-id :images images}]) (when (or (= box-filter :all) (= box-filter :colors)) - [:& colors-box {:colors {}}])])])) + [:& colors-box {:colors colors}])])])) (mf/defc assets-toolbox [] (let [team-id (-> refs/workspace-project mf/deref :team-id) file-id (-> refs/workspace-file mf/deref :id) file-images (mf/deref refs/workspace-images) + file-colors (mf/deref refs/workspace-colors) state (mf/use-state {:search-term "" :box-filter :all}) @@ -121,6 +136,9 @@ filtered-images (filter #(matches-search (:name %) (:search-term @state)) (vals file-images)) + filtered-colors (filter #(matches-search (:name %) (:search-term @state)) + (vals file-colors)) + on-search-term-change (fn [event] (let [value (-> (dom/get-target event) (dom/get-value))] @@ -135,7 +153,8 @@ (mf/use-effect (mf/deps file-id) #(when file-id - (st/emit! (dw/fetch-images file-id)))) + (st/emit! (dw/fetch-images file-id)) + (st/emit! (dw/fetch-colors file-id)))) [:div.assets-bar @@ -158,6 +177,7 @@ [:& library-toolbox {:library-id file-id :images filtered-images + :colors filtered-colors :initial-open? true :search-term (:search-term @state) :box-filter (:box-filter @state)}]])) diff --git a/sample_media/config.edn b/sample_media/config.edn index 082c93749..c5512e976 100644 --- a/sample_media/config.edn +++ b/sample_media/config.edn @@ -1,54 +1,58 @@ -{:icons - [{:name "Material Design (Action)" - :path "./icons/material-action" - :regex #"^.*_48px\.svg$"} +[ + ;; Icons + {:name "Material Design (Action)" + :images {:path "./icons/material-action" + :regex #"^.*_48px\.svg$"}} {:name "Material Design (Alert)" - :path "./icons/material-alert" - :regex #"^.*_48px\.svg$"} + :images {:path "./icons/material-alert" + :regex #"^.*_48px\.svg$"}} {:name "Material Design (Av)" - :path "./icons/material-av" - :regex #"^.*_48px\.svg$"} + :images {:path "./icons/material-av" + :regex #"^.*_48px\.svg$"}} {:name "Material Design (Content)" - :path "./icons/material-content" - :regex #"^.*_48px\.svg$"} + :images {:path "./icons/material-content" + :regex #"^.*_48px\.svg$"}} {:name "Material Design (Device)" - :path "./icons/material-device" - :regex #"^.*_48px\.svg$"} + :images {:path "./icons/material-device" + :regex #"^.*_48px\.svg$"}} {:name "Material Design (Editor)" - :path "./icons/material-editor" - :regex #"^.*_48px\.svg$"} + :images {:path "./icons/material-editor" + :regex #"^.*_48px\.svg$"}} {:name "Material Design (File)" - :path "./icons/material-file" - :regex #"^.*_48px\.svg$"} + :images {:path "./icons/material-file" + :regex #"^.*_48px\.svg$"}} {:name "Material Design (Hardware)" - :path "./icons/material-hardware" - :regex #"^.*_48px\.svg$"} + :images {:path "./icons/material-hardware" + :regex #"^.*_48px\.svg$"}} {:name "Material Design (Image)" - :path "./icons/material-image" - :regex #"^.*_48px\.svg$"} + :images {:path "./icons/material-image" + :regex #"^.*_48px\.svg$"}} {:name "Material Design (Maps)" - :path "./icons/material-maps" - :regex #"^.*_48px\.svg$"} + :images {:path "./icons/material-maps" + :regex #"^.*_48px\.svg$"}} {:name "Material Design (Navigation)" - :path "./icons/material-navigation" - :regex #"^.*_48px\.svg$"} + :images {:path "./icons/material-navigation" + :regex #"^.*_48px\.svg$"}} {:name "Material Design (Notification)" - :path "./icons/material-notification" - :regex #"^.*_48px\.svg$"} + :images {:path "./icons/material-notification" + :regex #"^.*_48px\.svg$"}} {:name "Material Design (Places)" - :path "./icons/material-places" - :regex #"^.*_48px\.svg$"} + :images {:path "./icons/material-places" + :regex #"^.*_48px\.svg$"}} {:name "Material Design (Social)" - :path "./icons/material-social" - :regex #"^.*_48px\.svg$"} + :images {:path "./icons/material-social" + :regex #"^.*_48px\.svg$"}} {:name "Material Design (Toggle)" - :path "./icons/material-toggle" - :regex #"^.*_48px\.svg$"} - ] + :images {:path "./icons/material-toggle" + :regex #"^.*_48px\.svg$"}} - :colors - [{:name "Flat design" - :id #uuid "00000000-0000-0000-0000-00000000001" + ;; Images + {:name "Unsplash" + :images {:path "./images/unsplash" + :regex #"^.*\.jpg$"}} + + ;; Colors + {:name "Flat design" :colors ["turquoise-50" "#e8f8f5" "turquoise-100" "#d1f2eb" "turquoise-200" "#a3e4d7" @@ -250,9 +254,7 @@ "asbestos-800" "#515a5a" "asbestos-900" "#424949"]} - {:name "Material design" - :id #uuid "00000000-0000-0000-0000-000000000020" :colors ["red-50" "#ffebee" "red-100" "#ffcdd2" "red-200" "#ef9a9a" @@ -509,5 +511,4 @@ "blue-grey-900" "#263238" "white" "#ffffff" "black" "#000000"]} - ]} - +] diff --git a/sample_media/images/unsplash/anna-pelzer.jpg b/sample_media/images/unsplash/anna-pelzer.jpg new file mode 100644 index 000000000..77a9a0eef Binary files /dev/null and b/sample_media/images/unsplash/anna-pelzer.jpg differ diff --git a/sample_media/images/unsplash/bruna-branco.jpg b/sample_media/images/unsplash/bruna-branco.jpg new file mode 100644 index 000000000..08fb25864 Binary files /dev/null and b/sample_media/images/unsplash/bruna-branco.jpg differ diff --git a/sample_media/images/unsplash/cayla1.jpg b/sample_media/images/unsplash/cayla1.jpg new file mode 100644 index 000000000..9a84f0b98 Binary files /dev/null and b/sample_media/images/unsplash/cayla1.jpg differ diff --git a/sample_media/images/unsplash/charles-deluvio.jpg b/sample_media/images/unsplash/charles-deluvio.jpg new file mode 100644 index 000000000..e763158d8 Binary files /dev/null and b/sample_media/images/unsplash/charles-deluvio.jpg differ diff --git a/sample_media/images/unsplash/dan-gold.jpg b/sample_media/images/unsplash/dan-gold.jpg new file mode 100644 index 000000000..0ad02f178 Binary files /dev/null and b/sample_media/images/unsplash/dan-gold.jpg differ diff --git a/sample_media/images/unsplash/dose-juice.jpg b/sample_media/images/unsplash/dose-juice.jpg new file mode 100644 index 000000000..baf50fbc8 Binary files /dev/null and b/sample_media/images/unsplash/dose-juice.jpg differ