diff --git a/examples/api_keys.rb b/examples/api_keys.rb index c0ea37e..0b728d3 100644 --- a/examples/api_keys.rb +++ b/examples/api_keys.rb @@ -19,6 +19,14 @@ def list puts keys end +def list_paginated + # List with pagination parameters + paginated_keys = Resend::ApiKeys.list({ limit: 10, after: "key_id_here" }) + puts "Paginated response:" + puts paginated_keys + puts "Has more: #{paginated_keys[:has_more]}" if paginated_keys[:has_more] +end + def remove key = Resend::ApiKeys.create({name: "t"}) puts "created api key id: #{key[:id]}" diff --git a/examples/audiences.rb b/examples/audiences.rb index 1838b89..c6dc942 100644 --- a/examples/audiences.rb +++ b/examples/audiences.rb @@ -19,6 +19,11 @@ def example audiences = Resend::Audiences.list puts audiences + # Example with pagination + paginated_audiences = Resend::Audiences.list({ limit: 5 }) + puts "Paginated audiences (limit 5):" + puts paginated_audiences + Resend::Audiences.remove audience[:id] puts "deleted #{audience[:id]}" end diff --git a/examples/broadcasts.rb b/examples/broadcasts.rb index c5ca80b..758295e 100644 --- a/examples/broadcasts.rb +++ b/examples/broadcasts.rb @@ -39,6 +39,11 @@ broadcasts = Resend::Broadcasts.list puts broadcasts +# Example with pagination +paginated_broadcasts = Resend::Broadcasts.list({ limit: 15, after: "broadcast_id_here" }) +puts "Paginated broadcasts (limit 15, after broadcast_id_here):" +puts paginated_broadcasts + retrieved = Resend::Broadcasts.get(broadcast[:id]) puts "retrieved #{retrieved[:id]}" diff --git a/examples/contacts.rb b/examples/contacts.rb index f7ae318..092c77a 100644 --- a/examples/contacts.rb +++ b/examples/contacts.rb @@ -43,6 +43,11 @@ def example contacts = Resend::Contacts.list(audience_id) puts contacts + # Example with pagination + paginated_contacts = Resend::Contacts.list(audience_id, { limit: 10 }) + puts "Paginated contacts (limit 10):" + puts paginated_contacts + # delete by id del = Resend::Contacts.remove(audience_id, contact[:id]) diff --git a/examples/domains.rb b/examples/domains.rb index d6655cd..d691dc3 100644 --- a/examples/domains.rb +++ b/examples/domains.rb @@ -49,6 +49,11 @@ def update def list domains = Resend::Domains.list puts domains + + # Example with pagination + paginated_domains = Resend::Domains.list({ limit: 20, before: "domain_id_here" }) + puts "Paginated domains (limit 20, before domain_id_here):" + puts paginated_domains end def remove diff --git a/examples/with_pagination.rb b/examples/with_pagination.rb new file mode 100644 index 0000000..b8324f9 --- /dev/null +++ b/examples/with_pagination.rb @@ -0,0 +1,83 @@ +# frozen_string_literal: true + +require_relative "../lib/resend" + +raise if ENV["RESEND_API_KEY"].nil? + +Resend.api_key = ENV["RESEND_API_KEY"] + +puts "=== Resend Ruby SDK Pagination Examples ===" +puts + +# API Keys pagination +puts "1. API Keys Pagination:" +puts "List first 5 API keys:" +api_keys = Resend::ApiKeys.list({ limit: 5 }) +puts api_keys +puts "Has more: #{api_keys[:has_more]}" if api_keys.key?(:has_more) +puts + +# Audiences pagination +puts "2. Audiences Pagination:" +puts "List first 10 audiences:" +audiences = Resend::Audiences.list({ limit: 10 }) +puts audiences +puts "Has more: #{audiences[:has_more]}" if audiences.key?(:has_more) +puts + +# If we have audiences, demonstrate pagination with 'after' +if audiences[:data] && !audiences[:data].empty? + first_audience_id = audiences[:data].first[:id] + puts "List audiences after '#{first_audience_id}':" + next_audiences = Resend::Audiences.list({ limit: 5, after: first_audience_id }) + puts next_audiences + puts +end + +# Domains pagination +puts "3. Domains Pagination:" +puts "List first 3 domains:" +domains = Resend::Domains.list({ limit: 3 }) +puts domains +puts "Has more: #{domains[:has_more]}" if domains.key?(:has_more) +puts + +# Broadcasts pagination +puts "4. Broadcasts Pagination:" +puts "List first 2 broadcasts:" +broadcasts = Resend::Broadcasts.list({ limit: 2 }) +puts broadcasts +puts "Has more: #{broadcasts[:has_more]}" if broadcasts.key?(:has_more) +puts + +# Contacts pagination (requires an audience ID) +puts "5. Contacts Pagination:" +if audiences[:data] && !audiences[:data].empty? + audience_id = audiences[:data].first[:id] + if audience_id && !audience_id.empty? + puts "List first 5 contacts from audience '#{audience_id}':" + begin + contacts = Resend::Contacts.list(audience_id, { limit: 5 }) + puts contacts + puts "Has more: #{contacts[:has_more]}" if contacts.key?(:has_more) + rescue Resend::Error => e + puts "Error listing contacts: #{e.message}" + puts "(This might happen if the audience has no contacts or access is restricted)" + end + else + puts "Audience ID is empty, skipping contacts pagination example" + end +else + puts "No audiences available for contacts pagination example" +end +puts + +puts "=== Pagination Parameters ===" +puts "• limit: Number of items to retrieve (1-100)" +puts "• after: ID after which to retrieve more items" +puts "• before: ID before which to retrieve more items" +puts +puts "Example usage:" +puts "Resend::ApiKeys.list({ limit: 25, after: 'key_123' })" +puts "Resend::Domains.list({ limit: 10, before: 'domain_456' })" +puts "Resend::Contacts.list('audience_id', { limit: 50 })" \ No newline at end of file diff --git a/lib/resend.rb b/lib/resend.rb index 8ac5d02..758dea9 100644 --- a/lib/resend.rb +++ b/lib/resend.rb @@ -6,9 +6,11 @@ # Utils require "httparty" require "json" +require "cgi" require "resend/errors" require "resend/client" require "resend/request" +require "resend/pagination_helper" # API Operations require "resend/audiences" diff --git a/lib/resend/api_keys.rb b/lib/resend/api_keys.rb index 1701ff1..ed88a3d 100644 --- a/lib/resend/api_keys.rb +++ b/lib/resend/api_keys.rb @@ -11,8 +11,8 @@ def create(params) end # https://resend.com/docs/api-reference/api-keys/list-api-keys - def list - path = "api-keys" + def list(params = {}) + path = Resend::PaginationHelper.build_paginated_path("api-keys", params) Resend::Request.new(path, {}, "get").perform end diff --git a/lib/resend/audiences.rb b/lib/resend/audiences.rb index 04afe05..29bb38e 100644 --- a/lib/resend/audiences.rb +++ b/lib/resend/audiences.rb @@ -17,8 +17,8 @@ def get(audience_id = "") end # https://resend.com/docs/api-reference/audiences/list-audiences - def list - path = "audiences" + def list(params = {}) + path = Resend::PaginationHelper.build_paginated_path("audiences", params) Resend::Request.new(path, {}, "get").perform end diff --git a/lib/resend/broadcasts.rb b/lib/resend/broadcasts.rb index 62d835b..16e2b9d 100644 --- a/lib/resend/broadcasts.rb +++ b/lib/resend/broadcasts.rb @@ -23,8 +23,8 @@ def send(params = {}) end # https://resend.com/docs/api-reference/broadcasts/list-broadcasts - def list - path = "broadcasts" + def list(params = {}) + path = Resend::PaginationHelper.build_paginated_path("broadcasts", params) Resend::Request.new(path, {}, "get").perform end diff --git a/lib/resend/contacts.rb b/lib/resend/contacts.rb index a1e4786..12b5be2 100644 --- a/lib/resend/contacts.rb +++ b/lib/resend/contacts.rb @@ -26,9 +26,10 @@ def get(audience_id, id) # List contacts in an audience # # @param audience_id [String] the audience id + # @param params [Hash] optional pagination parameters # https://resend.com/docs/api-reference/contacts/list-contacts - def list(audience_id) - path = "audiences/#{audience_id}/contacts" + def list(audience_id, params = {}) + path = Resend::PaginationHelper.build_paginated_path("audiences/#{audience_id}/contacts", params) Resend::Request.new(path, {}, "get").perform end diff --git a/lib/resend/domains.rb b/lib/resend/domains.rb index d3a220c..f90516c 100644 --- a/lib/resend/domains.rb +++ b/lib/resend/domains.rb @@ -22,9 +22,9 @@ def get(domain_id = "") Resend::Request.new(path, {}, "get").perform end - # https://resend.com/docs/api-reference/api-keys/list-api-keys - def list - path = "domains" + # https://resend.com/docs/api-reference/domains/list-domains + def list(params = {}) + path = Resend::PaginationHelper.build_paginated_path("domains", params) Resend::Request.new(path, {}, "get").perform end diff --git a/lib/resend/pagination_helper.rb b/lib/resend/pagination_helper.rb new file mode 100644 index 0000000..18417a4 --- /dev/null +++ b/lib/resend/pagination_helper.rb @@ -0,0 +1,30 @@ +# frozen_string_literal: true + +module Resend + # Helper class for building paginated query strings + class PaginationHelper + class << self + # Builds a paginated path with query parameters + # + # @param base_path [String] the base API path + # @param query_params [Hash] optional pagination parameters + # @option query_params [Integer] :limit Number of items to retrieve (max 100) + # @option query_params [String] :after ID after which to retrieve more items + # @option query_params [String] :before ID before which to retrieve more items + # @return [String] the path with query parameters + def build_paginated_path(base_path, query_params = nil) + return base_path if query_params.nil? || query_params.empty? + + # Filter out nil values and convert to string keys + filtered_params = query_params.compact.transform_keys(&:to_s) + + # Build query string + query_string = filtered_params.map { |k, v| "#{k}=#{CGI.escape(v.to_s)}" }.join("&") + + return base_path if query_string.empty? + + "#{base_path}?#{query_string}" + end + end + end +end diff --git a/spec/pagination_spec.rb b/spec/pagination_spec.rb new file mode 100644 index 0000000..aa42bcc --- /dev/null +++ b/spec/pagination_spec.rb @@ -0,0 +1,206 @@ +# frozen_string_literal: true + +RSpec.describe "Pagination" do + before do + Resend.configure do |config| + config.api_key = "re_123" + end + end + + describe "PaginationHelper" do + describe ".build_paginated_path" do + it "returns base path when no query params provided" do + path = Resend::PaginationHelper.build_paginated_path("api-keys") + expect(path).to eq("api-keys") + end + + it "returns base path when empty query params provided" do + path = Resend::PaginationHelper.build_paginated_path("api-keys", {}) + expect(path).to eq("api-keys") + end + + it "builds path with limit parameter" do + path = Resend::PaginationHelper.build_paginated_path("api-keys", { limit: 10 }) + expect(path).to eq("api-keys?limit=10") + end + + it "builds path with after parameter" do + path = Resend::PaginationHelper.build_paginated_path("api-keys", { after: "key_123" }) + expect(path).to eq("api-keys?after=key_123") + end + + it "builds path with before parameter" do + path = Resend::PaginationHelper.build_paginated_path("api-keys", { before: "key_456" }) + expect(path).to eq("api-keys?before=key_456") + end + + it "builds path with multiple parameters" do + path = Resend::PaginationHelper.build_paginated_path("api-keys", { limit: 25, after: "key_123" }) + expect(path).to include("limit=25") + expect(path).to include("after=key_123") + expect(path).to include("api-keys?") + expect(path).to include("&") + end + + it "URL encodes parameter values" do + path = Resend::PaginationHelper.build_paginated_path("api-keys", { after: "key with spaces" }) + expect(path).to eq("api-keys?after=key+with+spaces") + end + + it "filters out nil values" do + path = Resend::PaginationHelper.build_paginated_path("api-keys", { limit: 10, after: nil, before: "key_123" }) + expect(path).to include("limit=10") + expect(path).to include("before=key_123") + expect(path).not_to include("after") + end + + it "builds path with any limit value (validation deferred to API)" do + path = Resend::PaginationHelper.build_paginated_path("api-keys", { limit: 0 }) + expect(path).to eq("api-keys?limit=0") + + path = Resend::PaginationHelper.build_paginated_path("api-keys", { limit: 101 }) + expect(path).to eq("api-keys?limit=101") + end + end + end + + describe "ApiKeys.list with pagination" do + it "should accept pagination parameters" do + resp = { + "object": "list", + "data": [ + { + "id": "6e3c3d83-05dc-4b51-acfc-fe8972738bd0", + "name": "test1", + "created_at": "2023-04-21T01:31:02.671414+00:00" + } + ], + "has_more": true + } + + request_instance = instance_double(Resend::Request) + allow(request_instance).to receive(:perform).and_return(resp) + allow(Resend::Request).to receive(:new) do |path, body, verb| + expect(path).to include("limit=10") + expect(path).to include("after=key_123") + request_instance + end + + params = { limit: 10, after: "key_123" } + result = Resend::ApiKeys.list(params) + expect(result[:object]).to eql("list") + expect(result[:has_more]).to be true + expect(result[:data].length).to eql(1) + end + end + + describe "Audiences.list with pagination" do + it "should accept pagination parameters" do + resp = { + "object": "list", + "data": [ + { + "id": "78261eea-8f8b-4381-83c6-79fa7120f1cf", + "name": "Developers" + } + ], + "has_more": false + } + + request_instance = instance_double(Resend::Request) + allow(request_instance).to receive(:perform).and_return(resp) + allow(Resend::Request).to receive(:new) do |path, body, verb| + expect(path).to include("limit=5") + request_instance + end + + params = { limit: 5 } + result = Resend::Audiences.list(params) + expect(result[:object]).to eql("list") + expect(result[:has_more]).to be false + end + end + + describe "Broadcasts.list with pagination" do + it "should accept pagination parameters" do + resp = { + "object": "list", + "data": [ + { + "id": "b6d24b8e-af0b-4c3c-be0c-359bf9d251d2", + "name": "Weekly Newsletter" + } + ], + "has_more": true + } + + request_instance = instance_double(Resend::Request) + allow(request_instance).to receive(:perform).and_return(resp) + allow(Resend::Request).to receive(:new) do |path, body, verb| + expect(path).to include("before=broadcast_456") + request_instance + end + + params = { before: "broadcast_456" } + result = Resend::Broadcasts.list(params) + expect(result[:object]).to eql("list") + expect(result[:has_more]).to be true + end + end + + describe "Contacts.list with pagination" do + it "should accept pagination parameters" do + resp = { + "object": "list", + "data": [ + { + "id": "e169aa45-1ecf-4183-9955-b1499d5701d3", + "email": "steve.wozniak@gmail.com" + } + ], + "has_more": false + } + + request_instance = instance_double(Resend::Request) + allow(request_instance).to receive(:perform).and_return(resp) + allow(Resend::Request).to receive(:new) do |path, body, verb| + expect(path).to include("audiences/audience_123/contacts") + expect(path).to include("limit=20") + request_instance + end + + params = { limit: 20 } + result = Resend::Contacts.list("audience_123", params) + expect(result[:object]).to eql("list") + expect(result[:has_more]).to be false + end + end + + describe "Domains.list with pagination" do + it "should accept pagination parameters" do + resp = { + "object": "list", + "data": [ + { + "id": "d91cd9bd-1176-453e-8fc1-35364d380206", + "name": "example.com" + } + ], + "has_more": true + } + + request_instance = instance_double(Resend::Request) + allow(request_instance).to receive(:perform).and_return(resp) + allow(Resend::Request).to receive(:new) do |path, body, verb| + expect(path).to include("limit=50") + expect(path).to include("after=domain_789") + request_instance + end + + params = { limit: 50, after: "domain_789" } + result = Resend::Domains.list(params) + expect(result[:object]).to eql("list") + expect(result[:has_more]).to be true + end + end +end \ No newline at end of file