├── .github └── workflows │ └── ci.yml ├── .gitignore ├── .standard.yml ├── .vscode └── ruby-sdk.code-workspace ├── CHANGELOG.md ├── Gemfile ├── LICENSE ├── Makefile ├── README.md ├── Rakefile ├── examples ├── README.md └── basic │ ├── assets │ ├── APU_Shutdown.mp3 │ ├── Computers_are_in_Control.flac │ ├── Go_for_Deploy.mp3 │ ├── Lookin_At_It.mp3 │ └── cat.jpg │ ├── audio-concat-transcoder.rb │ ├── audio-transcoder.rb │ ├── image-transcoder.rb │ ├── main.rb │ └── media-transcoder.rb ├── lib ├── transloadit.rb └── transloadit │ ├── api_model.rb │ ├── assembly.rb │ ├── exception.rb │ ├── request.rb │ ├── response.rb │ ├── response │ └── assembly.rb │ ├── step.rb │ ├── template.rb │ └── version.rb ├── test ├── fixtures │ └── cassettes │ │ ├── cancel_assembly.yml │ │ ├── create_template.yml │ │ ├── delete_template.yml │ │ ├── fetch_assemblies.yml │ │ ├── fetch_assembly_aborted.yml │ │ ├── fetch_assembly_errors.yml │ │ ├── fetch_assembly_executing.yml │ │ ├── fetch_assembly_notifications.yml │ │ ├── fetch_assembly_ok.yml │ │ ├── fetch_assembly_uploading.yml │ │ ├── fetch_billing.yml │ │ ├── fetch_root.yml │ │ ├── fetch_template.yml │ │ ├── fetch_templates.yml │ │ ├── post_assembly.yml │ │ ├── rate_limit_fail.yml │ │ ├── rate_limit_succeed.yml │ │ ├── replay_assembly.yml │ │ ├── replay_assembly_notification.yml │ │ ├── submit_assembly.yml │ │ └── update_template.yml ├── test_helper.rb └── unit │ ├── test_transloadit.rb │ └── transloadit │ ├── node-smartcdn-sig.ts │ ├── test_api.rb │ ├── test_assembly.rb │ ├── test_request.rb │ ├── test_response.rb │ ├── test_smart_cdn.rb │ ├── test_step.rb │ └── test_template.rb └── transloadit.gemspec /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | on: 3 | push: 4 | branches: 5 | - main 6 | pull_request: 7 | types: 8 | - opened 9 | - synchronize 10 | jobs: 11 | ci: 12 | runs-on: ubuntu-latest 13 | strategy: 14 | matrix: 15 | ruby: 16 | - 3.0 17 | - 3.1 18 | - 3.2 19 | - 3.3 20 | - jruby 21 | - truffleruby 22 | fail-fast: false 23 | steps: 24 | - uses: actions/checkout@v3 25 | - uses: actions/setup-node@v4 26 | with: 27 | node-version: 20 28 | - run: npm install -g tsx 29 | - uses: ruby/setup-ruby@v1 30 | with: 31 | ruby-version: ${{ matrix.ruby }} 32 | bundler-cache: true 33 | - run: bundle exec standardrb 34 | - name: Run tests 35 | env: 36 | COVERAGE: ${{ matrix.ruby == '3.3' && '1' || '0' }} 37 | TEST_NODE_PARITY: ${{ matrix.ruby == '3.3' && '1' || '0' }} 38 | run: bundle exec rake test 39 | - name: Upload coverage to Codecov 40 | if: matrix.ruby == '3.3' 41 | uses: codecov/codecov-action@v5 42 | with: 43 | token: ${{ secrets.CODECOV_TOKEN }} 44 | files: ./coverage/coverage.json 45 | fail_ci_if_error: true 46 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | .bundle 2 | .rvmrc 3 | .yardoc 4 | .ruby-version 5 | .idea/ 6 | Gemfile.lock 7 | 8 | coverage 9 | doc 10 | pkg 11 | 12 | transloadit-*.gem 13 | 14 | .DS_Store 15 | env.sh 16 | .env 17 | vendor/bundle/ 18 | -------------------------------------------------------------------------------- /.standard.yml: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transloadit/ruby-sdk/28060df9595618c890666a89f6c03285cb1f003a/.standard.yml -------------------------------------------------------------------------------- /.vscode/ruby-sdk.code-workspace: -------------------------------------------------------------------------------- 1 | { 2 | "folders": [ 3 | { 4 | "path": ".." 5 | } 6 | ], 7 | "settings": { 8 | "workbench.colorCustomizations": { 9 | "titleBar.activeForeground": "#ffffff", 10 | "titleBar.activeBackground": "#cc0000" 11 | }, 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | ### 3.1.0 / 2024-11-24 2 | 3 | - Add Smart CDN signature support via `signed_smart_cdn_url` method (kvz) 4 | 5 | ### 3.0.2 / 2024-03-01 6 | 7 | - Upgrade signature algorithm to more secure SHA-384 [#69](https://github.com/transloadit/ruby-sdk/pull/69) (@aduh95) 8 | 9 | ### 3.0.1 / 2024-01-10 10 | 11 | - Fix `undefined method` errors when handling network exceptions [#67](https://github.com/transloadit/ruby-sdk/pull/67) (@Acconut) 12 | 13 | ### 3.0.0 / 2024-01-10 14 | 15 | - BREAKING: Drop support for EOL'd Ruby 2.x 16 | - Ensure that signature is sent before files [#65](https://github.com/transloadit/ruby-sdk/pull/65) (@Acconut) 17 | - Send requests via HTTPS by default [#64](https://github.com/transloadit/ruby-sdk/pull/64) (@Acconut) 18 | - Prevent duplicate assembly steps [#49](https://github.com/transloadit/ruby-sdk/issues/27) (@ifedapoolarewaju) 19 | - Send "Transloadit-Client" header for every request (@ifedapoolarewaju) 20 | - Send all requests via HTTPS by default 21 | - Position signature before any files in requests [#51](https://github.com/transloadit/ruby-sdk/issues/51) 22 | 23 | ### 2.0.1 / 2017-01-23 24 | 25 | - Use the ssl enabled url as the API base url (@ifedapoolarewaju) 26 | 27 | ### 2.0.0 / 2016-12-03 28 | 29 | - Drop support for EOL'd Ruby 1.9.x and Ruby 2.0, please use version 1.2.0 if you need support for older 30 | Ruby versions. 31 | - Fix compatibility to Ruby >=2.1 and Rails 5. 32 | - Remove bored instance logic (thanks @ifedapoolarewaju for the PR). This shouldn't affect users at all and removes 33 | the need for another HTTP request before the actual HTTP request. 34 | - We now have the `transloadit.bill` method to retrieve billing reports. (@ifedapoolarewaju) 35 | - Deprecate `assembly.submit!` method for `assembly.create!`. This shouldn't affect users as the `submit!` method remains 36 | as an alias for `create!`. (@ifedapoolarewaju) 37 | - Add support for new `assembly` methods (Thanks @ifedapoolarewaju): 38 | - list to get a list of all assemblies. 39 | - get to retrieve a particular assembly. Requires assembly id to be passed as argument. 40 | - replay to replay a particular assembly. Requires assembly id to be passed as argument. 41 | - get_notifications to get a list of all assembly notifications. 42 | - replay_notification to replay the notification of a particular assembly. Requires assembly id to be passed as argument. 43 | - We now have a Template api with the following methods: 44 | - create to create a new template. 45 | - list to get a list of all templates. 46 | - get to retrieve a particular template. 47 | - update to update a particular template. 48 | - delete to delete a particular template. 49 | - Add rate limit feature to implicitly retry assembly creation when the rate limit is reached. 50 | - Add `assembly.reload_until_finished!` which calls `reload!` once per second until assembly is finished (@gbuesing) 51 | - Added example files with a [small tutorial](examples/README.md) in `examples` (@jasonaibrahim) 52 | 53 | ### 1.2.0 / 2015-12-28 54 | 55 | - allow custom fields to be passed to Transloadit and received back in the response (thanks @Acconut for the pull request) 56 | 57 | ### 1.1.4 / 2015-12-14 58 | 59 | - fix Ruby 1.9.x compatibility by explicitly requiring mime-types 2.99 60 | 61 | ### 1.1.3 / 2014-08-21 62 | 63 | - Use rest-client < 1.7.0 for Ruby version below 1.9 to stay 1.8 compatible. 64 | 65 | ### 1.1.2 / 2014-06-17 66 | 67 | - Fix deprecation warning on Ruby 2.1.0 for OpenSSL::Digest (thanks @pekeler for the patch) 68 | 69 | ### 1.1.1 / 2013-06-25 70 | 71 | - request.get with secret (thanks @miry for the patch) 72 | 73 | ### 1.1.0 / 2013-04-22 74 | 75 | - We now have more statuses available in the response: 76 | - finished? to check if processing is finished 77 | - error? to check if processing failed with errors 78 | - canceled? to check if processing was canceled 79 | - aborted? to check if processing was aborted 80 | - executing? to check if processing is still executing 81 | - uploading? to check if the upload is still going 82 | - Please use `finished?` to check if procssing is finished and `completed?` to 83 | check if completed successfully 84 | 85 | ### 1.0.5 / 2013-03-13 86 | 87 | - Use MultiJSON so everyone can use the JSON parser they like. (thanks @kselden for the patch) 88 | - Switch to Kramdown for RDoc formatting 89 | - Support jRuby 1.8/1.9 and MRI 2.0.0 too 90 | 91 | ### 1.0.4 / 2013-03-06 92 | 93 | - allow symbols as keys for response attributes (thanks @gbuesing for reporting) 94 | 95 | ### 1.0.3 / 2012-11-10 96 | 97 | - Support max_size option 98 | 99 | ### 1.0.1 / 2011-02-08 100 | 101 | [Full list of changes](https://github.com/transloadit/ruby-sdk/compare/v1.0.0...v1.0.1) 102 | 103 | - Enhancements 104 | 105 | - support custom form fields for Transloadit::Assembly 106 | 107 | - New Maintainers 108 | - Robin Mehner 109 | 110 | ### 1.0.0 / 2011-09-06 111 | 112 | - Initial release 113 | -------------------------------------------------------------------------------- /Gemfile: -------------------------------------------------------------------------------- 1 | source "https://rubygems.org" 2 | 3 | gem "rake" 4 | gem "minitest" 5 | gem "simplecov" 6 | gem "simplecov-cobertura" 7 | 8 | gemspec 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License 2 | 3 | Copyright (c) 2016 Transloadit Ltd. 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. 10 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: all test fix 2 | 3 | all: fix test 4 | 5 | # Run tests 6 | test: 7 | bundle exec rake test 8 | 9 | # Fix code formatting 10 | fix: 11 | bundle exec standardrb --fix 12 | 13 | # Install dependencies 14 | install: 15 | bundle install 16 | 17 | # Run both fix and test 18 | check: fix test 19 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | [![Build Status](https://github.com/transloadit/ruby-sdk/actions/workflows/ci.yml/badge.svg)](https://github.com/transloadit/ruby-sdk/actions/workflows/ci.yml) 2 | [![Coverage](https://codecov.io/gh/transloadit/ruby-sdk/branch/main/graph/badge.svg)](https://codecov.io/gh/transloadit/ruby-sdk) 3 | 4 | # Transloadit Ruby SDK 5 | 6 | A **Ruby** Integration for [Transloadit](https://transloadit.com)'s file uploading and encoding service 7 | 8 | ## Intro 9 | 10 | [Transloadit](https://transloadit.com) is a service that helps you handle file uploads, resize, crop and watermark your images, make GIFs, transcode your videos, extract thumbnails, generate audio waveforms, and so much more. In short, [Transloadit](https://transloadit.com) is the Swiss Army Knife for your files. 11 | 12 | This is a **Ruby** SDK to make it easy to talk to the [Transloadit](https://transloadit.com) REST API. 13 | 14 | _If you run Ruby on Rails and are looking to integrate with the browser for file uploads, checkout the [rails-sdk](https://github.com/transloadit/rails-sdk)._ 15 | 16 | ## Install 17 | 18 | ```bash 19 | gem install transloadit 20 | ``` 21 | 22 | ## Usage 23 | 24 | To get started, you need to require the 'transloadit' gem: 25 | 26 | ```bash 27 | $ irb -rubygems 28 | >> require 'transloadit' 29 | => true 30 | ``` 31 | 32 | Then create a Transloadit instance, which will maintain your 33 | [authentication credentials](https://transloadit.com/accounts/credentials) 34 | and allow us to make requests to [the API](https://transloadit.com/docs/api/). 35 | 36 | ```ruby 37 | transloadit = Transloadit.new( 38 | :key => 'MY_TRANSLOADIT_KEY', 39 | :secret => 'MY_TRANSLOADIT_SECRET' 40 | ) 41 | ``` 42 | 43 | ### 1. Resize and store an image 44 | 45 | This example demonstrates how you can create an Assembly to resize an image 46 | and store the result on [Amazon S3](https://aws.amazon.com/s3/). 47 | 48 | ```ruby 49 | require 'transloadit' 50 | 51 | transloadit = Transloadit.new( 52 | :key => 'MY_TRANSLOADIT_KEY', 53 | :secret => 'MY_TRANSLOADIT_SECRET' 54 | ) 55 | 56 | # First, we create two steps: one to resize the image to 320x240, and another to 57 | # store the image in our S3 bucket. 58 | resize = transloadit.step 'resize', '/image/resize', 59 | :width => 320, 60 | :height => 240 61 | 62 | store = transloadit.step 'store', '/s3/store', 63 | :key => 'MY_AWS_KEY', 64 | :secret => 'MY_AWS_SECRET', 65 | :bucket => 'MY_S3_BUCKET' 66 | 67 | # Now that we have the steps, we create an assembly (which is just a request to 68 | # process a file or set of files) and let Transloadit do the rest. 69 | assembly = transloadit.assembly( 70 | :steps => [ resize, store ] 71 | ) 72 | 73 | response = assembly.create! open('/PATH/TO/FILE.jpg') 74 | 75 | # reloads the response once per second until all processing is finished 76 | response.reload_until_finished! 77 | 78 | if response.error? 79 | # handle error 80 | else 81 | # handle other cases 82 | puts response 83 | end 84 | ``` 85 | 86 |
87 | Note: The Assembly method submit! has been deprecated and replaced with create!. 88 | The submit! method remains as an alias of create! for backward Compatibility. 89 |
90 | 91 | When the `create!` method returns, the file has been uploaded but may not yet 92 | be done processing. We can use the returned object to check if processing has 93 | completed, or examine other attributes of the request. 94 | 95 | ```ruby 96 | # returns the unique API ID of the assembly 97 | response[:assembly_id] # => '9bd733a...' 98 | 99 | # returns the API URL endpoint for the assembly 100 | response[:assembly_ssl_url] # => 'https://api2.vivian.transloadit.com/assemblies/9bd733a...' 101 | 102 | # checks how many bytes were expected / received by transloadit 103 | response[:bytes_expected] # => 92933 104 | response[:bytes_received] # => 92933 105 | 106 | # checks if all processing has been finished 107 | response.finished? # => false 108 | 109 | # cancels further processing on the assembly 110 | response.cancel! # => true 111 | 112 | # checks if processing was successfully completed 113 | response.completed? # => true 114 | 115 | # checks if the processing returned with an error 116 | response.error? # => false 117 | ``` 118 | 119 | It's important to note that none of these queries are "live" (with the 120 | exception of the `cancel!` method). They all check the response given by the 121 | API at the time the Assembly was created. You have to explicitly ask the 122 | Assembly to reload its results from the API. 123 | 124 | ```ruby 125 | # reloads the response's contents from the REST API 126 | response.reload! 127 | 128 | # reloads once per second until all processing is finished, up to number of 129 | # times specified in :tries option, otherwise will raise ReloadLimitReached 130 | response.reload_until_finished! tries: 300 # default is 600 131 | ``` 132 | 133 | In general, you use hash accessor syntax to query any direct attribute from 134 | the [response](https://transloadit.com/docs/api/#assembly-status-response). 135 | Methods suffixed by a question mark provide a more readable way of querying 136 | state (e.g., `assembly.completed?` vs. checking the result of 137 | `assembly[:ok]`). Methods suffixed by a bang make a live query against the 138 | Transloadit HTTP API. 139 | 140 | ### 2. Uploading multiple files 141 | 142 | Multiple files can be given to the `create!` method in order to upload more 143 | than one file in the same request. You can also pass a single Step for the 144 | `steps` parameter, without having to wrap it in an Array. 145 | 146 | ```ruby 147 | require 'transloadit' 148 | 149 | transloadit = Transloadit.new( 150 | :key => 'MY_TRANSLOADIT_KEY', 151 | :secret => 'MY_TRANSLOADIT_SECRET' 152 | ) 153 | 154 | assembly = transloadit.assembly(steps: store) 155 | 156 | response = assembly.create!( 157 | open('puppies.jpg'), 158 | open('kittens.jpg'), 159 | open('ferrets.jpg') 160 | ) 161 | ``` 162 | 163 | You can also pass an array of files to the `create!` method. 164 | Just unpack the array using the splat `*` operator. 165 | 166 | ```ruby 167 | files = [open('puppies.jpg'), open('kittens.jpg'), open('ferrets.jpg')] 168 | response = assembly.create! *files 169 | ``` 170 | 171 | ### 3. Parallel Assembly 172 | 173 | Transloadit allows you to perform several processing steps in parallel. You 174 | simply need to `use` other Steps. Following 175 | [their example](https://transloadit.com/docs/#special-parameters): 176 | 177 | ```ruby 178 | require 'transloadit' 179 | 180 | transloadit = Transloadit.new( 181 | :key => 'MY_TRANSLOADIT_KEY', 182 | :secret => 'MY_TRANSLOADIT_SECRET' 183 | ) 184 | 185 | encode = transloadit.step 'encode', '/video/encode', { ... } 186 | thumbs = transloadit.step 'thumbs', '/video/thumbs', { ... } 187 | export = transloadit.step 'store', '/s3/store', { ... } 188 | 189 | export.use [ encode, thumbs ] 190 | 191 | transloadit.assembly( 192 | :steps => [ encode, thumbs, export ] 193 | ).create! open('/PATH/TO/FILE.mpg') 194 | ``` 195 | 196 | You can also tell a step to use the original uploaded file by passing the 197 | Symbol `:original` instead of another step. 198 | 199 | Check the YARD documentation for more information on using 200 | [use](https://rubydoc.info/gems/transloadit/frames/Transloadit/Step#use-instance_method). 201 | 202 | ### 4. Creating an Assembly with Templates 203 | 204 | Transloadit allows you to use custom [templates](https://github.com/transloadit/ruby-sdk/blob/main/README.md#8-templates) 205 | for recurring encoding tasks. In order to use these do the following: 206 | 207 | ```ruby 208 | require 'transloadit' 209 | 210 | transloadit = Transloadit.new( 211 | :key => 'MY_TRANSLOADIT_KEY', 212 | :secret => 'MY_TRANSLOADIT_SECRET' 213 | ) 214 | 215 | transloadit.assembly( 216 | :template_id => 'MY_TEMPLATE_ID' 217 | ).create! open('/PATH/TO/FILE.mpg') 218 | ``` 219 | 220 | You can use your steps together with this template and even use variables. 221 | The [Transloadit documentation](https://transloadit.com/docs/#passing-variables-into-a-template) 222 | has some nice examples for that. 223 | 224 | ### 5. Using fields 225 | 226 | Transloadit allows you to submit form field values that you'll get back in the 227 | notification. This is quite handy if you want to add additional custom metadata 228 | to the upload itself. You can use fields like the following: 229 | 230 | ```ruby 231 | require 'transloadit' 232 | 233 | transloadit = Transloadit.new( 234 | :key => 'MY_TRANSLOADIT_KEY', 235 | :secret => 'MY_TRANSLOADIT_SECRET' 236 | ) 237 | 238 | transloadit.assembly( 239 | :fields => { 240 | :tag => 'some_tag_name', 241 | :field_name => 'field_value' 242 | } 243 | ).create! open('/PATH/TO/FILE.mpg') 244 | ``` 245 | 246 | ### 6. Notify URL 247 | 248 | If you want to be notified when the processing is finished you can provide 249 | a Notify URL for the Assembly. 250 | 251 | ```ruby 252 | require 'transloadit' 253 | 254 | transloadit = Transloadit.new( 255 | :key => 'MY_TRANSLOADIT_KEY', 256 | :secret => 'MY_TRANSLOADIT_SECRET' 257 | ) 258 | 259 | transloadit.assembly( 260 | :notify_url => 'https://example.com/processing_finished' 261 | ).create! open('/PATH/TO/FILE.mpg') 262 | ``` 263 | 264 | Read up more on the Notifications [on Transloadit's documentation page](https://transloadit.com/docs/#notifications) 265 | 266 | ### 7. Other Assembly methods 267 | 268 | Transloadit also provides methods to retrieve/replay Assemblies and their Notifications. 269 | 270 | ```ruby 271 | require 'transloadit' 272 | 273 | transloadit = Transloadit.new( 274 | :key => 'MY_TRANSLOADIT_KEY', 275 | :secret => 'MY_TRANSLOADIT_SECRET' 276 | ) 277 | 278 | assembly = transloadit.assembly 279 | 280 | # returns a list of all assemblies 281 | assembly.list 282 | 283 | # returns a specific assembly 284 | assembly.get 'MY_ASSEMBLY_ID' 285 | 286 | # replays a specific assembly 287 | response = assembly.replay 'MY_ASSEMBLY_ID' 288 | # should return true if assembly is replaying and false otherwise. 289 | response.replaying? 290 | 291 | # returns all assembly notifications 292 | assembly.get_notifications 293 | 294 | # replays an assembly notification 295 | assembly.replay_notification 'MY_ASSEMBLY_ID' 296 | ``` 297 | 298 | ### 8. Templates 299 | 300 | Transloadit provides a [templates api](https://transloadit.com/docs/#templates) 301 | for recurring encoding tasks. Here's how you would create a Template: 302 | 303 | ```ruby 304 | require 'transloadit' 305 | 306 | transloadit = Transloadit.new( 307 | :key => 'MY_TRANSLOADIT_KEY', 308 | :secret => 'MY_TRANSLOADIT_SECRET' 309 | ) 310 | 311 | template = transloadit.template 312 | 313 | # creates a new template 314 | template.create( 315 | :name => 'TEMPLATE_NAME', 316 | :template => { 317 | "steps": { 318 | "encode": { 319 | "use": ":original", 320 | "robot": "/video/encode", 321 | "result": true 322 | } 323 | } 324 | } 325 | ) 326 | ``` 327 | 328 | There are also some other methods to retrieve, update and delete a Template. 329 | 330 | ```ruby 331 | require 'transloadit' 332 | 333 | transloadit = Transloadit.new( 334 | :key => 'MY_TRANSLOADIT_KEY', 335 | :secret => 'MY_TRANSLOADIT_SECRET' 336 | ) 337 | 338 | template = transloadit.template 339 | 340 | # returns a list of all templates. 341 | template.list 342 | 343 | # returns a specific template. 344 | template.get 'MY_TEMPLATE_ID' 345 | 346 | # updates the template whose id is specified. 347 | template.update( 348 | 'MY_TEMPLATE_ID', 349 | :name => 'CHANGED_TEMPLATE_NAME', 350 | :template => { 351 | :steps => { 352 | :encode => { 353 | :use => ':original', 354 | :robot => '/video/merge' 355 | } 356 | } 357 | } 358 | ) 359 | 360 | # deletes a specific template 361 | template.delete 'MY_TEMPLATE_ID' 362 | ``` 363 | 364 | ### 9. Getting Bill reports 365 | 366 | If you want to retrieve your Transloadit account billing report for a particular month and year 367 | you can use the `bill` method passing the required month and year like the following: 368 | 369 | ```ruby 370 | require 'transloadit' 371 | 372 | transloadit = Transloadit.new( 373 | :key => 'MY_TRANSLOADIT_KEY', 374 | :secret => 'MY_TRANSLOADIT_SECRET' 375 | ) 376 | 377 | # returns bill report for February, 2016. 378 | transloadit.bill(2, 2016) 379 | ``` 380 | 381 | Not specifying the `month` or `year` would default to the current month or year. 382 | 383 | ### 10. Signing Smart CDN URLs 384 | 385 | You can generate signed [Smart CDN](https://transloadit.com/services/content-delivery/) URLs using your Transloadit instance: 386 | 387 | ```ruby 388 | require 'transloadit' 389 | 390 | transloadit = Transloadit.new( 391 | :key => 'MY_TRANSLOADIT_KEY', 392 | :secret => 'MY_TRANSLOADIT_SECRET' 393 | ) 394 | 395 | # Generate a signed URL using instance credentials 396 | url = transloadit.signed_smart_cdn_url( 397 | workspace: "MY_WORKSPACE", 398 | template: "MY_TEMPLATE", 399 | input: "avatars/jane.jpg" 400 | ) 401 | 402 | # Add URL parameters 403 | url = transloadit.signed_smart_cdn_url( 404 | workspace: "MY_WORKSPACE", 405 | template: "MY_TEMPLATE", 406 | input: "avatars/jane.jpg", 407 | url_params: { 408 | width: 100, 409 | height: 200 410 | } 411 | ) 412 | 413 | # Set expiration time 414 | url = transloadit.signed_smart_cdn_url( 415 | workspace: "MY_WORKSPACE", 416 | template: "MY_TEMPLATE", 417 | input: "avatars/jane.jpg", 418 | expire_at_ms: 1732550672867 # Specific timestamp 419 | ) 420 | ``` 421 | 422 | The generated URL will be signed using your Transloadit credentials and can be used to access files through the Smart CDN in a secure manner. 423 | 424 | ### 11. Rate limits 425 | 426 | Transloadit enforces rate limits to guarantee that no customers are adversely affected by the usage 427 | of any given customer. See [Rate Limiting](https://transloadit.com/docs/api/#rate-limiting). 428 | 429 | While creating an Assembly, if a rate limit error is received, by default, 2 more attempts would be made for a successful response. If after these attempts the rate limit error persists, a `RateLimitReached` exception will be raised. 430 | 431 | To change the number of attempts that will be made when creating an Assembly, you may pass the `tries` option to your Assembly like so. 432 | 433 | ```ruby 434 | require 'transloadit' 435 | 436 | transloadit = Transloadit.new( 437 | :key => 'MY_TRANSLOADIT_KEY', 438 | :secret => 'MY_TRANSLOADIT_SECRET' 439 | ) 440 | 441 | # would make one extra attempt after a failed attempt. 442 | transloadit.assembly(:tries => 2).create! open('/PATH/TO/FILE.mpg') 443 | 444 | # Would make no attempt at all. Your request would not be sent. 445 | transloadit.assembly(:tries => 0).create! open('/PATH/TO/FILE.mpg') 446 | ``` 447 | 448 | ## Example 449 | 450 | A small sample tutorials of using the Transloadit Ruby SDK to optimize an image, encode MP3 audio, add ID3 tags, 451 | and more can be found [here](https://github.com/transloadit/ruby-sdk/tree/main/examples). 452 | 453 | ## Documentation 454 | 455 | Up-to-date YARD documentation is automatically generated. You can view the 456 | docs for the released gem or 457 | for the latest [git main](https://rubydoc.info/github/transloadit/ruby-sdk/main/frames). 458 | 459 | ## Compatibility 460 | 461 | Please see [ci.yml](https://github.com/transloadit/ruby-sdk/tree/main/.github/workflows/ci.yml) for a list of supported ruby versions. It may also work on older Rubies, but support for those is not guaranteed. If it doesn't work on one of the officially supported Rubies, please file a 462 | [bug report](https://github.com/transloadit/ruby-sdk/issues). Compatibility patches for other Rubies are welcome. 463 | 464 | ### Ruby 2.x 465 | 466 | If you still need support for Ruby 2.x, 2.0.1 is the last version that supports it. 467 | 468 | ## Contributing 469 | 470 | Contributions are welcome! 471 | 472 | ### Running tests 473 | 474 | ```bash 475 | bundle install 476 | bundle exec rake test 477 | ``` 478 | 479 | To also test parity against the Node.js reference implementation, run: 480 | 481 | ```bash 482 | TEST_NODE_PARITY=1 bundle exec rake test 483 | ``` 484 | 485 | To disable coverage reporting, run: 486 | 487 | ```bash 488 | COVERAGE=0 bundle exec rake test 489 | ``` 490 | 491 | ### Releasing on RubyGems 492 | 493 | Let's say you wanted to release version `3.1.0`, here are the steps: 494 | 495 | 1. Update the version number in the version file `version.rb` and `CHANGELOG.md` 496 | 2. Commit: `git add CHANGELOG.md lib/transloadit/version.rb && git commit -m "Release 3.1.0"` 497 | 3. Create a git tag: `git tag -a v3.1.0 -m "Release 3.1.0"` 498 | 4. Push the git tag: `git push origin v3.1.0` 499 | 5. Release on RubyGems: `gem build transloadit.gemspec && gem push transloadit-3.1.0.gem` 500 | 6. Draft a release [here](https://github.com/transloadit/ruby-sdk/releases). Click the `v3.1.0` tag and click `Generate release notes`. Inspect and Publish. 501 | -------------------------------------------------------------------------------- /Rakefile: -------------------------------------------------------------------------------- 1 | require "bundler/gem_tasks" 2 | require "rake/testtask" 3 | 4 | Rake::TestTask.new do |test| 5 | test.libs << "test" 6 | test.pattern = "test/**/test_*.rb" 7 | end 8 | 9 | begin 10 | require "yard" 11 | require "yard/rake/yardoc_task" 12 | 13 | YARD::Rake::YardocTask.new :doc do |yard| 14 | yard.options = %w[ 15 | --title Transloadit 16 | --readme README.md 17 | --markup rdoc 18 | ] 19 | end 20 | rescue 21 | desc "You need the `yard` gem to generate documentation" 22 | task :doc 23 | end 24 | 25 | task default: :test 26 | -------------------------------------------------------------------------------- /examples/README.md: -------------------------------------------------------------------------------- 1 | # Example Usage of the Transloadit Ruby SDK 2 | 3 | ### See an example 4 | Navigate to an example directory (e.g. ```basic```) and then run the following, making sure to 5 | substitute your own values for the environment variables below: 6 | 7 | ```bash 8 | TRANSLOADIT_KEY= \ 9 | TRANSLOADIT_SECRET= \ 10 | main.rb 11 | ``` 12 | 13 | If you wish to store results from the encoding in an s3 bucket of your own, you can set your s3 credentials as well like so: 14 | 15 | ```bash 16 | TRANSLOADIT_KEY= \ 17 | TRANSLOADIT_SECRET= \ 18 | S3_BUCKET= \ 19 | S3_ACCESS_KEY= \ 20 | S3_SECRET_KEY= \ 21 | S3_REGION= \ 22 | main.rb 23 | ``` 24 | 25 | Please be sure you have the `transloadit` gem installed by running `gem install transloadit` before running these examples. 26 | 27 | ## Code Review 28 | 29 | ### Overview 30 | 31 | In each example we utilize a simple base class `MediaTranscoder`. This class provides us a simple method: 32 | 33 | ``` 34 | transloadit_client 35 | ``` 36 | 37 | The method is responsible for returning us an instance of the Transloadit SDK object, 38 | utilizing our credentials that we set in environment variables. 39 | 40 | ### First example 41 | 42 | In the [first example](https://github.com/transloadit/ruby-sdk/blob/main/examples/basic/image-transcoder.rb) 43 | that gets played, we load an image, optimize it using the Transloadit `/image/optimize` robot, and then optionally 44 | store it in s3 if the s3 credentials are set. 45 | 46 | There are only two steps: 47 | 48 | ```ruby 49 | optimize = transloadit_client.step('image', '/image/optimize', { 50 | progressive: true, 51 | use: ':original', 52 | result: true 53 | }) 54 | 55 | steps = [optimize] 56 | 57 | begin 58 | store = transloadit_client.step('store', '/s3/store', { 59 | key: ENV.fetch('S3_ACCESS_KEY'), 60 | secret: ENV.fetch('S3_SECRET_KEY'), 61 | bucket: ENV.fetch('S3_BUCKET'), 62 | bucket_region: ENV.fetch('S3_REGION'), 63 | use: 'image' 64 | }) 65 | 66 | steps.push(store) 67 | rescue KeyError => e 68 | puts 's3 config not set. Skipping s3 storage...' 69 | end 70 | 71 | ``` 72 | 73 | Again, we utilize environment variables to access our s3 credentials if given and pass them to 74 | our assembly. If the s3 credentials are not set, this step is skipped and the transloadit temporary s3 storage is used. 75 | 76 | The job is invoked by running the following: 77 | 78 | ```ruby 79 | assembly = transloadit_client.assembly(steps: steps) 80 | assembly.create! open(file) 81 | ``` 82 | 83 | We pass the steps we defined above and call `open` on the file passed in. This method 84 | assumes the file object passed in responds to `open`. 85 | 86 | ### Second example 87 | 88 | In the [second example](https://github.com/transloadit/ruby-sdk/blob/main/examples/basic/audio-transcoder.rb), 89 | we take a non-mp3 audio file, encode it as an mp3, add ID3 tags to it, and then optionally store it in s3. 90 | There are many use cases for audio uploads, and adding ID3 tags provides the necessary metadata to display artist and track information 91 | in audio players such as iTunes. 92 | 93 | We have the following steps: 94 | 95 | ```ruby 96 | encode_mp3 = transloadit_client.step('mp3_encode', '/audio/encode', { 97 | use: ':original', 98 | preset: 'mp3', 99 | ffmpeg_stack: 'v2.2.3', 100 | result: true 101 | }) 102 | write_metadata = transloadit_client.step('mp3', '/meta/write', { 103 | use: 'mp3_encode', 104 | ffmpeg_stack: 'v2.2.3', 105 | result: true, 106 | data_to_write: mp3_metadata 107 | }) 108 | 109 | steps = [encode_mp3, write_metadata] 110 | 111 | begin 112 | store = transloadit_client.step('store', '/s3/store', { 113 | key: ENV.fetch('S3_ACCESS_KEY'), 114 | secret: ENV.fetch('S3_SECRET_KEY'), 115 | bucket: ENV.fetch('S3_BUCKET'), 116 | bucket_region: ENV.fetch('S3_REGION'), 117 | use: ['mp3'] 118 | }) 119 | 120 | steps.push(store) 121 | rescue KeyError => e 122 | puts 's3 config not set. Skipping s3 storage...' 123 | end 124 | ``` 125 | 126 | The first step simply uses the original file to create an mp3 version using the `audio/encode` 127 | robot. 128 | 129 | The second step takes the first step as input, and adds the appropriate metadata using the `meta/write` 130 | robot. In our simple example we set the track name to the name of the file using variable 131 | name substitution (see https://transloadit.com/docs/#assembly-variables), and set canned 132 | values for all other ID3 fields 133 | 134 | ```ruby 135 | def mp3_metadata 136 | meta = { publisher: 'Transloadit', title: '${file.name}' } 137 | meta[:album] = 'Transloadit Compilation' 138 | meta[:artist] = 'Transloadit' 139 | meta[:track] = '1/1' 140 | meta 141 | end 142 | ``` 143 | 144 | Again, we utilize environment variables to access our s3 credentials if given and pass them to 145 | our assembly. If the s3 credentials are not set, this step is skipped and the transloadit temporary s3 storage is used. 146 | 147 | Finally, we submit the assembly in the same way as the previous example: 148 | 149 | ```ruby 150 | assembly = transloadit_client.assembly(steps: steps) 151 | assembly.create! open(file) 152 | ``` 153 | 154 | ### Third example 155 | 156 | In the [third example](https://github.com/transloadit/ruby-sdk/blob/main/examples/basic/audio-concat-transcoder.rb), 157 | we take a series of mp3 files and concatenate them together. We then optionally upload the result to s3. 158 | 159 | This example is provided to showcase advanced usage of the `use` parameter in the `audio/concat` assembly. 160 | 161 | In our `transcode` method, note that this time we are passed an array of files. 162 | 163 | ```ruby 164 | concat = transloadit_client.step('concat', '/audio/concat', { 165 | ffmpeg_stack: 'v2.2.3', 166 | preset: 'mp3', 167 | use: { 168 | steps: files.map.each_with_index do |f, i| 169 | { name: ':original', as: "audio_#{i}", fields: "file_#{i}" } 170 | end 171 | }, 172 | result: true 173 | }) 174 | ``` 175 | 176 | Taking a look at the `concat` step, we see a different usage of the `use` parameter 177 | than we have seen in previous examples. We are effectively able to define the ordering of the 178 | concatenation by specifying the ```name```, `as` and `fields` parameters. 179 | 180 | In this example, we have set the name for each to `:original`, specifying that the input 181 | at index `i` should be the input file defined at index `i`. 182 | 183 | It is equally important to specify the `as` parameter. This simple parameter tells the assembly 184 | the ordering. 185 | 186 | Finally, we have the `fields` parameter. Files that get uploaded via the Ruby SDK get sent to Transloadit 187 | through an HTTP Rest client as a multipart/form-data request. This means that each field needs a name. The Ruby SDK 188 | automatically adds the name `file_` to the outgoing request, where `` is the number specified 189 | by its position in the array. 190 | 191 | This is why it is important to define the ordering in the `steps` array, as there is no guarantee that items 192 | will finish uploading in the order they are sent. 193 | 194 | With that step complete, we can finalize our store step (which is optionally added if the s3 credentials are set) and submit the assembly. 195 | 196 | ```ruby 197 | begin 198 | store = transloadit_client.step('store', '/s3/store', { 199 | key: ENV.fetch('S3_ACCESS_KEY'), 200 | secret: ENV.fetch('S3_SECRET_KEY'), 201 | bucket: ENV.fetch('S3_BUCKET'), 202 | bucket_region: ENV.fetch('S3_REGION'), 203 | use: ['concat'] 204 | }) 205 | 206 | steps.push(store) 207 | rescue KeyError => e 208 | puts 's3 config not set. Skipping s3 storage...' 209 | end 210 | 211 | assembly = transloadit_client.assembly(steps: steps) 212 | assembly.create! *open_files(files) 213 | ``` 214 | 215 | Note the final call to `create` and usage of the splat (`*`) operator. The `create!` method expects 216 | one or more arguments. If you would like to pass an array to this method, you must unpack the contents of the array 217 | or the method will treat the argument passed in as a single object, and you may have unexpected results in your 218 | final results. 219 | 220 | ### Conclusion 221 | 222 | With the above examples, we have seen how we can utilize the Transloadit Ruby SDK to perform simple image optimization, 223 | mp3 encoding and metadata writing, and audio concatenation features provided by the Transloadit service. Please visit 224 | https://transloadit.com/docs for the full Transloadit API documentation. 225 | -------------------------------------------------------------------------------- /examples/basic/assets/APU_Shutdown.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transloadit/ruby-sdk/28060df9595618c890666a89f6c03285cb1f003a/examples/basic/assets/APU_Shutdown.mp3 -------------------------------------------------------------------------------- /examples/basic/assets/Computers_are_in_Control.flac: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transloadit/ruby-sdk/28060df9595618c890666a89f6c03285cb1f003a/examples/basic/assets/Computers_are_in_Control.flac -------------------------------------------------------------------------------- /examples/basic/assets/Go_for_Deploy.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transloadit/ruby-sdk/28060df9595618c890666a89f6c03285cb1f003a/examples/basic/assets/Go_for_Deploy.mp3 -------------------------------------------------------------------------------- /examples/basic/assets/Lookin_At_It.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transloadit/ruby-sdk/28060df9595618c890666a89f6c03285cb1f003a/examples/basic/assets/Lookin_At_It.mp3 -------------------------------------------------------------------------------- /examples/basic/assets/cat.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/transloadit/ruby-sdk/28060df9595618c890666a89f6c03285cb1f003a/examples/basic/assets/cat.jpg -------------------------------------------------------------------------------- /examples/basic/audio-concat-transcoder.rb: -------------------------------------------------------------------------------- 1 | class AudioConcatTranscoder < MediaTranscoder 2 | require "transloadit" 3 | require_relative "media-transcoder" 4 | 5 | # in this example a file is encoded as an mp3, id3 tags are added, and it is stored in s3 6 | def transcode!(files) 7 | concat = transloadit_client.step("concat", "/audio/concat", { 8 | preset: "mp3", 9 | use: { 10 | steps: files.map.each_with_index do |f, i| 11 | {name: ":original", as: "audio_#{i}", fields: "file_#{i}"} 12 | end 13 | }, 14 | result: true 15 | }) 16 | 17 | steps = [concat] 18 | 19 | begin 20 | store = transloadit_client.step("store", "/s3/store", { 21 | key: ENV.fetch("S3_ACCESS_KEY"), 22 | secret: ENV.fetch("S3_SECRET_KEY"), 23 | bucket: ENV.fetch("S3_BUCKET"), 24 | bucket_region: ENV.fetch("S3_REGION"), 25 | use: ["concat"] 26 | }) 27 | 28 | steps.push(store) 29 | rescue KeyError 30 | puts "s3 config not set. Skipping s3 storage..." 31 | end 32 | 33 | assembly = transloadit_client.assembly(steps: steps) 34 | assembly.create!(*open_files(files)) 35 | end 36 | 37 | def open_files(files) 38 | files.map do |f| 39 | File.open(f) 40 | end 41 | end 42 | 43 | def mp3_metadata 44 | meta = {publisher: "Transloadit", title: "${file.name}"} 45 | meta[:album] = "Transloadit Compilation" 46 | meta[:artist] = "Transloadit" 47 | meta[:track] = "1/1" 48 | meta 49 | end 50 | end 51 | -------------------------------------------------------------------------------- /examples/basic/audio-transcoder.rb: -------------------------------------------------------------------------------- 1 | class AudioTranscoder < MediaTranscoder 2 | require "transloadit" 3 | require_relative "media-transcoder" 4 | 5 | # in this example a file is encoded as an mp3, id3 tags are added, and it is stored in s3 6 | def transcode!(file) 7 | encode_mp3 = transloadit_client.step("mp3_encode", "/audio/encode", { 8 | use: ":original", 9 | preset: "mp3", 10 | ffmpeg_stack: "v2.2.3", 11 | result: true 12 | }) 13 | write_metadata = transloadit_client.step("mp3", "/meta/write", { 14 | use: "mp3_encode", 15 | ffmpeg_stack: "v2.2.3", 16 | result: true, 17 | data_to_write: mp3_metadata 18 | }) 19 | 20 | steps = [encode_mp3, write_metadata] 21 | 22 | begin 23 | store = transloadit_client.step("store", "/s3/store", { 24 | key: ENV.fetch("S3_ACCESS_KEY"), 25 | secret: ENV.fetch("S3_SECRET_KEY"), 26 | bucket: ENV.fetch("S3_BUCKET"), 27 | bucket_region: ENV.fetch("S3_REGION"), 28 | use: ["mp3"] 29 | }) 30 | 31 | steps.push(store) 32 | rescue KeyError 33 | puts "s3 config not set. Skipping s3 storage..." 34 | end 35 | 36 | assembly = transloadit_client.assembly(steps: steps) 37 | assembly.create! File.open(file) 38 | end 39 | 40 | def mp3_metadata 41 | meta = {publisher: "Transloadit", title: "${file.name}"} 42 | meta[:album] = "Transloadit Compilation" 43 | meta[:artist] = "Transloadit" 44 | meta[:track] = "1/1" 45 | meta 46 | end 47 | end 48 | -------------------------------------------------------------------------------- /examples/basic/image-transcoder.rb: -------------------------------------------------------------------------------- 1 | class ImageTranscoder < MediaTranscoder 2 | require "transloadit" 3 | require_relative "media-transcoder" 4 | 5 | # in this example a file is submitted, optimized, and then stored in s3 6 | def transcode!(file) 7 | optimize = transloadit_client.step("image", "/image/optimize", { 8 | progressive: true, 9 | use: ":original", 10 | result: true 11 | }) 12 | 13 | steps = [optimize] 14 | 15 | begin 16 | store = transloadit_client.step("store", "/s3/store", { 17 | key: ENV.fetch("S3_ACCESS_KEY"), 18 | secret: ENV.fetch("S3_SECRET_KEY"), 19 | bucket: ENV.fetch("S3_BUCKET"), 20 | bucket_region: ENV.fetch("S3_REGION"), 21 | use: "image" 22 | }) 23 | 24 | steps.push(store) 25 | rescue KeyError 26 | puts "s3 config not set. Skipping s3 storage..." 27 | end 28 | 29 | assembly = transloadit_client.assembly(steps: steps) 30 | assembly.create! File.open(file) 31 | end 32 | end 33 | -------------------------------------------------------------------------------- /examples/basic/main.rb: -------------------------------------------------------------------------------- 1 | require_relative "media-transcoder" 2 | require_relative "image-transcoder" 3 | require_relative "audio-transcoder" 4 | require_relative "audio-concat-transcoder" 5 | 6 | puts "starting image transcoding job..." 7 | 8 | File.open("#{__dir__}/assets/cat.jpg") do |f| 9 | image_transcoder = ImageTranscoder.new 10 | response = image_transcoder.transcode!(f) 11 | 12 | # if you are using rails one thing you can do would be to start an ActiveJob process that recursively 13 | # checks on the status of the assembly until it is finished 14 | puts "checking job status..." 15 | response.reload_until_finished! 16 | 17 | puts response[:message] 18 | puts response[:results]["image"][0]["ssl_url"] 19 | puts "\n" 20 | end 21 | 22 | puts "starting audio transcoding job..." 23 | 24 | File.open("#{__dir__}/assets/Computers_are_in_Control.flac") do |f| 25 | audio_transcoder = AudioTranscoder.new 26 | response = audio_transcoder.transcode!(f) 27 | 28 | # if you are using rails one thing you can do would be to start an ActiveJob process that recursively 29 | # checks on the status of the assembly until it is finished 30 | puts "checking job status..." 31 | response.reload_until_finished! 32 | 33 | puts response[:message] 34 | puts response[:results]["mp3"][0]["ssl_url"] 35 | puts "\n" 36 | end 37 | 38 | puts "starting audio concat transcoding job..." 39 | 40 | files = [ 41 | "#{__dir__}/assets/APU_Shutdown.mp3", 42 | "#{__dir__}/assets/Go_for_Deploy.mp3", 43 | "#{__dir__}/assets/Lookin_At_It.mp3" 44 | ] 45 | 46 | audio_concat_transcoder = AudioConcatTranscoder.new 47 | response = audio_concat_transcoder.transcode!(files) 48 | 49 | # if you are using rails one thing you can do would be to start an ActiveJob process that recursively 50 | # checks on the status of the assembly until it is finished 51 | puts "checking job status..." 52 | response.reload_until_finished! 53 | 54 | puts response[:message] 55 | puts response[:results]["concat"][0]["ssl_url"] 56 | puts "\n" 57 | -------------------------------------------------------------------------------- /examples/basic/media-transcoder.rb: -------------------------------------------------------------------------------- 1 | class MediaTranscoder 2 | def transloadit_client 3 | @transloadit ||= Transloadit.new({ 4 | key: ENV.fetch("TRANSLOADIT_KEY"), 5 | secret: ENV.fetch("TRANSLOADIT_SECRET") 6 | }) 7 | end 8 | end 9 | -------------------------------------------------------------------------------- /lib/transloadit.rb: -------------------------------------------------------------------------------- 1 | require "multi_json" 2 | require "date" 3 | require "json" 4 | require "openssl" 5 | require "uri" 6 | require "cgi" 7 | 8 | # 9 | # Implements the Transloadit REST API in Ruby. Check the {file:README.md README} 10 | # for usage instructions. 11 | # 12 | class Transloadit 13 | autoload :ApiModel, "transloadit/api_model" 14 | autoload :Assembly, "transloadit/assembly" 15 | autoload :Exception, "transloadit/exception" 16 | autoload :Request, "transloadit/request" 17 | autoload :Response, "transloadit/response" 18 | autoload :SmartCDN, "transloadit/smart_cdn" 19 | autoload :Step, "transloadit/step" 20 | autoload :Template, "transloadit/template" 21 | autoload :VERSION, "transloadit/version" 22 | 23 | # @return [String] your Transloadit auth key 24 | attr_accessor :key 25 | 26 | # @return [String] your Transloadit auth secret, for signing requests 27 | attr_accessor :secret 28 | 29 | # @return [Integer] the duration in seconds that signed API requests 30 | # generated from this instance remain valid 31 | attr_accessor :duration 32 | 33 | attr_accessor :max_size 34 | 35 | # 36 | # Creates a new instance of the Transloadit API. 37 | # 38 | # @param [Hash] options a hash of options, which can be any of: 39 | # @option options [String] :key your auth key from the 40 | # {credentials}[https://transloadit.com/accounts/credentials] page 41 | # (required) 42 | # @option options [String] :secret your auth secret from the 43 | # {credentials}[https://transloadit.com/accounts/credentials] page, for 44 | # signing requests (optional) 45 | # 46 | def initialize(options = {}) 47 | self.key = options[:key] 48 | self.secret = options[:secret] 49 | self.duration = options[:duration] || 5 * 60 50 | self.max_size = options[:max_size] 51 | 52 | _ensure_key_provided 53 | end 54 | 55 | # 56 | # Creates a Transloadit::Step describing a step in an upload assembly. 57 | # 58 | # @param [String] name the name to give the step 59 | # @param [String] robot the robot to use in this step (e.g., '/image/resize') 60 | # @param [Hash] options a hash of options to customize the robot's 61 | # operation; see the {online documentation}[https://transloadit.com/docs/building-assembly-instructions] 62 | # for robot-specific options 63 | # @return [Step] the created Step 64 | # 65 | def step(name, robot, options = {}) 66 | Transloadit::Step.new(name, robot, options) 67 | end 68 | 69 | # 70 | # Creates a Transloadit::Assembly ready to be sent to the REST API. 71 | # 72 | # @param [Hash] options additional parameters to send with the assembly 73 | # submission; for a full list of parameters, see the official 74 | # documentation on {templates}[https://transloadit.com/docs/templates]. 75 | # @option options [Step, Array] :steps the steps to perform in this 76 | # assembly 77 | # @option options [String] :notify_url A URL to be POSTed when the assembly 78 | # has finished processing 79 | # @option options [String] :template_id the ID of a 80 | # {template}[https://transloadit.com/templates] to use instead of 81 | # specifying options here directly 82 | # 83 | def assembly(options = {}) 84 | Transloadit::Assembly.new(self, options) 85 | end 86 | 87 | # 88 | # Creates a Transloadit::Template instance ready to interact with its corresponding REST API. 89 | # 90 | # See the Transloadit {documentation}[https://transloadit.com/docs/api-docs/#template-api] 91 | # for further information on Templates and available endpoints. 92 | # 93 | def template(options = {}) 94 | Transloadit::Template.new(self, options) 95 | end 96 | 97 | # 98 | # Gets user billing reports for specified month and year. 99 | # Defaults to current month or year if corresponding param is not specified. 100 | # 101 | # @param [Integer] month the month for which billing reports should be retrieved. 102 | # defaults to current month if not specified. 103 | # @param [Integer] year the year for which billing reports should be retrieved. 104 | # defaults to current year if not specified. 105 | # 106 | def bill(month = Date.today.month, year = Date.today.year) 107 | # convert month to 2 digit format 108 | month = format "%02d", month 109 | path = "bill/#{year}-#{month}" 110 | 111 | Transloadit::Request.new(path, secret).get({auth: to_hash}) 112 | end 113 | 114 | # 115 | # @return [String] a human-readable version of the Transloadit. 116 | # 117 | def inspect 118 | to_hash.inspect 119 | end 120 | 121 | # 122 | # @return [Hash] a Transloadit-compatible Hash of the instance's contents 123 | # 124 | def to_hash 125 | result = {key: key} 126 | result.update(max_size: max_size) unless max_size.nil? 127 | result.update(expires: _generate_expiry) unless secret.nil? 128 | result 129 | end 130 | 131 | # 132 | # @return [String] JSON-encoded String containing the object's hash contents 133 | # 134 | def to_json 135 | MultiJson.dump(to_hash) 136 | end 137 | 138 | # @param workspace [String] Workspace slug 139 | # @param template [String] Template slug or template ID 140 | # @param input [String] Input value that is provided as `${fields.input}` in the template 141 | # @param url_params [Hash] Additional parameters for the URL query string (optional) 142 | # @param expire_at_ms [Integer] Expiration time as Unix timestamp in milliseconds (optional) 143 | # @return [String] Signed Smart CDN URL 144 | def signed_smart_cdn_url( 145 | workspace:, 146 | template:, 147 | input:, 148 | expire_at_ms: nil, 149 | url_params: {} 150 | ) 151 | raise ArgumentError, "workspace is required" if workspace.nil? || workspace.empty? 152 | raise ArgumentError, "template is required" if template.nil? || template.empty? 153 | raise ArgumentError, "input is required" if input.nil? 154 | 155 | workspace_slug = CGI.escape(workspace) 156 | template_slug = CGI.escape(template) 157 | input_field = CGI.escape(input) 158 | 159 | expire_at = expire_at_ms || (Time.now.to_i * 1000 + 60 * 60 * 1000) # 1 hour default 160 | 161 | query_params = {} 162 | url_params.each do |key, value| 163 | next if value.nil? 164 | Array(value).each do |val| 165 | next if val.nil? 166 | (query_params[key.to_s] ||= []) << val.to_s 167 | end 168 | end 169 | 170 | query_params["auth_key"] = [key] 171 | query_params["exp"] = [expire_at.to_s] 172 | 173 | # Sort parameters to ensure consistent ordering 174 | sorted_params = query_params.sort.flat_map do |key, values| 175 | values.map { |v| "#{CGI.escape(key)}=#{CGI.escape(v)}" } 176 | end.join("&") 177 | 178 | string_to_sign = "#{workspace_slug}/#{template_slug}/#{input_field}?#{sorted_params}" 179 | 180 | signature = OpenSSL::HMAC.hexdigest("sha256", secret, string_to_sign) 181 | 182 | final_params = "#{sorted_params}&sig=#{CGI.escape("sha256:#{signature}")}" 183 | "https://#{workspace_slug}.tlcdn.com/#{template_slug}/#{input_field}?#{final_params}" 184 | end 185 | 186 | private 187 | 188 | # 189 | # Raises an ArgumentError if no {#key} has been assigned. 190 | # 191 | def _ensure_key_provided 192 | unless key 193 | raise ArgumentError, "an authentication key must be provided" 194 | end 195 | end 196 | 197 | # 198 | # Generates an API-compatible request expiration timestamp. Uses the 199 | # current instance's duration. 200 | # 201 | # @return [String] an API-compatible timestamp 202 | # 203 | def _generate_expiry 204 | (Time.now + duration).utc.strftime("%Y/%m/%d %H:%M:%S+00:00") 205 | end 206 | end 207 | -------------------------------------------------------------------------------- /lib/transloadit/api_model.rb: -------------------------------------------------------------------------------- 1 | require "transloadit" 2 | 3 | # 4 | # Represents an API class that more Transloadit specific API classes 5 | # would inherit from. 6 | # 7 | class Transloadit::ApiModel 8 | # @return [Transloadit] the associated Transloadit instance 9 | attr_reader :transloadit 10 | 11 | # @return [Hash] the options describing the Assembly 12 | attr_accessor :options 13 | 14 | # 15 | # Creates a new API instance authenticated using the given +transloadit+ 16 | # instance. 17 | # 18 | # @param [Transloadit] transloadit the associated Transloadit instance 19 | # @param [Hash] options the configuration for the API; 20 | # 21 | def initialize(transloadit, options = {}) 22 | self.transloadit = transloadit 23 | self.options = options 24 | end 25 | 26 | # 27 | # @return [String] a human-readable version of the API 28 | # 29 | def inspect 30 | to_hash.inspect 31 | end 32 | 33 | # 34 | # @return [Hash] a Transloadit-compatible Hash of the API's contents 35 | # 36 | def to_hash 37 | options.merge( 38 | auth: transloadit.to_hash 39 | ).delete_if { |_, v| v.nil? } 40 | end 41 | 42 | # 43 | # @return [String] JSON-encoded String containing the API's contents 44 | # 45 | def to_json 46 | MultiJson.dump(to_hash) 47 | end 48 | 49 | protected 50 | 51 | attr_writer :transloadit 52 | 53 | private 54 | 55 | # 56 | # Performs http request in favour of it's caller 57 | # 58 | # @param [String] path url path to which request is made 59 | # @param [Hash] params POST/GET data to submit with the request 60 | # @param [String] method http request method. This could be 'post' or 'get' 61 | # @param [Hash] extra_params additional POST/GET data to submit with the request 62 | # 63 | # @return [Transloadit::Response] the response 64 | # 65 | def _do_request(path, params = nil, method = "get", extra_params = nil) 66 | unless params.nil? 67 | params = to_hash.update(params) 68 | params = {params: params} if ["post", "put", "delete"].include? method 69 | params.merge!(extra_params) unless extra_params.nil? 70 | end 71 | Transloadit::Request.new(path, transloadit.secret).public_send(method, params) 72 | end 73 | end 74 | -------------------------------------------------------------------------------- /lib/transloadit/assembly.rb: -------------------------------------------------------------------------------- 1 | require "transloadit" 2 | 3 | # 4 | # Represents an Assembly API ready to make calls to the REST API endpoints. 5 | # 6 | # See the Transloadit {documentation}[https://transloadit.com/docs/api-docs/#assembly-api] 7 | # for further information on Assemblies and available endpoints. 8 | # 9 | class Transloadit::Assembly < Transloadit::ApiModel 10 | DEFAULT_TRIES = 3 11 | 12 | # 13 | # @return [Hash] the processing steps, formatted for sending to Transloadit 14 | # 15 | def steps 16 | _wrap_steps_in_hash options[:steps] 17 | end 18 | 19 | # 20 | # Creates a Transloadit::Assembly and sends to the REST API. An 21 | # Assembly can contain one or more Steps for processing or point to a 22 | # server-side template. It's submitted along with a list of files to process, 23 | # at which point Transloadit will process and store the files according to the 24 | # rules in the Assembly. 25 | # See the Transloadit {documentation}[https://transloadit.com/docs/building-assembly-instructions] 26 | # for further information on Assemblies and their parameters. 27 | # 28 | # Accepts as many IO objects as you wish to process in the assembly. 29 | # The last argument is an optional Hash 30 | # of parameters to send along with the request. 31 | # 32 | # @overload create!(*ios) 33 | # @param [Array] *ios the files for the assembly to process 34 | # 35 | # @overload create!(*ios, params = {}) 36 | # @param [Array] *ios the files for the assembly to process 37 | # @param [Hash] params additional POST data to submit with the request; 38 | # for a full list of parameters, see the official documentation 39 | # on {templates}[https://transloadit.com/docs/templates]. 40 | # @option params [Step, Array] :steps the steps to perform in this 41 | # assembly 42 | # @option params [String] :notify_url A URL to be POSTed when the assembly 43 | # has finished processing 44 | # @option params [String] :template_id the ID of a 45 | # {template}[https://transloadit.com/templates] to use instead of 46 | # specifying params here directly 47 | # 48 | def create!(*ios, **params) 49 | params[:steps] = _wrap_steps_in_hash(params[:steps]) unless params[:steps].nil? 50 | 51 | extra_params = {} 52 | extra_params.merge!(options[:fields]) if options[:fields] 53 | 54 | trials = options[:tries] || DEFAULT_TRIES 55 | (1..trials).each do |trial| 56 | # update the payload with file entries 57 | ios.each_with_index { |f, i| extra_params.update "file_#{i}": f } 58 | 59 | response = _do_request( 60 | "/assemblies", params, "post", extra_params 61 | ).extend!(Transloadit::Response::Assembly) 62 | 63 | return response unless response.rate_limit? 64 | 65 | _handle_rate_limit!(response, ios, trial < trials) 66 | end 67 | end 68 | 69 | # 70 | # alias for create! 71 | # keeping this method for backward compatibility 72 | # 73 | def submit!(*ios) 74 | warn "#{caller(1..1).first}: warning: Transloadit::Assembly#submit!" \ 75 | " is deprecated. use Transloadit::Assembly#create! instead" 76 | create!(*ios) 77 | end 78 | 79 | # 80 | # Returns a list of all assemblies 81 | # @param [Hash] additional GET data to submit with the request 82 | # 83 | def list(params = {}) 84 | _do_request("/assemblies", params) 85 | end 86 | 87 | # 88 | # Returns a single assembly object specified by the assembly id 89 | # @param [String] id id of the desired assembly 90 | # 91 | def get(id) 92 | _do_request("/assemblies/#{id}").extend!(Transloadit::Response::Assembly) 93 | end 94 | 95 | # 96 | # Replays an assembly specified by the id 97 | # @param [String] id id of the desired assembly 98 | # @param [Hash] params additional POST data to submit with the request 99 | # 100 | def replay(id, params = {}) 101 | params[:wait] = false 102 | _do_request("/assemblies/#{id}/replay", params, "post").extend!(Transloadit::Response::Assembly) 103 | end 104 | 105 | # 106 | # Returns all assembly notifications 107 | # @param [Hash] params additional GET data to submit with the request 108 | # 109 | def get_notifications(params = {}) 110 | _do_request "/assembly_notifications", params 111 | end 112 | 113 | # 114 | # Replays an assembly notification by the id 115 | # @param [String] id id of the desired assembly 116 | # @param [Hash] params additional POST data to submit with the request 117 | # 118 | def replay_notification(id, params = {}) 119 | _do_request("/assembly_notifications/#{id}/replay", params, "post") 120 | end 121 | 122 | # 123 | # @return [Hash] a Transloadit-compatible Hash of the Assembly's contents 124 | # 125 | def to_hash 126 | options.merge( 127 | auth: transloadit.to_hash, 128 | steps: steps 129 | ).delete_if { |k, v| v.nil? } 130 | end 131 | 132 | private 133 | 134 | # 135 | # Returns a Transloadit-compatible Hash wrapping the +steps+ passed to it. 136 | # Accepts any supported format the +steps+ could come in. 137 | # 138 | # @param [nil, Hash, Step, Array] steps the steps to encode 139 | # @return [Hash] the Transloadit-compatible hash of steps 140 | # 141 | def _wrap_steps_in_hash(steps) 142 | case steps 143 | when nil then steps 144 | when Hash then steps 145 | when Transloadit::Step then steps.to_hash 146 | else 147 | if steps.uniq(&:name) != steps 148 | raise ArgumentError, "There are different Assembly steps using the same name" 149 | end 150 | steps.inject({}) { |h, s| h.update s } 151 | end 152 | end 153 | 154 | # 155 | # Stays idle for certain time and then reopens assembly files for reprocessing. 156 | # Should be called when assembly rate limit is reached. 157 | # 158 | # @param [Response] response assembly response that comes with a rate limit 159 | # @param [Array] ios the files sent for the assembly to process. 160 | # 161 | def _handle_rate_limit!(response, ios, is_retrying) 162 | if is_retrying 163 | warn "Rate limit reached. Waiting for #{response.wait_time} seconds before retrying." 164 | sleep response.wait_time 165 | # RestClient closes file streams at the end of a request. 166 | ios.collect! { |file| File.open file.path } 167 | else 168 | raise Transloadit::Exception::RateLimitReached.new(response) 169 | end 170 | end 171 | end 172 | -------------------------------------------------------------------------------- /lib/transloadit/exception.rb: -------------------------------------------------------------------------------- 1 | require "transloadit" 2 | 3 | require "rest-client" 4 | 5 | module Transloadit::Exception 6 | # 7 | # Exception raised when Rate limit error response is returned from the API. 8 | # See {Rate Limiting}[https://transloadit.com/docs/api-docs/#rate-limiting] 9 | # 10 | class RateLimitReached < RestClient::RequestEntityTooLarge 11 | def default_message 12 | retry_msg = " Retry in #{@response.wait_time} seconds" if @response 13 | "Transloadit Rate Limit Reached.#{retry_msg}" 14 | end 15 | end 16 | 17 | # 18 | # Exception raised when Response#reload_until_finished! reaches limit specified in :tries option 19 | # 20 | class ReloadLimitReached < StandardError 21 | def message 22 | "reload_until_finished! reached limit specified in :tries option. This is not a rate limit and you may continue to poll for updates." 23 | end 24 | end 25 | end 26 | -------------------------------------------------------------------------------- /lib/transloadit/request.rb: -------------------------------------------------------------------------------- 1 | require "transloadit" 2 | 3 | require "rest-client" 4 | require "openssl" 5 | 6 | # 7 | # Wraps requests to the Transloadit API. Ensures all API requests return a 8 | # parsed Transloadit::Response, and abstracts away finding a lightly-used 9 | # instance on startup. 10 | # 11 | class Transloadit::Request 12 | # The default Transloadit API endpoint. 13 | API_ENDPOINT = URI.parse("https://api2.transloadit.com/") 14 | 15 | # The default headers to send to the API. 16 | API_HEADERS = {"Transloadit-Client" => "ruby-sdk:#{Transloadit::VERSION}"} 17 | 18 | # The HMAC algorithm used for calculation request signatures. 19 | HMAC_ALGORITHM = OpenSSL::Digest.new("sha384") 20 | 21 | # @return [String] the API endpoint for the request 22 | attr_reader :url 23 | 24 | # @return [String] the authentication secret to sign the request with 25 | attr_accessor :secret 26 | 27 | # 28 | # Prepares a request against an endpoint URL, optionally with an encryption 29 | # secret. If only a path is passed, the API will automatically determine the 30 | # best server to use. If a full URL is given, the host provided will be 31 | # used. 32 | # 33 | # @param [String] url the API endpoint 34 | # @param [String] secret an optional secret with which to sign the request 35 | # 36 | def initialize(url, secret = nil) 37 | self.url = URI.parse(url.to_s) 38 | self.secret = secret 39 | end 40 | 41 | # 42 | # Performs an HTTP GET to the request's URL. Takes an optional hash of 43 | # query params. 44 | # 45 | # @param [Hash] params additional query parameters 46 | # @return [Transloadit::Response] the response 47 | # 48 | def get(params = {}) 49 | request! do 50 | api[url.path + to_query(params)].get(API_HEADERS) 51 | end 52 | end 53 | 54 | # 55 | # Performs an HTTP DELETE to the request's URL. Takes an optional hash 56 | # containing the form-encoded payload. 57 | # 58 | # @param [Hash] payload the payload to form-encode along with the POST 59 | # @return [Transloadit::Response] the response 60 | # 61 | def delete(payload = {}) 62 | request! do 63 | options = {payload: to_payload(payload)} 64 | api(options)[url.path].delete(API_HEADERS) 65 | end 66 | end 67 | 68 | # 69 | # Performs an HTTP POST to the request's URL. Takes an optional hash 70 | # containing the form-encoded payload. 71 | # 72 | # @param [Hash] payload the payload to form-encode along with the POST 73 | # @return [Transloadit::Response] the response 74 | # 75 | def post(payload = {}) 76 | request! do 77 | api[url.path].post(to_payload(payload), API_HEADERS) 78 | end 79 | end 80 | 81 | # 82 | # Performs an HTTP PUT to the request's URL. Takes an optional hash 83 | # containing the form-encoded payload. 84 | # 85 | # @param [Hash] payload the payload to form-encode along with the POST 86 | # @return [Transloadit::Response] the response 87 | # 88 | def put(payload = {}) 89 | request! do 90 | api[url.path].put(to_payload(payload), API_HEADERS) 91 | end 92 | end 93 | 94 | # 95 | # @return [String] a human-readable version of the prepared Request 96 | # 97 | def inspect 98 | url.to_s.inspect 99 | end 100 | 101 | # 102 | # Computes an HMAC digest from the key and message. 103 | # 104 | # @param [String] key the secret key to sign with 105 | # @param [String] message the message to sign 106 | # @return [String] the signature of the message 107 | # 108 | def self._hmac(key, message) 109 | OpenSSL::HMAC.hexdigest HMAC_ALGORITHM, key, message 110 | end 111 | 112 | protected 113 | 114 | attr_writer :url 115 | 116 | # 117 | # Retrieves the current API endpoint. If the URL of the request contains a 118 | # hostname, then the hostname is used as the base endpoint of the API. 119 | # Otherwise uses the class-level API base. 120 | # 121 | # @return [RestClient::Resource] the API endpoint for this instance 122 | # 123 | def api(options = {}) 124 | @api ||= case url.host 125 | when String then RestClient::Resource.new("#{url.scheme}://#{url.host}", options) 126 | else RestClient::Resource.new("#{API_ENDPOINT.scheme}://#{API_ENDPOINT.host}", options) 127 | end 128 | end 129 | 130 | # 131 | # Updates the POST payload passed to be compliant with the Transloadit API 132 | # spec. JSONifies the value for the +params+ key and signs the request with 133 | # the instance's +secret+ if it exists. 134 | # 135 | # @param [Hash] the payload to update 136 | # @return [Hash] the Transloadit-compliant POST payload 137 | # 138 | def to_payload(payload = nil) 139 | return {} if payload.nil? 140 | return {} if payload.respond_to?(:empty?) && payload.empty? 141 | 142 | # Create a new hash with JSONified params and a signature if a secret was provided. 143 | # Note: We first set :params and :signature to ensure that these are the first fields 144 | # in the multipart requests, before any file. Otherwise, a signature will only be transmitted 145 | # after all files have been uploaded. The order of the fields in a multipart request 146 | # follows the order of the entries in the returned hash here. 147 | # See https://github.com/transloadit/ruby-sdk/issues/51 148 | new_payload = { 149 | params: MultiJson.dump(payload[:params]) 150 | } 151 | sig = signature(new_payload[:params]) 152 | new_payload[:signature] = sig unless sig.nil? 153 | 154 | # Copy all values, excluding :params and :signature keys. 155 | new_payload.update payload.reject { |key, _| key == :params || key == :signature } 156 | 157 | new_payload 158 | end 159 | 160 | # 161 | # Updates the GET/DELETE params hash to be compliant with the Transloadit 162 | # API by URI escaping and encoding the params hash, and attaching a 163 | # signature. 164 | # 165 | # @param [Hash] params the params to encode 166 | # @return [String] the URI-encoded and escaped query parameters 167 | # 168 | def to_query(params = nil) 169 | return "" if params.nil? 170 | return "" if params.respond_to?(:empty?) && params.empty? 171 | 172 | params_in_json = MultiJson.dump(params) 173 | uri_params = URI.encode_www_form_component(params_in_json) 174 | 175 | params = { 176 | params: uri_params, 177 | signature: signature(params_in_json) 178 | } 179 | 180 | "?" + params.map { |k, v| "#{k}=#{v}" if v }.compact.join("&") 181 | end 182 | 183 | # 184 | # Wraps a request's results in a Transloadit::Response, even if an exception 185 | # is raised by RestClient. 186 | # 187 | def request!(&request) 188 | Transloadit::Response.new yield 189 | rescue RestClient::Exception => e 190 | # The response attribute can be nil, for example for RestClient::Exceptions::OpenTimeout exceptions. 191 | # Then, we cannot convert them into a Transloadit::Response, so instead we raise them again for 192 | # the user to be visible. 193 | # See https://github.com/transloadit/ruby-sdk/issues/53 194 | raise e if e.response.nil? 195 | Transloadit::Response.new e.response 196 | end 197 | 198 | # 199 | # Computes the HMAC digest of the params hash, if a secret was given to the 200 | # instance. 201 | # 202 | # @param [String] params the JSON encoded payload to sign 203 | # @return [String] the HMAC signature for the params 204 | # 205 | def signature(params) 206 | "sha384:" + self.class._hmac(secret, params) if secret.to_s.length > 0 207 | end 208 | end 209 | -------------------------------------------------------------------------------- /lib/transloadit/response.rb: -------------------------------------------------------------------------------- 1 | require "transloadit" 2 | 3 | require "rest-client" 4 | require "delegate" 5 | 6 | class Transloadit::Response < Delegator 7 | autoload :Assembly, "transloadit/response/assembly" 8 | 9 | # 10 | # Creates an enhanced response wrapped around a RestClient response. 11 | # 12 | # @param [RestClient::Response] response the JSON response to wrap 13 | # 14 | def initialize(response) 15 | __setobj__(response) 16 | end 17 | 18 | # 19 | # Returns the attribute from the JSON response. 20 | # 21 | # @param [String] attribute the attribute name to look up 22 | # @return [String] the value for the attribute 23 | # 24 | def [](attribute) 25 | body[attribute.to_s] 26 | end 27 | 28 | # 29 | # Returns the parsed JSON body. 30 | # 31 | # @return [Hash] the parsed JSON body hash 32 | # 33 | def body 34 | MultiJson.load __getobj__.body 35 | end 36 | 37 | # 38 | # Inspects the body of the response. 39 | # 40 | # @return [String] a human-readable version of the body 41 | # 42 | def inspect 43 | body.inspect 44 | end 45 | 46 | # 47 | # Chainably extends the response with additional methods. Used to add 48 | # context-specific functionality to a response. 49 | # 50 | # @param [Module] mod the module to extend with 51 | # @return [Transloadit::Response] the extended response 52 | # 53 | def extend!(mod) 54 | extend(mod) 55 | self 56 | end 57 | 58 | protected 59 | 60 | # 61 | # The object to delegate method calls to. 62 | # 63 | # @return [RestClient::Response] 64 | # 65 | def __getobj__ 66 | @response 67 | end 68 | 69 | # 70 | # Sets the object to delegate method calls to. 71 | # 72 | # @param [RestClient::Response] response the response to delegate to 73 | # @return [RestClient::Response] the delegated response 74 | # 75 | def __setobj__(response) 76 | @response = response 77 | end 78 | 79 | # 80 | # Replaces the object this instance delegates to with the one the other 81 | # object uses. 82 | # 83 | # @param [Delegator] other the object whose delegate to use 84 | # @return [Transloadit::Response] this response 85 | # 86 | def replace(other) 87 | __setobj__ other.__getobj__ 88 | self 89 | end 90 | end 91 | -------------------------------------------------------------------------------- /lib/transloadit/response/assembly.rb: -------------------------------------------------------------------------------- 1 | require "transloadit" 2 | 3 | module Transloadit::Response::Assembly 4 | def reload! 5 | replace Transloadit::Request.new(self["assembly_ssl_url"]).get 6 | end 7 | 8 | def cancel! 9 | replace Transloadit::Request.new(self["assembly_ssl_url"]).delete 10 | end 11 | 12 | def aborted? 13 | self["ok"] == "REQUEST_ABORTED" 14 | end 15 | 16 | def canceled? 17 | self["ok"] == "ASSEMBLY_CANCELED" 18 | end 19 | 20 | def completed? 21 | self["ok"] == "ASSEMBLY_COMPLETED" 22 | end 23 | 24 | def error? 25 | self["error"] != nil 26 | end 27 | 28 | def executing? 29 | self["ok"] == "ASSEMBLY_EXECUTING" 30 | end 31 | 32 | def replaying? 33 | self["ok"] == "ASSEMBLY_REPLAYING" 34 | end 35 | 36 | def finished? 37 | aborted? || canceled? || completed? || error? 38 | end 39 | 40 | def uploading? 41 | self["ok"] == "ASSEMBLY_UPLOADING" 42 | end 43 | 44 | def rate_limit? 45 | self["error"] == "RATE_LIMIT_REACHED" 46 | end 47 | 48 | def wait_time 49 | self["info"]["retryIn"] || 0 50 | end 51 | 52 | DEFAULT_RELOAD_TRIES = 600 53 | 54 | def reload_until_finished!(options = {}) 55 | tries = options[:tries] || DEFAULT_RELOAD_TRIES 56 | 57 | tries.times do 58 | sleep 1 59 | reload! 60 | return self if finished? 61 | end 62 | 63 | raise Transloadit::Exception::ReloadLimitReached 64 | end 65 | end 66 | -------------------------------------------------------------------------------- /lib/transloadit/step.rb: -------------------------------------------------------------------------------- 1 | require "transloadit" 2 | 3 | # 4 | # Implements the concept of a step in the Transloadit API. Each Step has a 5 | # +robot+ (e.g., '/image/resize' or '/video/thumbnail') and a hash of 6 | # +options+ specific to the chosen robot. 7 | # 8 | # See the Transloadit {documentation}[https://transloadit.com/docs/building-assembly-instructions] 9 | # for further information on robot types and their parameters. 10 | # 11 | class Transloadit::Step 12 | # @return [String] the name to give the step 13 | attr_accessor :name 14 | 15 | # @return [String] the robot to use 16 | attr_reader :robot 17 | 18 | # @return [Hash] the robot's options 19 | attr_accessor :options 20 | 21 | # 22 | # Creates a new Step with the given +robot+. 23 | # 24 | # @param [String] name the explicit name to give the step 25 | # @param [String] robot the robot to use 26 | # @param [Hash] options the configuration options for the robot; see 27 | # {Transloadit#step} for possible values 28 | # 29 | def initialize(name, robot, options = {}) 30 | self.name = name 31 | self.robot = robot 32 | self.options = options 33 | end 34 | 35 | # 36 | # Specifies that this Step should process the provided +input+ instead of 37 | # the output of the Step before it. 38 | # 39 | # @param [Step, Array, Symbol, nil] input The input 40 | # step to use. Follows the conventions outlined in the 41 | # online {documentation}[https://transloadit.com/docs/building-assembly-instructions#special-parameters]. 42 | # The symbol +:original+ specifies that the original file should be sent 43 | # to the robot. A Step indicates that this Step's output should be used 44 | # as the input to this one. Likewise, an array of Steps tells Transloadit 45 | # to use pass each of their outputs to this Step. And lastly, an explicit 46 | # nil clears the setting and restores it to its default input. 47 | # 48 | # @return [String, Array, nil> The value for the +:use+ parameter 49 | # that will actually be sent to the REST API. 50 | # 51 | def use(input) 52 | options.delete(:use) && return if input.nil? 53 | 54 | options[:use] = case input 55 | when Symbol then input.inspect 56 | when Array then input.map { |i| i.name } 57 | else [input.name] 58 | end 59 | end 60 | 61 | # 62 | # @return [String] a human-readable version of the Step 63 | # 64 | def inspect 65 | to_hash[name].inspect 66 | end 67 | 68 | # 69 | # @return [Hash] a Transloadit-compatible Hash of the Step's contents 70 | # 71 | def to_hash 72 | {name => options.merge(robot: robot)} 73 | end 74 | 75 | # 76 | # @return [String] JSON-encoded String containing the Step's hash contents 77 | # 78 | def to_json 79 | MultiJson.dump(to_hash) 80 | end 81 | 82 | protected 83 | 84 | attr_writer :robot 85 | end 86 | -------------------------------------------------------------------------------- /lib/transloadit/template.rb: -------------------------------------------------------------------------------- 1 | require "transloadit" 2 | 3 | # 4 | # Represents a Template API ready to interact with its corresponding REST API. 5 | # 6 | # See the Transloadit {documentation}[https://transloadit.com/docs/api-docs/#template-api] 7 | # for further information on Templates and their parameters. 8 | # 9 | class Transloadit::Template < Transloadit::ApiModel 10 | # 11 | # Submits a template to be created. 12 | # 13 | # @param [Hash] params POST data to submit with the request. 14 | # must contain keys 'name' and 'template' 15 | # 16 | # @option params [String] :name name assigned to the newly created template 17 | # @option params [Hash] :template key, value pair of template content 18 | # see {template}[https://transloadit.com/templates] 19 | # 20 | def create(params) 21 | _do_request("/templates", params, "post") 22 | end 23 | 24 | # 25 | # Returns a list of all templates 26 | # @param [Hash] additional GET data to submit with the request 27 | # 28 | def list(params = {}) 29 | _do_request("/templates", params) 30 | end 31 | 32 | # 33 | # Returns a single template object specified by the template id 34 | # @param [String] id id of the desired template 35 | # @param [Hash] additional GET data to submit with the request 36 | # 37 | def get(id, params = {}) 38 | _do_request("/templates/#{id}", params) 39 | end 40 | 41 | # 42 | # Updates the template object specified by the template id 43 | # @param [String] id id of the desired template 44 | # @param [Hash] additional POST data to submit with the request 45 | # must contain keys 'name' and 'template' 46 | # 47 | # @option params [String] :name name assigned to the newly created template 48 | # @option params [Hash] :template key, value pair of template content 49 | # see {template}[https://transloadit.com/templates] 50 | # 51 | def update(id, params = {}) 52 | _do_request("/templates/#{id}", params, "put") 53 | end 54 | 55 | # 56 | # Deletes the template object specified by the template id 57 | # @param [String] id id of the desired template 58 | # @param [Hash] additional POST data to submit with the request 59 | # 60 | def delete(id, params = {}) 61 | _do_request("/templates/#{id}", params, "delete") 62 | end 63 | end 64 | -------------------------------------------------------------------------------- /lib/transloadit/version.rb: -------------------------------------------------------------------------------- 1 | class Transloadit 2 | VERSION = "3.1.0" 3 | end 4 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/cancel_assembly.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: delete 5 | uri: https://api2.jane.transloadit.com/assemblies/76fe5df1c93a0a530f3e583805cf98b4 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - ! '*/*; q=0.5, application/xml' 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Transloadit Ruby SDK 0.1.2 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Content-Type: 22 | - text/plain 23 | Access-Control-Allow-Origin: 24 | - ! '*' 25 | Access-Control-Allow-Methods: 26 | - POST, GET, PUT, DELETE, OPTIONS 27 | Access-Control-Allow-Headers: 28 | - X-Requested-With, Content-Type, Accept, Content-Length 29 | Transfer-Encoding: 30 | - chunked 31 | body: 32 | encoding: UTF-8 33 | string: ! '{"ok":"ASSEMBLY_CANCELED","message":"The assembly was canceled.","assembly_id":"76fe5df1c93a0a530f3e583805cf98b4","assembly_ssl_url":"https://api2.jane.transloadit.com/assemblies/76fe5df1c93a0a530f3e583805cf98b4","bytes_received":95355,"bytes_expected":95355,"client_agent":"Transloadit 34 | Ruby SDK 0.1.2","client_ip":"74.95.29.209","client_referer":null,"start_date":"2011/02/21 35 | 15:54:17 GMT","upload_duration":0.394,"execution_duration":0.006,"fields":{},"uploads":[{"id":"0767b43537389a177a1be96ab06e8a4b","name":"test.png","basename":"test","ext":"png","size":95010,"mime":"image/jpeg","type":"image","field":"file_0","original_id":"0767b43537389a177a1be96ab06e8a4b","ssl_url":"https://tmp.jane.transloadit.com/upload/bf7801cb21fb96cbafd403467244d125.png","meta":{"width":640,"height":480,"date_recorded":null,"date_file_created":null,"date_file_modified":"2011/02/21 36 | 15:54:17 GMT","title":null,"description":null,"location":null,"city":null,"state":null,"country":null,"country_code":null,"keywords":"Photo 37 | Booth","aperture":null,"exposure_compensation":null,"exposure_mode":null,"exposure_time":null,"flash":null,"focal_length":null,"f_number":null,"iso":null,"light_value":null,"metering_mode":null,"shutter_speed":null,"white_balance":null,"device_name":null,"device_vendor":null,"device_software":null,"latitude":null,"longitude":null,"frame_count":null}}],"results":{}}' 38 | http_version: '1.1' 39 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 40 | recorded_with: VCR 2.4.0 41 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/create_template.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api2.transloadit.com/templates 6 | body: 7 | encoding: UTF-8 8 | string: params=%7B%22auth%22%3A%7B%22key%22%3A%22%22%7D%2C%22name%22%3A%22foo%22%2C%22template%22%3A%7B%22key%22%3A%22value%22%7D%7D 9 | headers: 10 | Accept: 11 | - "*/*" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Transloadit Ruby SDK 1.2.0 16 | Content-Length: 17 | - '124' 18 | Content-Type: 19 | - application/x-www-form-urlencoded 20 | Host: 21 | - api2.transloadit.com 22 | response: 23 | status: 24 | code: 200 25 | message: OK 26 | headers: 27 | Access-Control-Allow-Headers: 28 | - X-Requested-With, Content-Type, Cache-Control, Accept, Content-Length 29 | Access-Control-Allow-Methods: 30 | - POST, GET, PUT, DELETE, OPTIONS 31 | Access-Control-Allow-Origin: 32 | - "*" 33 | Cache-Control: 34 | - no-cache 35 | Content-Type: 36 | - application/json; charset=utf-8 37 | Date: 38 | - Thu, 25 Aug 2016 10:13:30 GMT 39 | Content-Length: 40 | - '104' 41 | Connection: 42 | - keep-alive 43 | body: 44 | encoding: UTF-8 45 | string: '{"ok":"TEMPLATE_CREATED","template_name":"foo","template_content": {"key": "value"}}' 46 | http_version: 47 | recorded_at: Thu, 25 Aug 2016 10:13:31 GMT 48 | recorded_with: VCR 3.0.3 49 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/delete_template.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: delete 5 | uri: https://api2.transloadit.com/templates/55c965a063a311e6ba2d379ef10b28f7 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - "*/*" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Transloadit Ruby SDK 1.2.0 16 | Host: 17 | - api2.transloadit.com 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Access-Control-Allow-Headers: 24 | - X-Requested-With, Content-Type, Cache-Control, Accept, Content-Length 25 | Access-Control-Allow-Methods: 26 | - POST, GET, PUT, DELETE, OPTIONS 27 | Access-Control-Allow-Origin: 28 | - "*" 29 | Cache-Control: 30 | - no-cache 31 | Content-Type: 32 | - application/json; charset=utf-8 33 | Date: 34 | - Wed, 24 Aug 2016 11:15:20 GMT 35 | Content-Length: 36 | - '65' 37 | Connection: 38 | - keep-alive 39 | body: 40 | encoding: UTF-8 41 | string: '{"ok":"TEMPLATE_DELETED","message":"Your template was successfully deleted."}' 42 | http_version: 43 | recorded_at: Wed, 24 Aug 2016 11:15:19 GMT 44 | recorded_with: VCR 3.0.3 45 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/fetch_assemblies.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api2.transloadit.com/assemblies?params=%7B%22auth%22:%7B%22key%22:%22%22%7D%7D 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - "*/*" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Transloadit Ruby SDK 1.2.0 16 | Host: 17 | - api2.transloadit.com 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Access-Control-Allow-Headers: 24 | - X-Requested-With, Content-Type, Cache-Control, Accept, Content-Length 25 | Access-Control-Allow-Methods: 26 | - POST, GET, PUT, DELETE, OPTIONS 27 | Access-Control-Allow-Origin: 28 | - "*" 29 | Cache-Control: 30 | - no-cache 31 | Content-Type: 32 | - application/json; charset=utf-8 33 | Date: 34 | - Tue, 16 Aug 2016 09:09:24 GMT 35 | Content-Length: 36 | - '22' 37 | Connection: 38 | - keep-alive 39 | body: 40 | encoding: UTF-8 41 | string: '{"items":[],"count":0}' 42 | http_version: 43 | recorded_at: Tue, 16 Aug 2016 09:09:24 GMT 44 | recorded_with: VCR 3.0.3 45 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/fetch_assembly_aborted.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api2.jane.transloadit.com/assemblies/76fe5df1c93a0a530f3e583805cf98b4 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - ! '*/*; q=0.5, application/xml' 12 | Accept-Encoding: 13 | - gzip, deflate 14 | response: 15 | status: 16 | code: 200 17 | message: OK 18 | headers: 19 | Content-Type: 20 | - text/plain 21 | Access-Control-Allow-Origin: 22 | - ! '*' 23 | Access-Control-Allow-Methods: 24 | - POST, GET, PUT, DELETE, OPTIONS 25 | Access-Control-Allow-Headers: 26 | - X-Requested-With, Content-Type, Accept, Content-Length 27 | Transfer-Encoding: 28 | - chunked 29 | body: 30 | encoding: UTF-8 31 | string: ! '{"ok":"REQUEST_ABORTED","message":"The upload connection was closed or timed out before receiving all data.","assembly_id":"76fe5df1c93a0a530f3e583805cf98b4","assembly_ssl_url":"https://api2.jane.transloadit.com/assemblies/76fe5df1c93a0a530f3e583805cf98b4","bytes_received":95355,"bytes_expected":95355,"client_agent":"Transloadit 32 | Ruby SDK 0.1.2","client_ip":"74.95.29.209","client_referer":null,"start_date":"2011/02/21 33 | 15:54:17 GMT","upload_duration":0.394,"execution_duration":0.006,"fields":{},"uploads":[{"id":"0767b43537389a177a1be96ab06e8a4b","name":"test.png","basename":"test","ext":"png","size":95010,"mime":"image/jpeg","type":"image","field":"file_0","original_id":"0767b43537389a177a1be96ab06e8a4b","ssl_url":"https://tmp.jane.transloadit.com/upload/bf7801cb21fb96cbafd403467244d125.png","meta":{"width":640,"height":480,"date_recorded":null,"date_file_created":null,"date_file_modified":"2011/02/21 34 | 15:54:17 GMT","title":null,"description":null,"location":null,"city":null,"state":null,"country":null,"country_code":null,"keywords":"Photo 35 | Booth","aperture":null,"exposure_compensation":null,"exposure_mode":null,"exposure_time":null,"flash":null,"focal_length":null,"f_number":null,"iso":null,"light_value":null,"metering_mode":null,"shutter_speed":null,"white_balance":null,"device_name":null,"device_vendor":null,"device_software":null,"latitude":null,"longitude":null,"frame_count":null}}],"results":{}}' 36 | http_version: '1.1' 37 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 38 | recorded_with: VCR 2.4.0 39 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/fetch_assembly_errors.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api2.jane.transloadit.com/assemblies/76fe5df1c93a0a530f3e583805cf98b4 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - ! '*/*; q=0.5, application/xml' 12 | Accept-Encoding: 13 | - gzip, deflate 14 | response: 15 | status: 16 | code: 200 17 | message: OK 18 | headers: 19 | Content-Type: 20 | - text/plain 21 | Access-Control-Allow-Origin: 22 | - ! '*' 23 | Access-Control-Allow-Methods: 24 | - POST, GET, PUT, DELETE, OPTIONS 25 | Access-Control-Allow-Headers: 26 | - X-Requested-With, Content-Type, Accept, Content-Length 27 | Transfer-Encoding: 28 | - chunked 29 | body: 30 | encoding: UTF-8 31 | string: ! '{"ok":"ASSEMBLY_CANCELED","message":"The assembly is still in the process of being uploaded.","error": "INVALID_FORM_DATA", "assembly_id":"76fe5df1c93a0a530f3e583805cf98b4","assembly_ssl_url":"https://api2.jane.transloadit.com/assemblies/76fe5df1c93a0a530f3e583805cf98b4","bytes_received":95355,"bytes_expected":95355,"client_agent":"Transloadit 32 | Ruby SDK 0.1.2","client_ip":"74.95.29.209","client_referer":null,"start_date":"2011/02/21 33 | 15:54:17 GMT","upload_duration":0.394,"execution_duration":0.006,"fields":{},"uploads":[{"id":"0767b43537389a177a1be96ab06e8a4b","name":"test.png","basename":"test","ext":"png","size":95010,"mime":"image/jpeg","type":"image","field":"file_0","original_id":"0767b43537389a177a1be96ab06e8a4b","ssl_url":"https://tmp.jane.transloadit.com/upload/bf7801cb21fb96cbafd403467244d125.png","meta":{"width":640,"height":480,"date_recorded":null,"date_file_created":null,"date_file_modified":"2011/02/21 34 | 15:54:17 GMT","title":null,"description":null,"location":null,"city":null,"state":null,"country":null,"country_code":null,"keywords":"Photo 35 | Booth","aperture":null,"exposure_compensation":null,"exposure_mode":null,"exposure_time":null,"flash":null,"focal_length":null,"f_number":null,"iso":null,"light_value":null,"metering_mode":null,"shutter_speed":null,"white_balance":null,"device_name":null,"device_vendor":null,"device_software":null,"latitude":null,"longitude":null,"frame_count":null}}],"results":{}}' 36 | http_version: '1.1' 37 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 38 | recorded_with: VCR 2.4.0 39 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/fetch_assembly_executing.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api2.jane.transloadit.com/assemblies/76fe5df1c93a0a530f3e583805cf98b4 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - ! '*/*; q=0.5, application/xml' 12 | Accept-Encoding: 13 | - gzip, deflate 14 | response: 15 | status: 16 | code: 200 17 | message: OK 18 | headers: 19 | Content-Type: 20 | - text/plain 21 | Access-Control-Allow-Origin: 22 | - ! '*' 23 | Access-Control-Allow-Methods: 24 | - POST, GET, PUT, DELETE, OPTIONS 25 | Access-Control-Allow-Headers: 26 | - X-Requested-With, Content-Type, Accept, Content-Length 27 | Transfer-Encoding: 28 | - chunked 29 | body: 30 | encoding: UTF-8 31 | string: ! '{"ok":"ASSEMBLY_EXECUTING","message":"The assembly is currently being executed.","assembly_id":"76fe5df1c93a0a530f3e583805cf98b4","assembly_ssl_url":"https://api2.jane.transloadit.com/assemblies/76fe5df1c93a0a530f3e583805cf98b4","bytes_received":95355,"bytes_expected":95355,"client_agent":"Transloadit 32 | Ruby SDK 0.1.2","client_ip":"74.95.29.209","client_referer":null,"start_date":"2011/02/21 33 | 15:54:17 GMT","upload_duration":0.394,"execution_duration":0.006,"fields":{},"uploads":[{"id":"0767b43537389a177a1be96ab06e8a4b","name":"test.png","basename":"test","ext":"png","size":95010,"mime":"image/jpeg","type":"image","field":"file_0","original_id":"0767b43537389a177a1be96ab06e8a4b","ssl_url":"https://tmp.jane.transloadit.com/upload/bf7801cb21fb96cbafd403467244d125.png","meta":{"width":640,"height":480,"date_recorded":null,"date_file_created":null,"date_file_modified":"2011/02/21 34 | 15:54:17 GMT","title":null,"description":null,"location":null,"city":null,"state":null,"country":null,"country_code":null,"keywords":"Photo 35 | Booth","aperture":null,"exposure_compensation":null,"exposure_mode":null,"exposure_time":null,"flash":null,"focal_length":null,"f_number":null,"iso":null,"light_value":null,"metering_mode":null,"shutter_speed":null,"white_balance":null,"device_name":null,"device_vendor":null,"device_software":null,"latitude":null,"longitude":null,"frame_count":null}}],"results":{}}' 36 | http_version: '1.1' 37 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 38 | recorded_with: VCR 2.4.0 39 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/fetch_assembly_notifications.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api2.transloadit.com/assembly_notifications?params=%7B%22auth%22:%7B%22key%22:%22%22%7D%7D 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - "*/*" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Transloadit Ruby SDK 1.2.0 16 | Host: 17 | - api2.transloadit.com 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Access-Control-Allow-Headers: 24 | - X-Requested-With, Content-Type, Cache-Control, Accept, Content-Length 25 | Access-Control-Allow-Methods: 26 | - POST, GET, PUT, DELETE, OPTIONS 27 | Access-Control-Allow-Origin: 28 | - "*" 29 | Cache-Control: 30 | - no-cache 31 | Content-Type: 32 | - application/json; charset=utf-8 33 | Date: 34 | - Tue, 16 Aug 2016 10:38:22 GMT 35 | Content-Length: 36 | - '22' 37 | Connection: 38 | - keep-alive 39 | body: 40 | encoding: UTF-8 41 | string: '{"items":[],"count":0}' 42 | http_version: 43 | recorded_at: Tue, 16 Aug 2016 10:38:22 GMT 44 | recorded_with: VCR 3.0.3 45 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/fetch_assembly_ok.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api2.transloadit.com/assemblies/76fe5df1c93a0a530f3e583805cf98b4 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - ! '*/*; q=0.5, application/xml' 12 | Accept-Encoding: 13 | - gzip, deflate 14 | response: 15 | status: 16 | code: 200 17 | message: OK 18 | headers: 19 | Content-Type: 20 | - text/plain 21 | Access-Control-Allow-Origin: 22 | - ! '*' 23 | Access-Control-Allow-Methods: 24 | - POST, GET, PUT, DELETE, OPTIONS 25 | Access-Control-Allow-Headers: 26 | - X-Requested-With, Content-Type, Accept, Content-Length 27 | Transfer-Encoding: 28 | - chunked 29 | body: 30 | encoding: UTF-8 31 | string: ! '{"ok":"ASSEMBLY_COMPLETED","message":"The assembly was successfully 32 | completed.","assembly_id":"76fe5df1c93a0a530f3e583805cf98b4","assembly_ssl_url":"https://api2.jane.transloadit.com/assemblies/76fe5df1c93a0a530f3e583805cf98b4","bytes_received":95355,"bytes_expected":95355,"client_agent":"Transloadit 33 | Ruby SDK 0.1.2","client_ip":"74.95.29.209","client_referer":null,"start_date":"2011/02/21 34 | 15:54:17 GMT","upload_duration":0.394,"execution_duration":0.006,"fields":{},"uploads":[{"id":"0767b43537389a177a1be96ab06e8a4b","name":"test.png","basename":"test","ext":"png","size":95010,"mime":"image/jpeg","type":"image","field":"file_0","original_id":"0767b43537389a177a1be96ab06e8a4b","ssl_url":"https://tmp.jane.transloadit.com/upload/bf7801cb21fb96cbafd403467244d125.png","meta":{"width":640,"height":480,"date_recorded":null,"date_file_created":null,"date_file_modified":"2011/02/21 35 | 15:54:17 GMT","title":null,"description":null,"location":null,"city":null,"state":null,"country":null,"country_code":null,"keywords":"Photo 36 | Booth","aperture":null,"exposure_compensation":null,"exposure_mode":null,"exposure_time":null,"flash":null,"focal_length":null,"f_number":null,"iso":null,"light_value":null,"metering_mode":null,"shutter_speed":null,"white_balance":null,"device_name":null,"device_vendor":null,"device_software":null,"latitude":null,"longitude":null,"frame_count":null}}],"results":{}}' 37 | http_version: '1.1' 38 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 39 | - request: 40 | method: get 41 | uri: https://api2.jane.transloadit.com/assemblies/76fe5df1c93a0a530f3e583805cf98b4 42 | body: 43 | encoding: US-ASCII 44 | string: '' 45 | headers: 46 | Accept: 47 | - ! '*/*; q=0.5, application/xml' 48 | Accept-Encoding: 49 | - gzip, deflate 50 | response: 51 | status: 52 | code: 200 53 | message: OK 54 | headers: 55 | Content-Type: 56 | - text/plain 57 | Access-Control-Allow-Origin: 58 | - ! '*' 59 | Access-Control-Allow-Methods: 60 | - POST, GET, PUT, DELETE, OPTIONS 61 | Access-Control-Allow-Headers: 62 | - X-Requested-With, Content-Type, Accept, Content-Length 63 | Transfer-Encoding: 64 | - chunked 65 | body: 66 | encoding: UTF-8 67 | string: ! '{"ok":"ASSEMBLY_COMPLETED","message":"The assembly was successfully 68 | completed.","assembly_id":"76fe5df1c93a0a530f3e583805cf98b4","assembly_ssl_url":"https://api2.jane.transloadit.com/assemblies/76fe5df1c93a0a530f3e583805cf98b4","bytes_received":95355,"bytes_expected":95355,"client_agent":"Transloadit 69 | Ruby SDK 0.1.2","client_ip":"74.95.29.209","client_referer":null,"start_date":"2011/02/21 70 | 15:54:17 GMT","upload_duration":0.394,"execution_duration":0.006,"fields":{},"uploads":[{"id":"0767b43537389a177a1be96ab06e8a4b","name":"test.png","basename":"test","ext":"png","size":95010,"mime":"image/jpeg","type":"image","field":"file_0","original_id":"0767b43537389a177a1be96ab06e8a4b","ssl_url":"https://tmp.jane.transloadit.com/upload/bf7801cb21fb96cbafd403467244d125.png","meta":{"width":640,"height":480,"date_recorded":null,"date_file_created":null,"date_file_modified":"2011/02/21 71 | 15:54:17 GMT","title":null,"description":null,"location":null,"city":null,"state":null,"country":null,"country_code":null,"keywords":"Photo 72 | Booth","aperture":null,"exposure_compensation":null,"exposure_mode":null,"exposure_time":null,"flash":null,"focal_length":null,"f_number":null,"iso":null,"light_value":null,"metering_mode":null,"shutter_speed":null,"white_balance":null,"device_name":null,"device_vendor":null,"device_software":null,"latitude":null,"longitude":null,"frame_count":null}}],"results":{}}' 73 | http_version: '1.1' 74 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 75 | recorded_with: VCR 2.4.0 76 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/fetch_assembly_uploading.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api2.jane.transloadit.com/assemblies/76fe5df1c93a0a530f3e583805cf98b4 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - ! '*/*; q=0.5, application/xml' 12 | Accept-Encoding: 13 | - gzip, deflate 14 | response: 15 | status: 16 | code: 200 17 | message: OK 18 | headers: 19 | Content-Type: 20 | - text/plain 21 | Access-Control-Allow-Origin: 22 | - ! '*' 23 | Access-Control-Allow-Methods: 24 | - POST, GET, PUT, DELETE, OPTIONS 25 | Access-Control-Allow-Headers: 26 | - X-Requested-With, Content-Type, Accept, Content-Length 27 | Transfer-Encoding: 28 | - chunked 29 | body: 30 | encoding: UTF-8 31 | string: ! '{"ok":"ASSEMBLY_UPLOADING","message":"The assembly is still in the process of being uploaded.","assembly_id":"76fe5df1c93a0a530f3e583805cf98b4","assembly_ssl_url":"https://api2.jane.transloadit.com/assemblies/76fe5df1c93a0a530f3e583805cf98b4","bytes_received":95355,"bytes_expected":95355,"client_agent":"Transloadit 32 | Ruby SDK 0.1.2","client_ip":"74.95.29.209","client_referer":null,"start_date":"2011/02/21 33 | 15:54:17 GMT","upload_duration":0.394,"execution_duration":0.006,"fields":{},"uploads":[{"id":"0767b43537389a177a1be96ab06e8a4b","name":"test.png","basename":"test","ext":"png","size":95010,"mime":"image/jpeg","type":"image","field":"file_0","original_id":"0767b43537389a177a1be96ab06e8a4b","ssl_url":"https://tmp.jane.transloadit.com/upload/bf7801cb21fb96cbafd403467244d125.png","meta":{"width":640,"height":480,"date_recorded":null,"date_file_created":null,"date_file_modified":"2011/02/21 34 | 15:54:17 GMT","title":null,"description":null,"location":null,"city":null,"state":null,"country":null,"country_code":null,"keywords":"Photo 35 | Booth","aperture":null,"exposure_compensation":null,"exposure_mode":null,"exposure_time":null,"flash":null,"focal_length":null,"f_number":null,"iso":null,"light_value":null,"metering_mode":null,"shutter_speed":null,"white_balance":null,"device_name":null,"device_vendor":null,"device_software":null,"latitude":null,"longitude":null,"frame_count":null}}],"results":{}}' 36 | http_version: '1.1' 37 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 38 | recorded_with: VCR 2.4.0 39 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/fetch_billing.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api2.transloadit.com/bill/2016-09?params=%7B%22auth%22:%7B%22key%22:%22%22%7D%7D 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - "*/*" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Transloadit Ruby SDK 1.2.0 16 | Host: 17 | - api2.transloadit.com 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Access-Control-Allow-Headers: 24 | - X-Requested-With, Content-Type, Cache-Control, Accept, Content-Length 25 | Access-Control-Allow-Methods: 26 | - POST, GET, PUT, DELETE, OPTIONS 27 | Access-Control-Allow-Origin: 28 | - "*" 29 | Cache-Control: 30 | - no-cache 31 | Content-Type: 32 | - application/json; charset=utf-8 33 | Date: 34 | - Thu, 08 Sep 2016 11:06:58 GMT 35 | Content-Length: 36 | - '104' 37 | Connection: 38 | - keep-alive 39 | body: 40 | encoding: UTF-8 41 | string: '{"ok":"BILL_FOUND", "invoice_id":"76fe5df1c93a0a530f3e583805cf98b4", "to":"", "email":"user@email.com", "month":"2016-09", "created":"2016-07-29T15:27:19.000Z", "plan":{"id":"4c3ffb9856704668bd484a19cee4b066", "title":"Sandbox", "slug":"sandbox", "price_per_month":0, "gb_included":2, "gb_limit":2, "max_jobs_simulteneous":1, "has_lifetime_limit":1, "price_per_gb":0, "published":1}}' 42 | http_version: 43 | recorded_at: Thu, 08 Sep 2016 11:06:58 GMT 44 | recorded_with: VCR 3.0.3 45 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/fetch_root.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api2.transloadit.com 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - ! '*/*; q=0.5, application/xml' 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Transloadit Ruby SDK 0.1.2 16 | response: 17 | status: 18 | code: 200 19 | message: OK 20 | headers: 21 | Access-Control-Allow-Headers: 22 | - X-Requested-With, Content-Type, Accept, Content-Length 23 | Access-Control-Allow-Methods: 24 | - POST, GET, PUT, DELETE, OPTIONS 25 | Access-Control-Allow-Origin: 26 | - ! '*' 27 | Content-Type: 28 | - text/plain 29 | Content-Length: 30 | - '94' 31 | Connection: 32 | - keep-alive 33 | body: 34 | encoding: UTF-8 35 | string: ! '{"ok":"SERVER_ROOT","hostname":"jane.transloadit.com"}' 36 | http_version: '1.1' 37 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 38 | - request: 39 | method: get 40 | uri: https://api2.transloadit.com/?params=%7B%22params%22:%7B%22foo%22:%22bar%22%7D%7D 41 | body: 42 | encoding: US-ASCII 43 | string: '' 44 | headers: 45 | Accept: 46 | - ! '*/*; q=0.5, application/xml' 47 | Accept-Encoding: 48 | - gzip, deflate 49 | User-Agent: 50 | - Transloadit Ruby SDK 0.1.2 51 | response: 52 | status: 53 | code: 200 54 | message: OK 55 | headers: 56 | Access-Control-Allow-Headers: 57 | - X-Requested-With, Content-Type, Accept, Content-Length 58 | Access-Control-Allow-Methods: 59 | - POST, GET, PUT, DELETE, OPTIONS 60 | Access-Control-Allow-Origin: 61 | - ! '*' 62 | Content-Type: 63 | - text/plain 64 | Content-Length: 65 | - '94' 66 | Connection: 67 | - keep-alive 68 | body: 69 | encoding: UTF-8 70 | string: ! '{"ok":"SERVER_ROOT","host":"jane.transloadit.com"}' 71 | http_version: '1.1' 72 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 73 | - request: 74 | method: get 75 | uri: https://api2.transloadit.com/?params=%7B%22params%22:%7B%22foo%22:%22bar%22%7D%7D&signature=sha384:edccc99aef8cb46e087e17093fbcd6a2316e2fbe31dc0842c4af87a52808d0736d94a3bd5774deca2c215b53804ca572 76 | body: 77 | encoding: US-ASCII 78 | string: '' 79 | headers: 80 | Accept: 81 | - ! '*/*; q=0.5, application/xml' 82 | Accept-Encoding: 83 | - gzip, deflate 84 | User-Agent: 85 | - Transloadit Ruby SDK 0.1.2 86 | response: 87 | status: 88 | code: 200 89 | message: OK 90 | headers: 91 | Access-Control-Allow-Headers: 92 | - X-Requested-With, Content-Type, Accept, Content-Length 93 | Access-Control-Allow-Methods: 94 | - POST, GET, PUT, DELETE, OPTIONS 95 | Access-Control-Allow-Origin: 96 | - ! '*' 97 | Content-Type: 98 | - text/plain 99 | Content-Length: 100 | - '94' 101 | Connection: 102 | - keep-alive 103 | body: 104 | encoding: UTF-8 105 | string: ! '{"ok":"SERVER_ROOT","hostname":"jane.transloadit.com"}' 106 | http_version: '1.1' 107 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 108 | recorded_with: VCR 2.4.0 109 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/fetch_template.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api2.transloadit.com/templates/76fe5df1c93a0a530f3e583805cf98b4?params=%7B%22auth%22:%7B%22key%22:%22%22%7D%7D 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - "*/*" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Transloadit Ruby SDK 1.2.0 16 | Host: 17 | - api2.transloadit.com 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Access-Control-Allow-Headers: 24 | - X-Requested-With, Content-Type, Cache-Control, Accept, Content-Length 25 | Access-Control-Allow-Methods: 26 | - POST, GET, PUT, DELETE, OPTIONS 27 | Access-Control-Allow-Origin: 28 | - "*" 29 | Cache-Control: 30 | - no-cache 31 | Content-Type: 32 | - application/json; charset=utf-8 33 | Date: 34 | - Wed, 24 Aug 2016 09:58:40 GMT 35 | Content-Length: 36 | - '104' 37 | Connection: 38 | - keep-alive 39 | body: 40 | encoding: UTF-8 41 | string: '{"ok": "TEMPLATE_FOUND", "template_id": "76fe5df1c93a0a530f3e583805cf98b4", "template_name": "name"}' 42 | http_version: 43 | recorded_at: Wed, 24 Aug 2016 09:58:38 GMT 44 | recorded_with: VCR 3.0.3 45 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/fetch_templates.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: get 5 | uri: https://api2.transloadit.com/templates?params=%7B%22auth%22:%7B%22key%22:%22%22%7D%7D 6 | body: 7 | encoding: US-ASCII 8 | string: '' 9 | headers: 10 | Accept: 11 | - "*/*" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Transloadit Ruby SDK 1.2.0 16 | Host: 17 | - api2.transloadit.com 18 | response: 19 | status: 20 | code: 200 21 | message: OK 22 | headers: 23 | Access-Control-Allow-Headers: 24 | - X-Requested-With, Content-Type, Cache-Control, Accept, Content-Length 25 | Access-Control-Allow-Methods: 26 | - POST, GET, PUT, DELETE, OPTIONS 27 | Access-Control-Allow-Origin: 28 | - "*" 29 | Cache-Control: 30 | - no-cache 31 | Content-Type: 32 | - application/json; charset=utf-8 33 | Date: 34 | - Wed, 24 Aug 2016 09:58:39 GMT 35 | Content-Length: 36 | - '22' 37 | Connection: 38 | - keep-alive 39 | body: 40 | encoding: UTF-8 41 | string: '{"items":[],"count":0}' 42 | http_version: 43 | recorded_at: Wed, 24 Aug 2016 09:58:38 GMT 44 | recorded_with: VCR 3.0.3 45 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/post_assembly.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api2.transloadit.com/assemblies 6 | body: 7 | encoding: UTF-8 8 | string: params=%7B%22auth%22%3A%7B%22key%22%3A%22%22%2C%22expires%22%3A%222011%2F02%2F21%2023%3A15%3A43%2B00%3A00%22%7D%2C%22steps%22%3A%7B%22encode%22%3A%7B%22robot%22%3A%22%2Fvideo%2Fencode%22%7D%7D%7D&signature=d1a856e3d04103da448a512070139d3e721594cc 9 | headers: 10 | Accept: 11 | - ! '*/*; q=0.5, application/xml' 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Transloadit Ruby SDK 0.1.2 16 | Content-Length: 17 | - '278' 18 | Content-Type: 19 | - application/x-www-form-urlencoded 20 | response: 21 | status: 22 | code: 200 23 | message: OK 24 | headers: 25 | Access-Control-Allow-Headers: 26 | - X-Requested-With, Content-Type, Accept, Content-Length 27 | Access-Control-Allow-Methods: 28 | - POST, GET, PUT, DELETE, OPTIONS 29 | Access-Control-Allow-Origin: 30 | - ! '*' 31 | Content-Type: 32 | - text/plain 33 | Content-Length: 34 | - '482' 35 | Connection: 36 | - keep-alive 37 | body: 38 | encoding: UTF-8 39 | string: ! '{"ok":"ASSEMBLY_COMPLETED","message":"The assembly was successfully 40 | completed.","assembly_id":"2a4cb778995be35ffe5fcda3c01e4267","assembly_ssl_url":"https://api2.jane.transloadit.com/assemblies/2a4cb778995be35ffe5fcda3c01e4267","bytes_received":278,"bytes_expected":278,"client_agent":"Transloadit 41 | Ruby SDK 0.1.2","client_ip":"74.95.29.209","client_referer":null,"start_date":"2011/02/21 42 | 23:15:33 GMT","upload_duration":0.094,"execution_duration":0,"fields":{},"uploads":[],"results":{}}' 43 | http_version: '1.1' 44 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 45 | recorded_with: VCR 2.4.0 46 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/rate_limit_fail.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api2.transloadit.com/assemblies 6 | body: 7 | encoding: UTF-8 8 | string: params=%7B%22steps%22%3A%7B%22a479db2c601661d8f914caf9cf258c0b%22%3A%7B%22robot%22%3A%22%2Fvideo%2Fthumbs%22%7D%7D%2C%22redirect_url%22%3A%22http%3A%2F%2Ffoo.bar%2F%22%2C%22auth%22%3A%7B%22key%22%3A%22%22%7D%2C%22file_0%22%3A%22%23%3CFile%3A0x00000100c63738%3E%22%7D 9 | headers: 10 | Accept: 11 | - ! '*/*; q=0.5, application/xml' 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Transloadit Ruby SDK 0.0.1 16 | Content-Length: 17 | - '298' 18 | Content-Type: 19 | - application/x-www-form-urlencoded 20 | response: 21 | status: 22 | code: 413 23 | headers: 24 | Content-Type: 25 | - application/json; charset=utf-8 26 | Access-Control-Allow-Origin: 27 | - ! '*' 28 | Access-Control-Allow-Methods: 29 | - POST, GET, PUT, DELETE, OPTIONS 30 | Access-Control-Allow-Headers: 31 | - X-Requested-With, Content-Type, Accept, Content-Length 32 | body: 33 | encoding: UTF-8 34 | string: ! '{"error":"RATE_LIMIT_REACHED","message":"Request limit reached", "info":{"retryIn":0}}' 35 | http_version: '1.1' 36 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 37 | - request: 38 | method: post 39 | uri: https://api2.transloadit.com/assemblies 40 | body: 41 | encoding: UTF-8 42 | string: params=%7B%22steps%22%3A%7B%22a479db2c601661d8f914caf9cf258c0b%22%3A%7B%22robot%22%3A%22%2Fvideo%2Fthumbs%22%7D%7D%2C%22redirect_url%22%3A%22http%3A%2F%2Ffoo.bar%2F%22%2C%22auth%22%3A%7B%22key%22%3A%22%22%7D%2C%22file_0%22%3A%22%23%3CFile%3A0x00000100c63738%3E%22%7D 43 | headers: 44 | Accept: 45 | - ! '*/*; q=0.5, application/xml' 46 | Accept-Encoding: 47 | - gzip, deflate 48 | User-Agent: 49 | - Transloadit Ruby SDK 0.0.1 50 | Content-Length: 51 | - '298' 52 | Content-Type: 53 | - application/x-www-form-urlencoded 54 | response: 55 | status: 56 | code: 413 57 | headers: 58 | Content-Type: 59 | - application/json; charset=utf-8 60 | Access-Control-Allow-Origin: 61 | - ! '*' 62 | Access-Control-Allow-Methods: 63 | - POST, GET, PUT, DELETE, OPTIONS 64 | Access-Control-Allow-Headers: 65 | - X-Requested-With, Content-Type, Accept, Content-Length 66 | body: 67 | encoding: UTF-8 68 | string: ! '{"error":"RATE_LIMIT_REACHED","message":"Request limit reached", "info":{"retryIn":0}}' 69 | http_version: '1.1' 70 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 71 | - request: 72 | method: post 73 | uri: https://api2.transloadit.com/assemblies 74 | body: 75 | encoding: UTF-8 76 | string: params=%7B%22steps%22%3A%7B%22a479db2c601661d8f914caf9cf258c0b%22%3A%7B%22robot%22%3A%22%2Fvideo%2Fthumbs%22%7D%7D%2C%22redirect_url%22%3A%22http%3A%2F%2Ffoo.bar%2F%22%2C%22auth%22%3A%7B%22key%22%3A%22%22%7D%2C%22file_0%22%3A%22%23%3CFile%3A0x00000100c63738%3E%22%7D 77 | headers: 78 | Accept: 79 | - ! '*/*; q=0.5, application/xml' 80 | Accept-Encoding: 81 | - gzip, deflate 82 | User-Agent: 83 | - Transloadit Ruby SDK 0.0.1 84 | Content-Length: 85 | - '298' 86 | Content-Type: 87 | - application/x-www-form-urlencoded 88 | response: 89 | status: 90 | code: 413 91 | headers: 92 | Content-Type: 93 | - application/json; charset=utf-8 94 | Access-Control-Allow-Origin: 95 | - ! '*' 96 | Access-Control-Allow-Methods: 97 | - POST, GET, PUT, DELETE, OPTIONS 98 | Access-Control-Allow-Headers: 99 | - X-Requested-With, Content-Type, Accept, Content-Length 100 | body: 101 | encoding: UTF-8 102 | string: ! '{"error":"RATE_LIMIT_REACHED","message":"Request limit reached", "info":{"retryIn":0}}' 103 | http_version: '1.1' 104 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 105 | recorded_with: VCR 2.4.0 106 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/rate_limit_succeed.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api2.transloadit.com/assemblies 6 | body: 7 | encoding: UTF-8 8 | string: params=%7B%22steps%22%3A%7B%22a479db2c601661d8f914caf9cf258c0b%22%3A%7B%22robot%22%3A%22%2Fvideo%2Fthumbs%22%7D%7D%2C%22redirect_url%22%3A%22http%3A%2F%2Ffoo.bar%2F%22%2C%22auth%22%3A%7B%22key%22%3A%22%22%7D%2C%22file_0%22%3A%22%23%3CFile%3A0x00000100c63738%3E%22%7D 9 | headers: 10 | Accept: 11 | - ! '*/*; q=0.5, application/xml' 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Transloadit Ruby SDK 0.0.1 16 | Content-Length: 17 | - '298' 18 | Content-Type: 19 | - application/x-www-form-urlencoded 20 | response: 21 | status: 22 | code: 413 23 | headers: 24 | Content-Type: 25 | - application/json; charset=utf-8 26 | Access-Control-Allow-Origin: 27 | - ! '*' 28 | Access-Control-Allow-Methods: 29 | - POST, GET, PUT, DELETE, OPTIONS 30 | Access-Control-Allow-Headers: 31 | - X-Requested-With, Content-Type, Accept, Content-Length 32 | body: 33 | encoding: UTF-8 34 | string: ! '{"error":"RATE_LIMIT_REACHED","message":"Request limit reached", "info":{"retryIn":0}}' 35 | http_version: '1.1' 36 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 37 | - request: 38 | method: post 39 | uri: https://api2.transloadit.com/assemblies 40 | body: 41 | encoding: UTF-8 42 | string: params=%7B%22steps%22%3A%7B%22a479db2c601661d8f914caf9cf258c0b%22%3A%7B%22robot%22%3A%22%2Fvideo%2Fthumbs%22%7D%7D%2C%22redirect_url%22%3A%22http%3A%2F%2Ffoo.bar%2F%22%2C%22auth%22%3A%7B%22key%22%3A%22%22%7D%2C%22file_0%22%3A%22%23%3CFile%3A0x00000100c63738%3E%22%7D 43 | headers: 44 | Accept: 45 | - ! '*/*; q=0.5, application/xml' 46 | Accept-Encoding: 47 | - gzip, deflate 48 | User-Agent: 49 | - Transloadit Ruby SDK 0.0.1 50 | Content-Length: 51 | - '298' 52 | Content-Type: 53 | - application/x-www-form-urlencoded 54 | response: 55 | status: 56 | code: 302 57 | message: Moved Temporarily 58 | headers: 59 | Content-Type: 60 | - text/plain 61 | Access-Control-Allow-Origin: 62 | - ! '*' 63 | Access-Control-Allow-Methods: 64 | - POST, GET, PUT, DELETE, OPTIONS 65 | Access-Control-Allow-Headers: 66 | - X-Requested-With, Content-Type, Accept, Content-Length 67 | Location: 68 | - https://foo.bar/?assembly_id=177c56e5435176f4877fbc1b397fa4f0&assembly_ssl_url=https://api2.vivian.transloadit.com/assemblies/177c56e5435176f4877fbc1b397fa4f0 69 | Transfer-Encoding: 70 | - chunked 71 | body: 72 | encoding: UTF-8 73 | string: ! '{"ok":"ASSEMBLY_COMPLETED","message":"The assembly was successfully 74 | completed.","assembly_id":"177c56e5435176f4877fbc1b397fa4f0","assembly_ssl_url":"https://api2.vivian.transloadit.com/assemblies/177c56e5435176f4877fbc1b397fa4f0","bytes_received":298,"bytes_expected":298,"client_agent":"Transloadit 75 | Ruby SDK 0.0.1","client_ip":"69.180.12.41","client_referer":null,"start_date":"2011/02/07 76 | 04:29:15 GMT","upload_duration":0.038,"execution_duration":0.002,"fields":{},"uploads":[],"results":{}}' 77 | http_version: '1.1' 78 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 79 | recorded_with: VCR 2.4.0 80 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/replay_assembly.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api2.transloadit.com/assemblies/55c965a063a311e6ba2d379ef10b28f7/replay 6 | body: 7 | encoding: UTF-8 8 | string: params=%%7B%22auth%22:%7B%22key%22:%22%22%7D%7D 9 | headers: 10 | Accept: 11 | - "*/*" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Transloadit Ruby SDK 1.2.0 16 | Content-Length: 17 | - '195' 18 | Content-Type: 19 | - application/x-www-form-urlencoded 20 | Host: 21 | - api2.transloadit.com 22 | response: 23 | status: 24 | code: 200 25 | message: OK 26 | headers: 27 | Access-Control-Allow-Headers: 28 | - X-Requested-With, Content-Type, Cache-Control, Accept, Content-Length 29 | Access-Control-Allow-Methods: 30 | - POST, GET, PUT, DELETE, OPTIONS 31 | Access-Control-Allow-Origin: 32 | - "*" 33 | Cache-Control: 34 | - no-cache 35 | Content-Type: 36 | - application/json; charset=utf-8 37 | Date: 38 | - Tue, 16 Aug 2016 11:19:45 GMT 39 | Content-Length: 40 | - '271' 41 | Connection: 42 | - keep-alive 43 | body: 44 | encoding: UTF-8 45 | string: '{"ok":"ASSEMBLY_REPLAYING","message":"The assembly is now in the process 46 | of being replayed.","success":true,"assembly_id":"b8590300650211e6b826d727b2cfd9ce","assembly_ssl_url":"https://api2.sayuri.transloadit.com/assemblies/b8590300650211e6b826d727b2cfd9ce","notify_url":null}' 47 | http_version: 48 | recorded_at: Tue, 16 Aug 2016 11:19:45 GMT 49 | recorded_with: VCR 3.0.3 50 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/replay_assembly_notification.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api2.transloadit.com/assembly_notifications/2ea5d21063ad11e6bc93e53395ce4e7d/replay 6 | body: 7 | encoding: UTF-8 8 | string: params=%%7B%22auth%22:%7B%22key%22:%22%22%7D%7D 9 | headers: 10 | Accept: 11 | - "*/*" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Transloadit Ruby SDK 1.2.0 16 | Content-Length: 17 | - '195' 18 | Content-Type: 19 | - application/x-www-form-urlencoded 20 | Host: 21 | - api2.transloadit.com 22 | response: 23 | status: 24 | code: 404 25 | message: Not Found 26 | headers: 27 | Access-Control-Allow-Headers: 28 | - X-Requested-With, Content-Type, Cache-Control, Accept, Content-Length 29 | Access-Control-Allow-Methods: 30 | - POST, GET, PUT, DELETE, OPTIONS 31 | Access-Control-Allow-Origin: 32 | - "*" 33 | Cache-Control: 34 | - no-cache 35 | Content-Type: 36 | - application/json; charset=utf-8 37 | Date: 38 | - Tue, 16 Aug 2016 11:56:54 GMT 39 | Content-Length: 40 | - '94' 41 | Connection: 42 | - keep-alive 43 | body: 44 | encoding: UTF-8 45 | string: '{"ok":"ASSEMBLY_NOTIFICATION_REPLAYED", "assembly_id":"2ea5d21063ad11e6bc93e53395ce4e7d"}' 46 | http_version: 47 | recorded_at: Tue, 16 Aug 2016 11:56:54 GMT 48 | recorded_with: VCR 3.0.3 49 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/submit_assembly.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: post 5 | uri: https://api2.transloadit.com/assemblies 6 | body: 7 | encoding: UTF-8 8 | string: params=%7B%22steps%22%3A%7B%22a479db2c601661d8f914caf9cf258c0b%22%3A%7B%22robot%22%3A%22%2Fvideo%2Fthumbs%22%7D%7D%2C%22redirect_url%22%3A%22http%3A%2F%2Ffoo.bar%2F%22%2C%22auth%22%3A%7B%22key%22%3A%22%22%7D%2C%22file_0%22%3A%22%23%3CFile%3A0x00000100c63738%3E%22%7D 9 | headers: 10 | Accept: 11 | - ! '*/*; q=0.5, application/xml' 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Transloadit Ruby SDK 0.0.1 16 | Content-Length: 17 | - '298' 18 | Content-Type: 19 | - application/x-www-form-urlencoded 20 | response: 21 | status: 22 | code: 302 23 | message: Moved Temporarily 24 | headers: 25 | Content-Type: 26 | - text/plain 27 | Access-Control-Allow-Origin: 28 | - ! '*' 29 | Access-Control-Allow-Methods: 30 | - POST, GET, PUT, DELETE, OPTIONS 31 | Access-Control-Allow-Headers: 32 | - X-Requested-With, Content-Type, Accept, Content-Length 33 | Location: 34 | - https://foo.bar/?assembly_id=177c56e5435176f4877fbc1b397fa4f0&assembly_ssl_url=https://api2.vivian.transloadit.com/assemblies/177c56e5435176f4877fbc1b397fa4f0 35 | Transfer-Encoding: 36 | - chunked 37 | body: 38 | encoding: UTF-8 39 | string: ! '{"ok":"ASSEMBLY_COMPLETED","message":"The assembly was successfully 40 | completed.","assembly_id":"177c56e5435176f4877fbc1b397fa4f0","assembly_ssl_url":"https://api2.vivian.transloadit.com/assemblies/177c56e5435176f4877fbc1b397fa4f0","bytes_received":298,"bytes_expected":298,"client_agent":"Transloadit 41 | Ruby SDK 0.0.1","client_ip":"69.180.12.41","client_referer":null,"start_date":"2011/02/07 42 | 04:29:15 GMT","upload_duration":0.038,"execution_duration":0.002,"fields":{},"uploads":[],"results":{}}' 43 | http_version: '1.1' 44 | recorded_at: Fri, 08 Mar 2013 15:13:10 GMT 45 | recorded_with: VCR 2.4.0 46 | -------------------------------------------------------------------------------- /test/fixtures/cassettes/update_template.yml: -------------------------------------------------------------------------------- 1 | --- 2 | http_interactions: 3 | - request: 4 | method: put 5 | uri: https://api2.transloadit.com/templates/55c965a063a311e6ba2d379ef10b28f7 6 | body: 7 | encoding: UTF-8 8 | string: params=%7B%22auth%22%3A%7B%22key%22%3A%22%22%7D%7D 9 | headers: 10 | Accept: 11 | - "*/*" 12 | Accept-Encoding: 13 | - gzip, deflate 14 | User-Agent: 15 | - Transloadit Ruby SDK 1.2.0 16 | Content-Length: 17 | - '50' 18 | Content-Type: 19 | - application/x-www-form-urlencoded 20 | Host: 21 | - api2.transloadit.com 22 | response: 23 | status: 24 | code: 200 25 | message: OK 26 | headers: 27 | Access-Control-Allow-Headers: 28 | - X-Requested-With, Content-Type, Cache-Control, Accept, Content-Length 29 | Access-Control-Allow-Methods: 30 | - POST, GET, PUT, DELETE, OPTIONS 31 | Access-Control-Allow-Origin: 32 | - "*" 33 | Cache-Control: 34 | - no-cache 35 | Content-Type: 36 | - application/json; charset=utf-8 37 | Date: 38 | - Wed, 24 Aug 2016 09:58:34 GMT 39 | Content-Length: 40 | - '75' 41 | Connection: 42 | - keep-alive 43 | body: 44 | encoding: UTF-8 45 | string: '{"ok":"TEMPLATE_UPDATED", "template_id":"55c965a063a311e6ba2d379ef10b28f7"}' 46 | http_version: 47 | recorded_at: Wed, 24 Aug 2016 09:58:33 GMT 48 | recorded_with: VCR 3.0.3 49 | -------------------------------------------------------------------------------- /test/test_helper.rb: -------------------------------------------------------------------------------- 1 | $:.unshift File.dirname(__FILE__) 2 | $:.unshift File.expand_path("../../lib", __FILE__) 3 | 4 | if ENV["COVERAGE"] != "0" 5 | require "simplecov" 6 | SimpleCov.start do 7 | add_filter "/test/" 8 | enable_coverage :branch 9 | end 10 | 11 | require "simplecov-cobertura" 12 | SimpleCov.formatter = SimpleCov::Formatter::CoberturaFormatter 13 | end 14 | 15 | require "minitest/autorun" 16 | require "transloadit" 17 | require "vcr" 18 | 19 | VCR.configure do |c| 20 | c.cassette_library_dir = "test/fixtures/cassettes" 21 | c.default_cassette_options = {record: :none} 22 | c.hook_into :webmock 23 | end 24 | 25 | def values_from_post_body(body) 26 | Addressable::URI.parse("?" + CGI.unescape(body)).query_values 27 | end 28 | -------------------------------------------------------------------------------- /test/unit/test_transloadit.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe Transloadit do 4 | before do 5 | @key = "a" 6 | @secret = "b" 7 | @duration = 10 8 | @max_size = 100 9 | end 10 | 11 | it "must allow initialization" do 12 | t = Transloadit.new(key: @key, secret: @secret) 13 | _(t).must_be_kind_of Transloadit 14 | end 15 | 16 | it "must not be initialized with no arguments" do 17 | _(lambda { Transloadit.new }).must_raise ArgumentError 18 | end 19 | 20 | it "must require a key" do 21 | _(lambda { Transloadit.new(secret: @secret) }).must_raise ArgumentError 22 | end 23 | 24 | it "must not require a secret" do 25 | t = Transloadit.new(key: @key) 26 | _(t).must_be_kind_of Transloadit 27 | end 28 | 29 | it "must provide a default duration" do 30 | _(Transloadit.new(key: @key).duration).wont_be_nil 31 | end 32 | 33 | describe "when initialized" do 34 | before do 35 | @transloadit = Transloadit.new( 36 | key: @key, 37 | secret: @secret, 38 | duration: @duration, 39 | max_size: @max_size 40 | ) 41 | end 42 | 43 | it "must allow access to the key" do 44 | _(@transloadit.key).must_equal @key 45 | end 46 | 47 | it "must allow access to the secret" do 48 | _(@transloadit.secret).must_equal @secret 49 | end 50 | 51 | it "must allow access to the duration" do 52 | _(@transloadit.duration).must_equal @duration 53 | end 54 | 55 | it "must allow access to the max_size" do 56 | _(@transloadit.max_size).must_equal @max_size 57 | end 58 | 59 | it "must create steps" do 60 | step = @transloadit.step("resize", "/image/resize", width: 320) 61 | 62 | _(step).must_be_kind_of Transloadit::Step 63 | _(step.name).must_equal "resize" 64 | _(step.robot).must_equal "/image/resize" 65 | _(step.options).must_equal width: 320 66 | end 67 | 68 | it "must create assembly api instances" do 69 | step = @transloadit.step(nil, nil) 70 | assembly = @transloadit.assembly steps: step 71 | 72 | _(assembly).must_be_kind_of Transloadit::Assembly 73 | _(assembly.steps).must_equal step.to_hash 74 | end 75 | 76 | it "must create assemblies with multiple steps" do 77 | steps = [ 78 | @transloadit.step("step1", nil), 79 | @transloadit.step("step2", nil) 80 | ] 81 | 82 | assembly = @transloadit.assembly steps: steps 83 | _(assembly.steps).must_equal steps.inject({}) { |h, s| h.merge s } 84 | end 85 | 86 | it "must get user billing report" do 87 | VCR.use_cassette "fetch_billing" do 88 | bill = Transloadit.new(key: "").bill(9, 2016) 89 | _(bill["ok"]).must_equal "BILL_FOUND" 90 | _(bill["invoice_id"]).must_equal "76fe5df1c93a0a530f3e583805cf98b4" 91 | end 92 | end 93 | 94 | it "must create template api instances" do 95 | template = @transloadit.template 96 | _(template).must_be_kind_of Transloadit::Template 97 | end 98 | 99 | it "must inspect like a hash" do 100 | _(@transloadit.inspect).must_equal @transloadit.to_hash.inspect 101 | end 102 | 103 | it "must produce Transloadit-compatible hash output" do 104 | _(@transloadit.to_hash[:key]).must_equal @key 105 | _(@transloadit.to_hash[:max_size]).must_equal @max_size 106 | _(@transloadit.to_hash[:expires]) 107 | .must_match %r{\d{4}/\d{2}/\d{2} \d{2}:\d{2}:\d{2}\+00:00} 108 | end 109 | 110 | it "must produce Transloadit-compatible JSON output" do 111 | _(@transloadit.to_json).must_equal MultiJson.dump(@transloadit.to_hash) 112 | end 113 | end 114 | 115 | describe "with no secret" do 116 | before do 117 | @transloadit = Transloadit.new(key: @key) 118 | end 119 | 120 | it "must not include a secret in its hash output" do 121 | _(@transloadit.to_hash.keys).wont_include :secret 122 | end 123 | 124 | it "must not include a secret in its JSON output" do 125 | _(@transloadit.to_json).must_equal MultiJson.dump(@transloadit.to_hash) 126 | end 127 | end 128 | end 129 | -------------------------------------------------------------------------------- /test/unit/transloadit/node-smartcdn-sig.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env tsx 2 | // Reference Smart CDN (https://transloadit.com/services/content-delivery/) Signature implementation 3 | // And CLI tester to see if our SDK's implementation 4 | // matches Node's 5 | 6 | /// 7 | 8 | import { createHash, createHmac } from 'crypto' 9 | 10 | interface SmartCDNParams { 11 | workspace: string 12 | template: string 13 | input: string 14 | expire_at_ms?: number 15 | auth_key?: string 16 | auth_secret?: string 17 | url_params?: Record 18 | } 19 | 20 | function signSmartCDNUrl(params: SmartCDNParams): string { 21 | const { 22 | workspace, 23 | template, 24 | input, 25 | expire_at_ms, 26 | auth_key = 'my-key', 27 | auth_secret = 'my-secret', 28 | url_params = {}, 29 | } = params 30 | 31 | if (!workspace) throw new Error('workspace is required') 32 | if (!template) throw new Error('template is required') 33 | if (input === null || input === undefined) 34 | throw new Error('input must be a string') 35 | 36 | const workspaceSlug = encodeURIComponent(workspace) 37 | const templateSlug = encodeURIComponent(template) 38 | const inputField = encodeURIComponent(input) 39 | 40 | const expireAt = expire_at_ms ?? Date.now() + 60 * 60 * 1000 // 1 hour default 41 | 42 | const queryParams: Record = {} 43 | 44 | // Handle url_params 45 | Object.entries(url_params).forEach(([key, value]) => { 46 | if (value === null || value === undefined) return 47 | if (Array.isArray(value)) { 48 | value.forEach((val) => { 49 | if (val === null || val === undefined) return 50 | ;(queryParams[key] ||= []).push(String(val)) 51 | }) 52 | } else { 53 | queryParams[key] = [String(value)] 54 | } 55 | }) 56 | 57 | queryParams.auth_key = [auth_key] 58 | queryParams.exp = [String(expireAt)] 59 | 60 | // Sort parameters to ensure consistent ordering 61 | const sortedParams = Object.entries(queryParams) 62 | .sort() 63 | .map(([key, values]) => 64 | values.map((v) => `${encodeURIComponent(key)}=${encodeURIComponent(v)}`) 65 | ) 66 | .flat() 67 | .join('&') 68 | 69 | const stringToSign = `${workspaceSlug}/${templateSlug}/${inputField}?${sortedParams}` 70 | const signature = createHmac('sha256', auth_secret) 71 | .update(stringToSign) 72 | .digest('hex') 73 | 74 | const finalParams = `${sortedParams}&sig=${encodeURIComponent( 75 | `sha256:${signature}` 76 | )}` 77 | return `https://${workspaceSlug}.tlcdn.com/${templateSlug}/${inputField}?${finalParams}` 78 | } 79 | 80 | // Read JSON from stdin 81 | let jsonInput = '' 82 | process.stdin.on('data', (chunk) => { 83 | jsonInput += chunk 84 | }) 85 | 86 | process.stdin.on('end', () => { 87 | const params = JSON.parse(jsonInput) 88 | console.log(signSmartCDNUrl(params)) 89 | }) 90 | -------------------------------------------------------------------------------- /test/unit/transloadit/test_api.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe Transloadit::ApiModel do 4 | let(:foo) { "foo" } 5 | let(:bar) { "bar" } 6 | let(:transloadit) { Transloadit.new(key: "") } 7 | 8 | let(:api) { 9 | Transloadit::ApiModel.new( 10 | transloadit, 11 | foo: foo, 12 | bar: bar 13 | ) 14 | } 15 | 16 | it "must allow initialization" do 17 | _(Transloadit::ApiModel.new(transloadit)) 18 | .must_be_kind_of Transloadit::ApiModel 19 | 20 | _(Transloadit::Template.new(transloadit)) 21 | .must_be_kind_of Transloadit::Template 22 | end 23 | 24 | describe "when initialized" do 25 | it "must store a pointer to the transloadit instance" do 26 | _(api.transloadit).must_equal transloadit 27 | end 28 | 29 | it "must remember the options passed" do 30 | _(api.options).must_equal( 31 | foo: foo, 32 | bar: bar 33 | ) 34 | end 35 | 36 | it "must inspect like a hash" do 37 | _(api.inspect).must_equal api.to_hash.inspect 38 | end 39 | 40 | it "must produce Transloadit-compatible hash output" do 41 | _(api.to_hash).must_equal( 42 | auth: transloadit.to_hash, 43 | foo: foo, 44 | bar: bar 45 | ) 46 | end 47 | 48 | it "must produce Transloadit-compatible JSON output" do 49 | _(api.to_json).must_equal MultiJson.dump(api.to_hash) 50 | end 51 | end 52 | end 53 | -------------------------------------------------------------------------------- /test/unit/transloadit/test_assembly.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe Transloadit::Assembly do 4 | before do 5 | @transloadit = Transloadit.new(key: "") 6 | end 7 | 8 | it "must inherit from Transloadit::ApiModel class" do 9 | _(Transloadit::Assembly < Transloadit::ApiModel).must_equal true 10 | end 11 | 12 | describe "when initialized" do 13 | before do 14 | @step = @transloadit.step "thumbs", "/video/thumbs" 15 | @redirect = "https://foo.bar/" 16 | 17 | @assembly = Transloadit::Assembly.new @transloadit, 18 | steps: @step, 19 | redirect_url: @redirect 20 | end 21 | 22 | it "must wrap its step in a hash" do 23 | _(@assembly.steps).must_equal @step.to_hash 24 | end 25 | 26 | it "must not wrap a nil step" do 27 | @assembly.options[:steps] = nil 28 | assert_nil @assembly.steps 29 | end 30 | 31 | it "must not wrap a hash step" do 32 | @assembly.options[:steps] = {foo: 1} 33 | _(@assembly.steps).must_equal foo: 1 34 | end 35 | 36 | it "must produce Transloadit-compatible hash output" do 37 | _(@assembly.to_hash).must_equal( 38 | auth: @transloadit.to_hash, 39 | steps: @assembly.steps, 40 | redirect_url: @redirect 41 | ) 42 | end 43 | 44 | it "must submit files for upload" do 45 | VCR.use_cassette "submit_assembly" do 46 | response = @assembly.create! open("lib/transloadit/version.rb") 47 | _(response.code).must_equal 302 48 | _(response.headers[:location]).must_match %r{^https://foo.bar/} 49 | end 50 | end 51 | 52 | describe "with a secret" do 53 | include WebMock::API 54 | 55 | before do 56 | WebMock.reset! 57 | stub_request(:post, "https://api2.transloadit.com/assemblies") 58 | .to_return(body: '{"ok":"ASSEMBLY_COMPLETED"}') 59 | end 60 | 61 | after do 62 | WebMock.reset! 63 | end 64 | 65 | it "must send the signature before any file" do 66 | transloadit = Transloadit.new(key: "", secret: "foo") 67 | Transloadit::Assembly.new( 68 | transloadit 69 | ).create! open("lib/transloadit/version.rb") 70 | 71 | assert_requested(:post, "https://api2.transloadit.com/assemblies") do |req| 72 | position_params = req.body.index 'name="params"' 73 | position_signature = req.body.index 'name="signature"' 74 | position_file = req.body.index 'name="file_0"' 75 | 76 | _(position_params).wont_be_nil 77 | _(position_signature).wont_be_nil 78 | _(position_file).wont_be_nil 79 | 80 | _(position_params < position_signature).must_equal true 81 | _(position_signature < position_file).must_equal true 82 | end 83 | end 84 | end 85 | 86 | describe "with additional parameters" do 87 | include WebMock::API 88 | 89 | before do 90 | WebMock.reset! 91 | stub_request(:post, "https://api2.transloadit.com/assemblies") 92 | .to_return(body: '{"ok":"ASSEMBLY_COMPLETED"}') 93 | end 94 | 95 | after do 96 | WebMock.reset! 97 | end 98 | 99 | it "must allow to send a template id along" do 100 | Transloadit::Assembly.new( 101 | @transloadit, 102 | template_id: "TEMPLATE_ID" 103 | ).create! 104 | 105 | assert_requested(:post, "https://api2.transloadit.com/assemblies") do |req| 106 | values = values_from_post_body(req.body) 107 | _(MultiJson.load(values["params"])["template_id"]).must_equal "TEMPLATE_ID" 108 | end 109 | end 110 | 111 | it "must allow to send the fields hash" do 112 | Transloadit::Assembly.new( 113 | @transloadit, 114 | fields: {tag: "ninja-cat"} 115 | ).create! 116 | 117 | assert_requested(:post, "https://api2.transloadit.com/assemblies") do |req| 118 | values = values_from_post_body(req.body) 119 | _(values["tag"]).must_equal "ninja-cat" 120 | _(MultiJson.load(values["params"])["fields"]["tag"]).must_equal "ninja-cat" 121 | end 122 | end 123 | 124 | it "must allow steps through the create! method" do 125 | Transloadit::Assembly.new(@transloadit).create!( 126 | steps: @transloadit.step("thumbs", "/video/thumbs") 127 | ) 128 | 129 | assert_requested(:post, "https://api2.transloadit.com/assemblies") do |req| 130 | values = values_from_post_body(req.body) 131 | _(MultiJson.load(values["params"])["steps"]).must_equal({"thumbs" => {"robot" => "/video/thumbs"}}) 132 | end 133 | end 134 | 135 | it "must allow steps passed through the create! method override steps previously set" do 136 | @assembly.create!(steps: @transloadit.step("resize", "/image/resize")) 137 | 138 | assert_requested(:post, "https://api2.transloadit.com/assemblies") do |req| 139 | values = values_from_post_body(req.body) 140 | _(MultiJson.load(values["params"])["steps"]).must_equal({"resize" => {"robot" => "/image/resize"}}) 141 | end 142 | end 143 | end 144 | 145 | describe 'when using the "submit!" method' do 146 | it "must call the create! method with the same parameters" do 147 | VCR.use_cassette "submit_assembly" do 148 | file = open("lib/transloadit/version.rb") 149 | mocker = Minitest::Mock.new 150 | mocker.expect :call, nil, [file] 151 | @assembly.stub :create!, mocker do 152 | @assembly.submit!(file) 153 | end 154 | mocker.verify 155 | end 156 | end 157 | end 158 | 159 | describe "when rate limit is reached" do 160 | it "must output a warning and retry for a successful request" do 161 | VCR.use_cassette "rate_limit_succeed" do 162 | _, warning = capture_io { 163 | response = @assembly.create! open("lib/transloadit/version.rb") 164 | _(response["ok"]).must_equal "ASSEMBLY_COMPLETED" 165 | } 166 | _(warning).must_equal "Rate limit reached. Waiting for 0 seconds before retrying.\n" 167 | end 168 | end 169 | 170 | it "must retry only the number of times specified" do 171 | @assembly.options[:tries] = 1 172 | 173 | VCR.use_cassette "rate_limit_succeed" do 174 | assert_raises Transloadit::Exception::RateLimitReached do 175 | @assembly.create! open("lib/transloadit/version.rb") 176 | end 177 | end 178 | end 179 | 180 | it "must raise RateLimitReached exception after multiple retries request" do 181 | VCR.use_cassette "rate_limit_fail" do 182 | assert_raises Transloadit::Exception::RateLimitReached do 183 | @assembly.create! open("lib/transloadit/version.rb") 184 | end 185 | end 186 | end 187 | end 188 | end 189 | 190 | describe "with multiple steps" do 191 | before do 192 | @encode = @transloadit.step "encode", "/video/encode" 193 | @thumbs = @transloadit.step "thumbs", "/video/thumbs" 194 | 195 | @assembly = Transloadit::Assembly.new @transloadit, 196 | steps: [@encode, @thumbs] 197 | end 198 | 199 | it "must wrap its steps into one hash" do 200 | _(@assembly.to_hash[:steps].keys).must_include @encode.name 201 | _(@assembly.to_hash[:steps].keys).must_include @thumbs.name 202 | end 203 | 204 | it "must not allow duplicate steps" do 205 | thumbs = @transloadit.step("thumbs", "/video/thumbs") 206 | thumbs_duplicate = @transloadit.step("thumbs", "/video/encode") 207 | options = {steps: [thumbs, thumbs_duplicate]} 208 | assert_raises ArgumentError do 209 | @assembly.create! open("lib/transloadit/version.rb"), **options 210 | end 211 | end 212 | end 213 | 214 | describe "using assembly API methods" do 215 | include WebMock::API 216 | 217 | before do 218 | WebMock.reset! 219 | @assembly = Transloadit::Assembly.new @transloadit 220 | end 221 | 222 | describe "when fetching all assemblies" do 223 | it "must perform GET request to /assemblies" do 224 | stub = stub_request(:get, "https://api2.transloadit.com/assemblies?params=%7B%22auth%22:%7B%22key%22:%22%22%7D%7D") 225 | @assembly.list 226 | 227 | assert_requested(stub) 228 | end 229 | 230 | it "must return a list of items" do 231 | VCR.use_cassette "fetch_assemblies" do 232 | response = @assembly.list 233 | 234 | _(response["items"]).must_equal [] 235 | _(response["count"]).must_equal 0 236 | end 237 | end 238 | end 239 | 240 | describe "when fetching single assembly" do 241 | it "must perform GET request to /assemblies/[id]" do 242 | stub = stub_request(:get, "https://api2.transloadit.com/assemblies/76fe5df1c93a0a530f3e583805cf98b4") 243 | @assembly.get "76fe5df1c93a0a530f3e583805cf98b4" 244 | 245 | assert_requested(stub) 246 | end 247 | 248 | it "must get assembly with specified id" do 249 | VCR.use_cassette "fetch_assembly_ok" do 250 | response = @assembly.get "76fe5df1c93a0a530f3e583805cf98b4" 251 | _(response["assembly_id"]).must_equal "76fe5df1c93a0a530f3e583805cf98b4" 252 | end 253 | end 254 | end 255 | 256 | describe "when fetching assembly notifications" do 257 | it "must perform GET request to /assembly_notifications" do 258 | stub = stub_request( 259 | :get, 260 | "https://api2.transloadit.com/assembly_notifications?params=%7B%22auth%22:%7B%22key%22:%22%22%7D%7D" 261 | ) 262 | @assembly.get_notifications 263 | 264 | assert_requested(stub) 265 | end 266 | 267 | it "must return a list of items" do 268 | VCR.use_cassette "fetch_assembly_notifications" do 269 | response = @assembly.get_notifications 270 | 271 | _(response["items"]).must_equal [] 272 | _(response["count"]).must_equal 0 273 | end 274 | end 275 | end 276 | 277 | describe "when replaying assembly" do 278 | it "must perform POST request to assemblies/[id]/replay" do 279 | VCR.use_cassette "replay_assembly" do 280 | response = @assembly.replay "55c965a063a311e6ba2d379ef10b28f7" 281 | 282 | _(response["ok"]).must_equal "ASSEMBLY_REPLAYING" 283 | _(response["assembly_id"]).must_equal "b8590300650211e6b826d727b2cfd9ce" 284 | end 285 | end 286 | end 287 | 288 | describe "when replaying assembly notification" do 289 | it "must replay notification of specified assembly" do 290 | VCR.use_cassette "replay_assembly_notification" do 291 | response = @assembly.replay_notification "2ea5d21063ad11e6bc93e53395ce4e7d" 292 | 293 | _(response["ok"]).must_equal "ASSEMBLY_NOTIFICATION_REPLAYED" 294 | _(response["assembly_id"]).must_equal "2ea5d21063ad11e6bc93e53395ce4e7d" 295 | end 296 | end 297 | end 298 | end 299 | end 300 | -------------------------------------------------------------------------------- /test/unit/transloadit/test_request.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe Transloadit::Request do 4 | it "must allow initialization" do 5 | request = Transloadit::Request.new "/" 6 | _(request).must_be_kind_of Transloadit::Request 7 | end 8 | 9 | describe "when performing a GET" do 10 | before do 11 | @request = Transloadit::Request.new "/" 12 | end 13 | 14 | it "must inspect to the API URL" do 15 | _(@request.inspect).must_equal @request.url.to_s.inspect 16 | end 17 | 18 | it "must perform a GET against the resource" do 19 | VCR.use_cassette "fetch_root" do 20 | _(@request.get(params: {foo: "bar"})["ok"]) 21 | .must_equal "SERVER_ROOT" 22 | end 23 | end 24 | 25 | describe "with secret" do 26 | before do 27 | @request.secret = "tehsupersecrettoken" 28 | end 29 | 30 | it "must inspect to the API URL" do 31 | _(@request.inspect).must_equal @request.url.to_s.inspect 32 | end 33 | 34 | it "must perform a GET against the resource" do 35 | VCR.use_cassette "fetch_root" do 36 | _(@request.get(params: {foo: "bar"})["ok"]) 37 | .must_equal "SERVER_ROOT" 38 | end 39 | end 40 | end 41 | end 42 | 43 | describe "when performing a POST" do 44 | it "must perform a POST against the resource" do 45 | @request = Transloadit::Request.new("assemblies", "secret") 46 | 47 | VCR.use_cassette "post_assembly" do 48 | _(@request.post(params: { 49 | auth: {key: "", 50 | expires: (Time.now + 10).utc.strftime("%Y/%m/%d %H:%M:%S+00:00")}, 51 | steps: {encode: {robot: "/video/encode"}} 52 | })["ok"]).must_equal "ASSEMBLY_COMPLETED" 53 | end 54 | end 55 | end 56 | 57 | describe "when performing a PUT" do 58 | it "must perform a PUT against the resource" do 59 | @request = Transloadit::Request.new("templates/55c965a063a311e6ba2d379ef10b28f7", "secret") 60 | VCR.use_cassette "update_template" do 61 | _(@request.put(params: { 62 | name: "foo", 63 | template: {key: "value"} 64 | })["ok"]).must_equal "TEMPLATE_UPDATED" 65 | end 66 | end 67 | end 68 | 69 | describe "when performing a DELETE" do 70 | it "must perform a DELETE against the resource" do 71 | @request = Transloadit::Request.new("templates/55c965a063a311e6ba2d379ef10b28f7", "secret") 72 | 73 | VCR.use_cassette "delete_template" do 74 | _(@request.delete["ok"]).must_equal "TEMPLATE_DELETED" 75 | end 76 | end 77 | end 78 | end 79 | -------------------------------------------------------------------------------- /test/unit/transloadit/test_response.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe Transloadit::Response do 4 | request_uri = "https://api2.jane.transloadit.com/assemblies/76fe5df1c93a0a530f3e583805cf98b4" 5 | 6 | it "must allow delegate initialization" do 7 | response = Transloadit::Response.new("test") 8 | _(response.class).must_equal Transloadit::Response 9 | end 10 | 11 | describe "when initialized" do 12 | before do 13 | VCR.use_cassette "fetch_assembly_ok" do 14 | @response = Transloadit::Response.new( 15 | RestClient::Resource.new(request_uri).get 16 | ) 17 | end 18 | end 19 | 20 | it "must parse the body" do 21 | _(@response.body).must_be_kind_of Hash 22 | end 23 | 24 | it "must allow access to body attributes" do 25 | %w[ok message assembly_id assembly_ssl_url].each do |attribute| 26 | _(@response[attribute]).must_equal @response.body[attribute] 27 | end 28 | end 29 | 30 | it "must allow access to body attributes as symbols" do 31 | [:ok, :message, :assembly_id, :assembly_ssl_url].each do |attribute| 32 | _(@response[attribute]).must_equal @response.body[attribute.to_s] 33 | end 34 | end 35 | 36 | it "must inspect as the body" do 37 | _(@response.inspect).must_equal @response.body.inspect 38 | end 39 | end 40 | 41 | describe "when extended as an assembly" do 42 | before do 43 | VCR.use_cassette "fetch_assembly_ok" do 44 | @response = Transloadit::Response.new( 45 | RestClient::Resource.new(request_uri).get 46 | ).extend!(Transloadit::Response::Assembly) 47 | end 48 | end 49 | 50 | it "must allow checking for completion" do 51 | _(@response.completed?).must_equal true 52 | _(@response.finished?).must_equal true 53 | _(@response.error?).must_equal false 54 | end 55 | 56 | # TODO: can this be tested better? 57 | it "must allow reloading the assembly" do 58 | VCR.use_cassette "fetch_assembly_ok", allow_playback_repeats: true do 59 | _(@response.send(:__getobj__)) 60 | .wont_be_same_as @response.reload!.send(:__getobj__) 61 | 62 | _(@response.object_id) 63 | .must_equal @response.reload!.object_id 64 | end 65 | end 66 | 67 | it "must allow canceling" do 68 | VCR.use_cassette "cancel_assembly" do 69 | @response.cancel! 70 | 71 | _(@response.completed?).must_equal false 72 | _(@response["ok"]).must_equal "ASSEMBLY_CANCELED" 73 | _(@response.canceled?).must_equal true 74 | _(@response.finished?).must_equal true 75 | end 76 | end 77 | end 78 | 79 | describe "long-running assembly" do 80 | before do 81 | VCR.use_cassette "fetch_assembly_executing" do 82 | @response = Transloadit::Response.new( 83 | RestClient::Resource.new(request_uri).get 84 | ).extend!(Transloadit::Response::Assembly) 85 | end 86 | end 87 | 88 | it "must allow reloading until finished" do 89 | _(@response.finished?).must_equal false 90 | 91 | VCR.use_cassette "fetch_assembly_ok" do 92 | VCR.use_cassette "fetch_assembly_executing" do 93 | @response.reload_until_finished! 94 | end 95 | end 96 | 97 | _(@response.finished?).must_equal true 98 | end 99 | 100 | it "must raise exception if reload until finished tries exceeded" do 101 | assert_raises Transloadit::Exception::ReloadLimitReached do 102 | VCR.use_cassette "fetch_assembly_executing", allow_playback_repeats: true do 103 | @response.reload_until_finished! tries: 1 104 | end 105 | end 106 | end 107 | end 108 | 109 | describe "statuses" do 110 | it "must allow checking for upload" do 111 | VCR.use_cassette "fetch_assembly_uploading" do 112 | @response = Transloadit::Response.new( 113 | RestClient::Resource.new(request_uri).get 114 | ).extend!(Transloadit::Response::Assembly) 115 | end 116 | 117 | _(@response.finished?).must_equal false 118 | _(@response.uploading?).must_equal true 119 | _(@response.error?).must_equal false 120 | end 121 | 122 | it "must allow to check for executing" do 123 | VCR.use_cassette "fetch_assembly_executing" do 124 | @response = Transloadit::Response.new( 125 | RestClient::Resource.new(request_uri).get 126 | ).extend!(Transloadit::Response::Assembly) 127 | end 128 | 129 | _(@response.finished?).must_equal false 130 | _(@response.executing?).must_equal true 131 | _(@response.error?).must_equal false 132 | end 133 | 134 | it "must allow to check for replaying" do 135 | VCR.use_cassette "replay_assembly" do 136 | @response = Transloadit::Response.new( 137 | RestClient::Resource.new( 138 | "https://api2.transloadit.com/assemblies/55c965a063a311e6ba2d379ef10b28f7/replay" 139 | ).post({}) 140 | ).extend!(Transloadit::Response::Assembly) 141 | end 142 | 143 | _(@response.finished?).must_equal false 144 | _(@response.replaying?).must_equal true 145 | _(@response.error?).must_equal false 146 | end 147 | 148 | it "must allow to check for aborted" do 149 | VCR.use_cassette "fetch_assembly_aborted" do 150 | @response = Transloadit::Response.new( 151 | RestClient::Resource.new(request_uri).get 152 | ).extend!(Transloadit::Response::Assembly) 153 | end 154 | 155 | _(@response.finished?).must_equal true 156 | _(@response.aborted?).must_equal true 157 | end 158 | 159 | it "must allow to check for errors" do 160 | VCR.use_cassette "fetch_assembly_errors" do 161 | @response = Transloadit::Response.new( 162 | RestClient::Resource.new(request_uri).get 163 | ).extend!(Transloadit::Response::Assembly) 164 | end 165 | 166 | _(@response.error?).must_equal true 167 | _(@response.finished?).must_equal true 168 | end 169 | end 170 | end 171 | -------------------------------------------------------------------------------- /test/unit/transloadit/test_smart_cdn.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | require "json" 3 | require "open3" 4 | 5 | describe Transloadit do 6 | before do 7 | @auth_key = "my-key" 8 | @auth_secret = "my-secret" 9 | @transloadit = Transloadit.new(key: @auth_key, secret: @auth_secret) 10 | @workspace = "my-app" 11 | @template = "test-smart-cdn" 12 | @input = "inputs/prinsengracht.jpg" 13 | @expire_at = 1732550672867 14 | end 15 | 16 | def run_node_script(params) 17 | return unless ENV["TEST_NODE_PARITY"] == "1" 18 | script_path = File.expand_path("./node-smartcdn-sig", __dir__) 19 | json_input = JSON.dump(params) 20 | stdout, stderr, status = Open3.capture3("tsx #{script_path}", stdin_data: json_input) 21 | raise "Node script failed: #{stderr}" unless status.success? 22 | stdout.strip 23 | end 24 | 25 | describe "#signed_smart_cdn_url" do 26 | it "requires workspace" do 27 | assert_raises ArgumentError, "workspace is required" do 28 | @transloadit.signed_smart_cdn_url( 29 | workspace: nil, 30 | template: @template, 31 | input: @input 32 | ) 33 | end 34 | 35 | assert_raises ArgumentError, "workspace is required" do 36 | @transloadit.signed_smart_cdn_url( 37 | workspace: "", 38 | template: @template, 39 | input: @input 40 | ) 41 | end 42 | end 43 | 44 | it "requires template" do 45 | assert_raises ArgumentError, "template is required" do 46 | @transloadit.signed_smart_cdn_url( 47 | workspace: @workspace, 48 | template: nil, 49 | input: @input 50 | ) 51 | end 52 | 53 | assert_raises ArgumentError, "template is required" do 54 | @transloadit.signed_smart_cdn_url( 55 | workspace: @workspace, 56 | template: "", 57 | input: @input 58 | ) 59 | end 60 | end 61 | 62 | it "requires input" do 63 | assert_raises ArgumentError, "input is required" do 64 | @transloadit.signed_smart_cdn_url( 65 | workspace: @workspace, 66 | template: @template, 67 | input: nil 68 | ) 69 | end 70 | end 71 | 72 | it "allows empty input string" do 73 | params = { 74 | workspace: @workspace, 75 | template: @template, 76 | input: "", 77 | expire_at_ms: @expire_at 78 | } 79 | expected_url = "https://my-app.tlcdn.com/test-smart-cdn/?auth_key=my-key&exp=1732550672867&sig=sha256%3Ad5e13df4acde8d4aaa0f34534489e54098b5128c54392600ed96dd77669a533e" 80 | 81 | url = @transloadit.signed_smart_cdn_url(**params) 82 | assert_equal expected_url, url 83 | 84 | if (node_url = run_node_script(params.merge(auth_key: "my-key", auth_secret: "my-secret"))) 85 | assert_equal expected_url, node_url 86 | end 87 | end 88 | 89 | it "uses instance credentials" do 90 | params = { 91 | workspace: @workspace, 92 | template: @template, 93 | input: @input, 94 | expire_at_ms: @expire_at 95 | } 96 | expected_url = "https://my-app.tlcdn.com/test-smart-cdn/inputs%2Fprinsengracht.jpg?auth_key=my-key&exp=1732550672867&sig=sha256%3A8620fc2a22aec6081cde730b7f3f29c0d8083f58a68f62739e642b3c03709139" 97 | 98 | url = @transloadit.signed_smart_cdn_url(**params) 99 | assert_equal expected_url, url 100 | 101 | if (node_url = run_node_script(params.merge(auth_key: "my-key", auth_secret: "my-secret"))) 102 | assert_equal expected_url, node_url 103 | end 104 | end 105 | 106 | it "includes empty width parameter" do 107 | params = { 108 | workspace: @workspace, 109 | template: @template, 110 | input: @input, 111 | expire_at_ms: @expire_at, 112 | url_params: { 113 | width: "", 114 | height: 200 115 | } 116 | } 117 | expected_url = "https://my-app.tlcdn.com/test-smart-cdn/inputs%2Fprinsengracht.jpg?auth_key=my-key&exp=1732550672867&height=200&width=&sig=sha256%3Aebf562722c504839db97165e657583f74192ac4ab580f1a0dd67d3d868b4ced3" 118 | 119 | url = @transloadit.signed_smart_cdn_url(**params) 120 | assert_equal expected_url, url 121 | 122 | if (node_url = run_node_script(params.merge(auth_key: "my-key", auth_secret: "my-secret"))) 123 | assert_equal expected_url, node_url 124 | end 125 | end 126 | 127 | it "handles nil values in parameters" do 128 | params = { 129 | workspace: @workspace, 130 | template: @template, 131 | input: @input, 132 | expire_at_ms: @expire_at, 133 | url_params: { 134 | width: nil, 135 | height: 200 136 | } 137 | } 138 | expected_url = "https://my-app.tlcdn.com/test-smart-cdn/inputs%2Fprinsengracht.jpg?auth_key=my-key&exp=1732550672867&height=200&sig=sha256%3Ad6897a0cb527a14eaab13c54b06f53527797c553d8b7e5d0b1a5df237212f083" 139 | 140 | url = @transloadit.signed_smart_cdn_url(**params) 141 | assert_equal expected_url, url 142 | 143 | if (node_url = run_node_script(params.merge(auth_key: "my-key", auth_secret: "my-secret"))) 144 | assert_equal expected_url, node_url 145 | end 146 | end 147 | 148 | it "handles array values in parameters" do 149 | params = { 150 | workspace: @workspace, 151 | template: @template, 152 | input: @input, 153 | expire_at_ms: @expire_at, 154 | url_params: { 155 | tags: ["landscape", "amsterdam", nil, ""], 156 | height: 200 157 | } 158 | } 159 | expected_url = "https://my-app.tlcdn.com/test-smart-cdn/inputs%2Fprinsengracht.jpg?auth_key=my-key&exp=1732550672867&height=200&tags=landscape&tags=amsterdam&tags=&sig=sha256%3Aff46eb0083d64b250b2e4510380e333f67da855b2401493dee7a706a47957d3f" 160 | 161 | url = @transloadit.signed_smart_cdn_url(**params) 162 | assert_equal expected_url, url 163 | 164 | if (node_url = run_node_script(params.merge(auth_key: "my-key", auth_secret: "my-secret"))) 165 | assert_equal expected_url, node_url 166 | end 167 | end 168 | end 169 | end 170 | -------------------------------------------------------------------------------- /test/unit/transloadit/test_step.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe Transloadit::Step do 4 | it "must allow initialization" do 5 | _(Transloadit::Step.new("store", "/s3/store")) 6 | .must_be_kind_of Transloadit::Step 7 | end 8 | 9 | describe "when initialized" do 10 | before do 11 | @name = "store" 12 | @robot = "/s3/store" 13 | @key = "aws-access-key-id" 14 | @secret = "aws-secret-access-key" 15 | @bucket = "s3-bucket-name" 16 | 17 | @step = Transloadit::Step.new @name, @robot, 18 | key: @key, 19 | secret: @secret, 20 | bucket: @bucket 21 | end 22 | 23 | it "should use the name given" do 24 | _(@step.name).must_equal @name 25 | end 26 | 27 | it "must remember the type" do 28 | _(@step.robot).must_equal @robot 29 | end 30 | 31 | it "must remember the parameters" do 32 | _(@step.options).must_equal( 33 | key: @key, 34 | secret: @secret, 35 | bucket: @bucket 36 | ) 37 | end 38 | 39 | it "must inspect like a hash" do 40 | _(@step.inspect).must_equal @step.to_hash[@step.name].inspect 41 | end 42 | 43 | it "must produce Transloadit-compatible hash output" do 44 | _(@step.to_hash).must_equal( 45 | @step.name => { 46 | robot: @robot, 47 | key: @key, 48 | secret: @secret, 49 | bucket: @bucket 50 | } 51 | ) 52 | end 53 | 54 | it "must produce Transloadit-compatible JSON output" do 55 | _(@step.to_json).must_equal MultiJson.dump(@step.to_hash) 56 | end 57 | end 58 | 59 | describe "when using alternative inputs" do 60 | before do 61 | @step = Transloadit::Step.new "resize", "/image/resize" 62 | end 63 | 64 | it "must allow using the original file as input" do 65 | _(@step.use(:original)).must_equal ":original" 66 | _(@step.options[:use]).must_equal ":original" 67 | end 68 | 69 | it "must allow using another step" do 70 | input = Transloadit::Step.new "thumbnail", "/video/thumbnail" 71 | 72 | _(@step.use(input)).must_equal [input.name] 73 | _(@step.options[:use]).must_equal [input.name] 74 | end 75 | 76 | it "must allow using multiple steps" do 77 | inputs = [ 78 | Transloadit::Step.new("thumbnail", "/video/thumbnail"), 79 | Transloadit::Step.new("resize", "/image/resize") 80 | ] 81 | 82 | _(@step.use(inputs)).must_equal(inputs.map { |i| i.name }) 83 | _(@step.options[:use]).must_equal(inputs.map { |i| i.name }) 84 | end 85 | 86 | it "must allow using nothing" do 87 | @step.use :original 88 | assert_nil @step.use(nil) 89 | _(@step.options.keys).wont_include(:use) 90 | end 91 | 92 | it "must include the used steps in the hash output" do 93 | _(@step.use(:original)).must_equal ":original" 94 | _(@step.to_hash[@step.name][:use]).must_equal ":original" 95 | end 96 | end 97 | end 98 | -------------------------------------------------------------------------------- /test/unit/transloadit/test_template.rb: -------------------------------------------------------------------------------- 1 | require "test_helper" 2 | 3 | describe Transloadit::Template do 4 | it "must inherit from Transloadit::ApiModel class" do 5 | _(Transloadit::Template < Transloadit::ApiModel).must_equal true 6 | end 7 | 8 | describe "using template API methods" do 9 | include WebMock::API 10 | 11 | before do 12 | WebMock.reset! 13 | @transloadit = Transloadit.new(key: "") 14 | @template = Transloadit::Template.new @transloadit 15 | end 16 | 17 | it "must allow to create new template" do 18 | VCR.use_cassette "create_template" do 19 | response = @template.create( 20 | { 21 | name: "foo", 22 | template: {"key" => "value"} 23 | } 24 | ) 25 | _(response["ok"]).must_equal "TEMPLATE_CREATED" 26 | _(response["template_name"]).must_equal "foo" 27 | _(response["template_content"]["key"]).must_equal "value" 28 | end 29 | end 30 | 31 | describe "when fetching all templates" do 32 | it "must perform GET request to /templates" do 33 | stub = stub_request(:get, "https://api2.transloadit.com/templates?params=%7B%22auth%22:%7B%22key%22:%22%22%7D%7D") 34 | @template.list 35 | 36 | assert_requested(stub) 37 | end 38 | 39 | it "must return a list of items" do 40 | VCR.use_cassette "fetch_templates" do 41 | response = @template.list 42 | 43 | _(response["items"]).must_equal [] 44 | _(response["count"]).must_equal 0 45 | end 46 | end 47 | end 48 | 49 | describe "when fetching single template" do 50 | it "must perform GET request to /templates/[id]" do 51 | stub = stub_request( 52 | :get, 53 | "https://api2.transloadit.com/templates/76fe5df1c93a0a530f3e583805cf98b4?params=%7B%22auth%22:%7B%22key%22:%22%22%7D%7D" 54 | ) 55 | @template.get "76fe5df1c93a0a530f3e583805cf98b4" 56 | 57 | assert_requested(stub) 58 | end 59 | 60 | it "must get template with specified id" do 61 | VCR.use_cassette "fetch_template" do 62 | response = @template.get "76fe5df1c93a0a530f3e583805cf98b4" 63 | _(response["ok"]).must_equal "TEMPLATE_FOUND" 64 | _(response["template_id"]).must_equal "76fe5df1c93a0a530f3e583805cf98b4" 65 | end 66 | end 67 | end 68 | 69 | describe "when updating template" do 70 | it "must perform PUT request to templates/[id]" do 71 | url = "https://api2.transloadit.com/templates/76fe5df1c93a0a530f3e583805cf98b4" 72 | stub_request(:put, url) 73 | @template.update( 74 | "76fe5df1c93a0a530f3e583805cf98b4", 75 | {name: "foo", template: {key: "value"}} 76 | ) 77 | 78 | assert_requested(:put, url) do |req| 79 | values = values_from_post_body(req.body) 80 | data = MultiJson.load(values["params"]) 81 | _(data["name"]).must_equal "foo" 82 | _(data["template"]["key"]).must_equal "value" 83 | end 84 | end 85 | 86 | it "must update template with specified id" do 87 | VCR.use_cassette "update_template" do 88 | response = @template.update "55c965a063a311e6ba2d379ef10b28f7" 89 | 90 | _(response["ok"]).must_equal "TEMPLATE_UPDATED" 91 | _(response["template_id"]).must_equal "55c965a063a311e6ba2d379ef10b28f7" 92 | end 93 | end 94 | end 95 | 96 | describe "when deleting a template" do 97 | it "must perform DELETE request to templates/[id]" do 98 | stub = stub_request(:delete, "https://api2.transloadit.com/templates/76fe5df1c93a0a530f3e583805cf98b4") 99 | @template.delete "76fe5df1c93a0a530f3e583805cf98b4" 100 | 101 | assert_requested(stub) 102 | end 103 | 104 | it "must delete specified template templates" do 105 | VCR.use_cassette "delete_template" do 106 | response = @template.delete "55c965a063a311e6ba2d379ef10b28f7" 107 | 108 | _(response["ok"]).must_equal "TEMPLATE_DELETED" 109 | end 110 | end 111 | end 112 | end 113 | end 114 | -------------------------------------------------------------------------------- /transloadit.gemspec: -------------------------------------------------------------------------------- 1 | $:.unshift File.expand_path("../lib", __FILE__) 2 | 3 | require "transloadit/version" 4 | 5 | Gem::Specification.new do |gem| 6 | gem.name = "transloadit" 7 | gem.version = Transloadit::VERSION 8 | gem.platform = Gem::Platform::RUBY 9 | 10 | gem.authors = ["Stephen Touset", "Robin Mehner", "Kevin van Zonneveld"] 11 | gem.email = %w[stephen@touset.org robin@coding-robin.de kevin@transloadit.com] 12 | gem.homepage = "https://github.com/transloadit/ruby-sdk/" 13 | gem.license = "MIT" 14 | 15 | gem.summary = "Official Ruby gem for Transloadit" 16 | gem.description = "The transloadit gem allows you to automate uploading files through the Transloadit REST API" 17 | 18 | gem.required_rubygems_version = ">= 2.2.0" 19 | gem.required_ruby_version = ">= 3.0.0" 20 | 21 | gem.files = `git ls-files`.split("\n") 22 | gem.require_paths = %w[lib] 23 | 24 | gem.add_dependency "rest-client" 25 | gem.add_dependency "multi_json" 26 | gem.add_dependency "mime-types" 27 | 28 | gem.add_development_dependency "rake" 29 | gem.add_development_dependency "minitest" 30 | gem.add_development_dependency "simplecov" 31 | gem.add_development_dependency "standard" 32 | 33 | gem.add_development_dependency "vcr" 34 | gem.add_development_dependency "webmock" 35 | 36 | gem.add_development_dependency "yard" 37 | gem.add_development_dependency "kramdown" # for YARD rdoc formatting 38 | end 39 | --------------------------------------------------------------------------------