diff --git a/backend/src/app/rpc/mutations/media.clj b/backend/src/app/rpc/mutations/media.clj index 7032146ef..8f9075cf1 100644 --- a/backend/src/app/rpc/mutations/media.clj +++ b/backend/src/app/rpc/mutations/media.clj @@ -92,6 +92,16 @@ :content-type mtype :expired-at (dt/in-future {:minutes 30})})))) +;; NOTE: we use the `on conflict do update` instead of `do nothing` +;; because postgresql does not returns anything if no update is +;; performed, the `do update` does the trick. + +(def sql:create-file-media-object + "insert into file_media_object (id, file_id, is_local, name, media_id, thumbnail_id, width, height, mtype) + values (?, ?, ?, ?, ?, ?, ?, ?, ?) + on conflict (id) do update set created_at=file_media_object.created_at + returning *") + (defn create-file-media-object [{:keys [conn storage] :as cfg} {:keys [id file-id is-local name content] :as params}] (media/validate-media-type (:content-type content)) @@ -117,18 +127,15 @@ thumb (when thumb (sto/put-object storage {:content (sto/content (:data thumb) (:size thumb)) :content-type (:mtype thumb)}))] - (db/insert! conn :file-media-object - {:id (or id (uuid/next)) - :file-id file-id - :is-local is-local - :name name - :media-id (:id image) - :thumbnail-id (:id thumb) - :width (:width source-info) - :height (:height source-info) - :mtype source-mtype} - {:on-conflict-do-nothing true}))) + (db/exec-one! conn [sql:create-file-media-object + (or id (uuid/next)) + file-id is-local name + (:id image) + (:id thumb) + (:width source-info) + (:height source-info) + source-mtype]))) ;; --- Create File Media Object (from URL) diff --git a/backend/test/app/services_media_test.clj b/backend/test/app/services_media_test.clj index a0e9c9780..1e8bd1ffe 100644 --- a/backend/test/app/services_media_test.clj +++ b/backend/test/app/services_media_test.clj @@ -86,3 +86,48 @@ (t/is (= 312043 (:size mobj1))) (t/is (= 3887 (:size mobj2))))) )) + + +(t/deftest media-object-upload-idempotency + (let [prof (th/create-profile* 1) + proj (th/create-project* 1 {:profile-id (:id prof) + :team-id (:default-team-id prof)}) + file (th/create-file* 1 {:profile-id (:id prof) + :project-id (:default-project-id prof) + :is-shared false}) + mfile {:filename "sample.jpg" + :tempfile (th/tempfile "app/test_files/sample.jpg") + :content-type "image/jpeg" + :size 312043} + + params {::th/type :upload-file-media-object + :profile-id (:id prof) + :file-id (:id file) + :is-local true + :name "testfile" + :content mfile + :id (uuid/next)}] + + ;; First try + (let [{:keys [result error] :as out} (th/mutation! params)] + ;; (th/print-result! out) + (t/is (nil? error)) + (t/is (= (:id params) (:id result))) + (t/is (= (:file-id params) (:file-id result))) + (t/is (= 800 (:width result))) + (t/is (= 800 (:height result))) + (t/is (= "image/jpeg" (:mtype result))) + (t/is (uuid? (:media-id result))) + (t/is (uuid? (:thumbnail-id result)))) + + ;; Second try + (let [{:keys [result error] :as out} (th/mutation! params)] + ;; (th/print-result! out) + (t/is (nil? error)) + (t/is (= (:id params) (:id result))) + (t/is (= (:file-id params) (:file-id result))) + (t/is (= 800 (:width result))) + (t/is (= 800 (:height result))) + (t/is (= "image/jpeg" (:mtype result))) + (t/is (uuid? (:media-id result))) + (t/is (uuid? (:thumbnail-id result)))))) diff --git a/common/src/app/common/types/interactions.cljc b/common/src/app/common/types/interactions.cljc index 21dc6ba12..66ad8dea8 100644 --- a/common/src/app/common/types/interactions.cljc +++ b/common/src/app/common/types/interactions.cljc @@ -72,17 +72,17 @@ (s/keys :opt-un [::destination ::preserve-scroll])) (defmethod action-opts-spec :open-overlay [_] - (s/keys :req-un [::destination - ::overlay-position + (s/keys :req-un [::overlay-position ::overlay-pos-type] - :opt-un [::close-click-outside + :opt-un [::destination + ::close-click-outside ::background-overlay])) (defmethod action-opts-spec :toggle-overlay [_] - (s/keys :req-un [::destination - ::overlay-position + (s/keys :req-un [::overlay-position ::overlay-pos-type] - :opt-un [::close-click-outside + :opt-un [::destination + ::close-click-outside ::background-overlay])) (defmethod action-opts-spec :close-overlay [_]