diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index da82256..07981d4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,9 +18,6 @@ on: type: string push: branches: [ main, master ] - paths: - - 'lib/getstream_ruby/version.rb' - - 'getstream-ruby.gemspec' jobs: create-release-pr: diff --git a/Gemfile b/Gemfile index 0f70349..5e8accf 100644 --- a/Gemfile +++ b/Gemfile @@ -2,7 +2,7 @@ source 'https://rubygems.org' -gemspec +gemspec name: 'getstream-ruby' group :development, :test do diff --git a/getstream-ruby.gemspec b/getstream-ruby.gemspec index b952e1d..abd861c 100644 --- a/getstream-ruby.gemspec +++ b/getstream-ruby.gemspec @@ -20,6 +20,7 @@ Gem::Specification.new do |spec| spec.add_dependency 'dotenv', '~> 2.0' spec.add_dependency 'faraday', '~> 2.0' + spec.add_dependency 'faraday-multipart', '~> 1.0' spec.add_dependency 'faraday-retry', '~> 2.0' spec.add_dependency 'json', '~> 2.0' spec.add_dependency 'jwt', '~> 2.0' diff --git a/lib/getstream_ruby/client.rb b/lib/getstream_ruby/client.rb index 3d2c504..f7bc9a2 100644 --- a/lib/getstream_ruby/client.rb +++ b/lib/getstream_ruby/client.rb @@ -2,6 +2,7 @@ require 'faraday' require 'faraday/retry' +require 'faraday/multipart' require 'json' require 'jwt' require_relative 'generated/base_model' @@ -86,6 +87,10 @@ def make_request(method, path, query_params: nil, body: nil) def request(method, path, data = {}) # Add API key to query parameters query_params = { api_key: @configuration.api_key } + + # Check if this is a file upload request that needs multipart + return make_multipart_request(method, path, query_params, data) if multipart_request?(data) + response = @connection.send(method) do |req| req.url path, query_params @@ -105,6 +110,7 @@ def request(method, path, data = {}) def build_connection Faraday.new(url: @configuration.base_url) do |conn| + conn.request :multipart conn.request :retry, { max: 3, interval: 0.05, @@ -159,6 +165,81 @@ def handle_response(response) end end + def multipart_request?(data) + return false if data.nil? || data == {} + + # Check if data is a FileUploadRequest or ImageUploadRequest + data.is_a?(GetStream::Generated::Models::FileUploadRequest) || + data.is_a?(GetStream::Generated::Models::ImageUploadRequest) + end + + def make_multipart_request(method, path, query_params, data) + # Build multipart form data + payload = {} + + # Handle file field + raise APIError, 'file name must be provided' if data.file.nil? || data.file.empty? + + file_path = data.file + raise APIError, "file not found: #{file_path}" unless File.exist?(file_path) + + # Determine content type + content_type = detect_content_type(file_path) + + # Add file as multipart (FilePart handles file opening/closing) + payload[:file] = Faraday::Multipart::FilePart.new( + file_path, + content_type, + File.basename(file_path), + ) + + # Add user field if present (as JSON string) + if data.user + user_json = data.user.to_json + payload[:user] = user_json + end + + # Add upload_sizes field for ImageUploadRequest (as JSON string) + if data.is_a?(GetStream::Generated::Models::ImageUploadRequest) && data.upload_sizes + upload_sizes_json = data.upload_sizes.to_json + payload[:upload_sizes] = upload_sizes_json + end + + response = @connection.send(method) do |req| + + req.url path, query_params + req.headers['Authorization'] = generate_auth_header + req.headers['stream-auth-type'] = 'jwt' + req.headers['X-Stream-Client'] = user_agent + req.body = payload + + end + + handle_response(response) + rescue Faraday::Error => e + raise APIError, "Request failed: #{e.message}" + end + + def detect_content_type(file_path) + ext = File.extname(file_path).downcase + case ext + when '.png' + 'image/png' + when '.jpg', '.jpeg' + 'image/jpeg' + when '.gif' + 'image/gif' + when '.pdf' + 'application/pdf' + when '.txt' + 'text/plain' + when '.json' + 'application/json' + else + 'application/octet-stream' + end + end + end end diff --git a/spec/integration/feed_integration_spec.rb b/spec/integration/feed_integration_spec.rb index acdec80..703fff9 100644 --- a/spec/integration/feed_integration_spec.rb +++ b/spec/integration/feed_integration_spec.rb @@ -825,6 +825,41 @@ end + describe 'File Upload' do + + it 'uploads a file using multipart form data' do + + puts "\nšŸ“¤ Testing file upload..." + + # Get the path to the test file (in the same directory as the spec) + test_file_path = File.join(__dir__, 'upload-test.png') + raise "Test file not found: #{test_file_path}" unless File.exist?(test_file_path) + + # Create file upload request + file_upload_request = GetStream::Generated::Models::FileUploadRequest.new( + file: test_file_path, + user: GetStream::Generated::Models::OnlyUserID.new(id: test_user_id_1), + ) + + # Upload the file + upload_response = client.common.upload_file(file_upload_request) + + expect(upload_response).to be_a(GetStreamRuby::StreamResponse) + expect(upload_response.file).not_to be_nil + expect(upload_response.file).to be_a(String) + expect(upload_response.file).not_to be_empty + + puts 'āœ… File uploaded successfully' + puts " File URL: #{upload_response.file}" + puts " Thumbnail URL: #{upload_response.thumb_url}" if upload_response.thumb_url + + # Verify the URL is a valid URL + expect(upload_response.file).to match(/^https?:\/\//) + + end + + end + describe 'Real World Usage Demo' do it 'demonstrates real-world usage patterns' do diff --git a/spec/integration/upload-test.png b/spec/integration/upload-test.png new file mode 100644 index 0000000..c1876c3 Binary files /dev/null and b/spec/integration/upload-test.png differ