Skip to content

Commit aab63fd

Browse files
committed
wip
1 parent d18e470 commit aab63fd

File tree

5 files changed

+217
-0
lines changed

5 files changed

+217
-0
lines changed

lib/transloadit.rb

+1
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@ class Transloadit
1111
autoload :Exception, "transloadit/exception"
1212
autoload :Request, "transloadit/request"
1313
autoload :Response, "transloadit/response"
14+
autoload :SmartCDN, "transloadit/smart_cdn"
1415
autoload :Step, "transloadit/step"
1516
autoload :Template, "transloadit/template"
1617
autoload :VERSION, "transloadit/version"

lib/transloadit/smart_cdn.rb

+62
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
require 'openssl'
2+
require 'uri'
3+
require 'cgi'
4+
5+
class Transloadit
6+
class SmartCDN
7+
# @param workspace [String] Workspace slug
8+
# @param template [String] Template slug or template ID
9+
# @param input [String] Input value that is provided as `${fields.input}` in the template
10+
# @param auth_key [String] Authentication key
11+
# @param auth_secret [String] Authentication secret
12+
# @param url_params [Hash] Additional parameters for the URL query string (optional)
13+
# @param expire_in_ms [Integer] Expiration time in milliseconds from now (optional)
14+
# @param expire_at_ms [Integer] Expiration time as Unix timestamp in milliseconds (optional)
15+
# @return [String] Signed Smart CDN URL
16+
def self.signed_url(workspace:, template:, input:, auth_key:, auth_secret:, url_params: {}, expire_in_ms: nil, expire_at_ms: nil)
17+
raise ArgumentError, 'workspace is required' if workspace.nil? || workspace.empty?
18+
raise ArgumentError, 'template is required' if template.nil? || template.empty?
19+
raise ArgumentError, 'input is required' if input.nil?
20+
21+
workspace_slug = CGI.escape(workspace)
22+
template_slug = CGI.escape(template)
23+
input_field = CGI.escape(input)
24+
25+
expire_at = if expire_at_ms
26+
expire_at_ms
27+
elsif expire_in_ms
28+
(Time.now.to_f * 1000).to_i + expire_in_ms
29+
else
30+
(Time.now.to_f * 1000).to_i + (1 * 60 * 60 * 1000) # 1 hour default
31+
end
32+
33+
query_params = {}
34+
url_params.each do |key, value|
35+
next if value.nil?
36+
if value.is_a?(Array)
37+
value.each do |val|
38+
next if val.nil?
39+
(query_params[key.to_s] ||= []) << val.to_s
40+
end
41+
else
42+
query_params[key.to_s] = [value.to_s]
43+
end
44+
end
45+
46+
query_params['auth_key'] = [auth_key]
47+
query_params['exp'] = [expire_at.to_s]
48+
49+
# Sort parameters to ensure consistent ordering
50+
sorted_params = query_params.sort.map do |key, values|
51+
values.compact.map { |v| "#{CGI.escape(key)}=#{CGI.escape(v)}" }
52+
end.flatten.reject(&:empty?).join('&')
53+
54+
string_to_sign = "#{workspace_slug}/#{template_slug}/#{input_field}?#{sorted_params}"
55+
56+
signature = OpenSSL::HMAC.hexdigest('sha256', auth_secret, string_to_sign)
57+
58+
final_params = "#{sorted_params}&sig=#{CGI.escape("sha256:#{signature}")}"
59+
"https://#{workspace_slug}.tlcdn.com/#{template_slug}/#{input_field}?#{final_params}"
60+
end
61+
end
62+
end

test/smart_cdn_compare.rb

+47
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
#!/usr/bin/env ruby
2+
puts "Current directory: #{Dir.pwd}"
3+
puts "Load path: #{$LOAD_PATH}"
4+
$LOAD_PATH.unshift File.expand_path('../lib', __dir__)
5+
puts "Updated load path: #{$LOAD_PATH}"
6+
require 'bundler/setup'
7+
puts "Loaded bundler/setup"
8+
require 'transloadit'
9+
puts "Loaded transloadit"
10+
puts "Loading smart_cdn..."
11+
require_relative '../lib/transloadit/smart_cdn'
12+
puts "Loaded smart_cdn"
13+
14+
# Get CLI arguments
15+
expire_at = ARGV[0]
16+
workspace = ARGV[1]
17+
template = ARGV[2]
18+
input = ARGV[3]
19+
20+
puts "Environment variables:"
21+
puts "TRANSLOADIT_KEY: #{ENV['TRANSLOADIT_KEY']}"
22+
puts "TRANSLOADIT_SECRET: #{ENV['TRANSLOADIT_SECRET']}"
23+
24+
# Generate URL using Ruby implementation
25+
url = Transloadit::SmartCDN.signed_url(
26+
workspace: workspace,
27+
template: template,
28+
input: input,
29+
auth_key: ENV['TRANSLOADIT_KEY'],
30+
auth_secret: ENV['TRANSLOADIT_SECRET'],
31+
expire_at_ms: expire_at.to_i
32+
)
33+
34+
puts "Ruby: #{url}"
35+
36+
# Execute Node.js implementation and capture output
37+
node_url = `tsx dev/smartcdn-sig.ts #{expire_at} #{workspace} #{template} #{input}`.strip
38+
puts "Node: #{node_url}"
39+
40+
# Compare outputs
41+
if url == node_url
42+
puts "\n✅ Outputs match!"
43+
exit 0
44+
else
45+
puts "\n❌ Outputs differ!"
46+
exit 1
47+
end
+56
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
require 'test_helper'
2+
3+
describe Transloadit::SmartCDN do
4+
before do
5+
@auth_key = 'my-key'
6+
@auth_secret = 'my-secret'
7+
@workspace = 'my-app'
8+
@template = 'test-smart-cdn'
9+
@input = 'inputs/prinsengracht.jpg'
10+
@expire_at = 1732550672867
11+
end
12+
13+
it 'generates correct signed urls' do
14+
url = Transloadit::SmartCDN.signed_url(
15+
workspace: @workspace,
16+
template: @template,
17+
input: @input,
18+
auth_key: @auth_key,
19+
auth_secret: @auth_secret,
20+
expire_at_ms: @expire_at
21+
)
22+
23+
expected_url = 'https://my-app.tlcdn.com/test-smart-cdn/inputs%2Fprinsengracht.jpg?auth_key=my-app&exp=1732550672867&sig=sha256%3A44e74094d8eb12598640ca339b773e1e0366365f9ba652ec2099da79d6efae1b'
24+
assert_equal expected_url, url
25+
end
26+
27+
it 'handles url parameters' do
28+
url = Transloadit::SmartCDN.signed_url(
29+
workspace: @workspace,
30+
template: @template,
31+
input: @input,
32+
auth_key: @auth_key,
33+
auth_secret: @auth_secret,
34+
expire_at_ms: @expire_at,
35+
url_params: {
36+
width: 100,
37+
height: 200
38+
}
39+
)
40+
41+
assert_match(/width=100/, url)
42+
assert_match(/height=200/, url)
43+
end
44+
45+
it 'requires workspace' do
46+
assert_raises(ArgumentError) do
47+
Transloadit::SmartCDN.signed_url(
48+
workspace: '',
49+
template: @template,
50+
input: @input,
51+
auth_key: @auth_key,
52+
auth_secret: @auth_secret
53+
)
54+
end
55+
end
56+
end
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
require 'test_helper'
2+
3+
describe Transloadit::SmartCDN do
4+
# Only run these tests if we're in CI or explicitly requested
5+
if ENV['CI'] == 'true' || ENV['TEST_NODE_PARITY'] == 'true'
6+
before do
7+
@auth_key = 'my-key'
8+
@auth_secret = 'my-secret'
9+
@workspace = 'my-app'
10+
@template = 'test-smart-cdn'
11+
@input = 'inputs/prinsengracht.jpg'
12+
@expire_at = 1732550672867
13+
14+
# Skip if tsx is not available
15+
skip 'tsx not available' unless system('which tsx > /dev/null 2>&1')
16+
end
17+
18+
it 'generates urls that match node implementation' do
19+
url = Transloadit::SmartCDN.signed_url(
20+
workspace: @workspace,
21+
template: @template,
22+
input: @input,
23+
auth_key: @auth_key,
24+
auth_secret: @auth_secret,
25+
expire_at_ms: @expire_at
26+
)
27+
28+
node_url = `tsx #{File.expand_path('../../../dev/smartcdn-sig.ts', __dir__)} #{@expire_at} #{@workspace} #{@template} #{@input}`.strip
29+
assert_equal node_url, url
30+
end
31+
32+
it 'handles url parameters the same as node' do
33+
url = Transloadit::SmartCDN.signed_url(
34+
workspace: @workspace,
35+
template: @template,
36+
input: @input,
37+
auth_key: @auth_key,
38+
auth_secret: @auth_secret,
39+
expire_at_ms: @expire_at,
40+
url_params: {
41+
width: 100,
42+
height: 200
43+
}
44+
)
45+
46+
node_url = `tsx #{File.expand_path('../../../dev/smartcdn-sig.ts', __dir__)} #{@expire_at} #{@workspace} #{@template} #{@input} width=100 height=200`.strip
47+
assert_equal node_url, url
48+
end
49+
end
50+
end
51+
it 'handles nil values in parameters the same as node' do

0 commit comments

Comments
 (0)