diff --git a/README.md b/README.md index adf8c98..0fead78 100644 --- a/README.md +++ b/README.md @@ -81,8 +81,22 @@ An example using it within an Om component: ) ``` +The `uploaded` channel will return a map with the `:file` object that +was uploaded and a `:response` map that contains the `:location`, +`:bucket`, `:key`, and `:etag` of the file in S3. + ## Changes +#### Not Released + +- Added the `:key-fn` option `s3-beam.handler/sign-upload` to generate + custom S3 keys from `file-name` and `mime-type`. +- **Breaking Change**: the `uploaded` channel now returns a map with + the `:file` that was uploaded and the S3 `:response` map with itself + contains all the information necessary to locate the file in S3: + `:location`, `:key`, `:bucket`, and `:etag` instead of just the + location as a string. + #### 0.3.0 - Allow customization of server-side endpoint ([1cb9b27](https://github.com/martinklepsch/s3-beam/commit/1cb9b2703691e172e275a95490b3fc8209dfa409)) @@ -91,7 +105,7 @@ An example using it within an Om component: #### 0.2.0 -- Allow passing of `aws-zone` parameter to `s3-sign` handler function ([b880736](https://github.com/martinklepsch/s3-beam/commit/b88073646b7c92b5493a168ce25d27feaa130c9e)) +- Allow passing of `aws-zone` parameter to `s3-sign` handler function. ([b880736](https://github.com/martinklepsch/s3-beam/commit/b88073646b7c92b5493a168ce25d27feaa130c9e)) ## Contributing diff --git a/src/clj/s3_beam/handler.clj b/src/clj/s3_beam/handler.clj index 7472bfd..419c5bd 100644 --- a/src/clj/s3_beam/handler.clj +++ b/src/clj/s3_beam/handler.clj @@ -34,17 +34,34 @@ (.init (javax.crypto.spec.SecretKeySpec. (.getBytes key) "HmacSHA1"))) (.getBytes string "UTF-8")))) -(defn sign-upload [{:keys [file-name mime-type]} - {:keys [bucket aws-zone aws-access-key aws-secret-key]}] - (let [p (policy bucket file-name mime-type)] - {:action (str "https://" bucket "." aws-zone ".amazonaws.com/") - :key file-name - :Content-Type mime-type - :policy p - :acl "public-read" - :success_action_status "201" - :AWSAccessKeyId aws-access-key - :signature (hmac-sha1 aws-secret-key p)})) +(defn sign-upload + "Takes the request's params {:file-name :mime-type} and + the aws-config {:bucket, :aws-zone, :aws-access-key, :aws-secret-key} + to produce a map with all the data needed to upload to s3. + May take an extra opts map with: + :key-fn - A fn (ifn?) that takes the request params {:file-name :mime-type} + and returns the key for S3 (i.e. hash of the file-name, UUID, etc.). + It should be side-effect free and return a string. + Defaults to the :file-name keyword." + ([params aws-config] (sign-upload params aws-config {:key-fn :file-name})) + ([{:keys [file-name mime-type] :as params} + {:keys [bucket aws-zone aws-access-key aws-secret-key]} + {:keys [key-fn]}] + {:pre [(ifn? key-fn)]} + (let [key (key-fn params)] + (assert (string? key) + (str "The given :key-fn returned " key " with type " (type key) + " when given file-name: " file-name + " and mime-type: " mime-type ". It should return a string.")) + (let [p (policy bucket key mime-type)] + {:action (str "https://" bucket "." aws-zone ".amazonaws.com/") + :key key + :Content-Type mime-type + :policy p + :acl "public-read" + :success_action_status "201" + :AWSAccessKeyId aws-access-key + :signature (hmac-sha1 aws-secret-key p)})))) (defn s3-sign [bucket aws-zone access-key secret-key] (fn [request] diff --git a/src/cljs/s3_beam/client.cljs b/src/cljs/s3_beam/client.cljs index 9e95e82..6a34da8 100644 --- a/src/cljs/s3_beam/client.cljs +++ b/src/cljs/s3_beam/client.cljs @@ -31,16 +31,33 @@ (.append fd (name k) v)) fd)) -(defn upload-file [upload-info ch] +(defn xml->clj + "Transforms a shallow XML document into an idiomatic clj map" + [xml] + (let [kv (->> (array-seq (.. xml -firstChild -childNodes)) + (map (fn [n] [(.toLowerCase (.-nodeName n)) + (gdom/getTextContent n)])))] + (zipmap (map (comp keyword first) kv) (map second kv)))) + +(defn upload-file + "Uploads the file given by upload-info and puts the file and + upload response in the ch. Contains the following keys: + :file - the File Object that was uploaded + :response - S3 file data + :key + :location + :bucket + :etag" + [upload-info ch] (let [sig-fields [:key :Content-Type :success_action_status :policy :AWSAccessKeyId :signature :acl] signature (select-keys (:signature upload-info) sig-fields) form-data (formdata-from-map (merge signature {:file (:f upload-info)}))] (xhr/send (:action (:signature upload-info)) (fn [res] - (let [loc (aget (.getElementsByTagName (.getResponseXml (.-target res)) "Location") 0)] - (put! ch (gdom/getTextContent loc)) - (close! ch))) + (put! ch {:file (:f upload-info) + :response (xml->clj (.. res -target getResponseXml))}) + (close! ch)) "POST" form-data)))