channex 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (51) hide show
  1. checksums.yaml +7 -0
  2. data/CHANGELOG.md +21 -0
  3. data/LICENSE +21 -0
  4. data/README.md +281 -0
  5. data/Rakefile +12 -0
  6. data/lib/channex/client.rb +283 -0
  7. data/lib/channex/configuration.rb +52 -0
  8. data/lib/channex/errors.rb +62 -0
  9. data/lib/channex/object.rb +201 -0
  10. data/lib/channex/objects/booking_revision.rb +124 -0
  11. data/lib/channex/objects/booking_room.rb +68 -0
  12. data/lib/channex/objects/company.rb +16 -0
  13. data/lib/channex/objects/customer.rb +43 -0
  14. data/lib/channex/objects/guarantee.rb +21 -0
  15. data/lib/channex/objects/guest.rb +21 -0
  16. data/lib/channex/objects/message.rb +95 -0
  17. data/lib/channex/objects/message_thread.rb +76 -0
  18. data/lib/channex/objects/occupancy.rb +29 -0
  19. data/lib/channex/objects/review.rb +87 -0
  20. data/lib/channex/objects/service.rb +44 -0
  21. data/lib/channex/objects/tax.rb +36 -0
  22. data/lib/channex/objects.rb +22 -0
  23. data/lib/channex/resource.rb +130 -0
  24. data/lib/channex/resources/attachments.rb +36 -0
  25. data/lib/channex/resources/availability.rb +87 -0
  26. data/lib/channex/resources/availability_rules.rb +52 -0
  27. data/lib/channex/resources/booking_revisions.rb +112 -0
  28. data/lib/channex/resources/bookings.rb +72 -0
  29. data/lib/channex/resources/channels.rb +96 -0
  30. data/lib/channex/resources/facilities.rb +29 -0
  31. data/lib/channex/resources/group_users.rb +76 -0
  32. data/lib/channex/resources/groups.rb +26 -0
  33. data/lib/channex/resources/hotel_policies.rb +54 -0
  34. data/lib/channex/resources/message_threads.rb +89 -0
  35. data/lib/channex/resources/messages.rb +64 -0
  36. data/lib/channex/resources/photos.rb +117 -0
  37. data/lib/channex/resources/properties.rb +42 -0
  38. data/lib/channex/resources/property_users.rb +32 -0
  39. data/lib/channex/resources/rate_plans.rb +34 -0
  40. data/lib/channex/resources/restrictions.rb +98 -0
  41. data/lib/channex/resources/reviews.rb +41 -0
  42. data/lib/channex/resources/room_types.rb +34 -0
  43. data/lib/channex/resources/tax_sets.rb +34 -0
  44. data/lib/channex/resources/taxes.rb +60 -0
  45. data/lib/channex/resources/webhooks.rb +87 -0
  46. data/lib/channex/resources.rb +5 -0
  47. data/lib/channex/response.rb +102 -0
  48. data/lib/channex/version.rb +3 -0
  49. data/lib/channex.rb +52 -0
  50. data/sig/channex.rbs +4 -0
  51. metadata +124 -0
@@ -0,0 +1,117 @@
1
+ module Channex
2
+ module Resources
3
+ # Photos API resource
4
+ #
5
+ # Photos are associated with Properties and optionally with Room Types.
6
+ # Position 0 is used as the cover photo.
7
+ #
8
+ # @example List photos for a property
9
+ # client.photos.list(property_id: "property-uuid")
10
+ #
11
+ # @example Create a photo from URL
12
+ # client.photos.create(
13
+ # property_id: "property-uuid",
14
+ # url: "https://example.com/photo.jpg",
15
+ # position: 0,
16
+ # description: "Hotel Lobby",
17
+ # kind: "photo"
18
+ # )
19
+ #
20
+ # @example Upload a photo file then create it
21
+ # # Step 1: Upload the file to get a temporary URL
22
+ # upload_response = client.photos.upload("/path/to/photo.jpg")
23
+ # temp_url = upload_response.body["url"]
24
+ #
25
+ # # Step 2: Create the photo using the temporary URL
26
+ # client.photos.create(
27
+ # property_id: "property-uuid",
28
+ # url: temp_url,
29
+ # position: 0
30
+ # )
31
+ #
32
+ # @see https://docs.channex.io/api-v.1-documentation/photos-collection
33
+ #
34
+ class Photos < Resource
35
+ KINDS = %w[photo ad menu].freeze
36
+
37
+ # Upload a photo file and get a temporary URL
38
+ #
39
+ # This uploads the file to Channex's servers and returns a temporary URL
40
+ # that can then be used with the create method.
41
+ #
42
+ # @param file_path [String] Path to the photo file
43
+ # @return [Response] Response with "url" in body containing temporary URL
44
+ #
45
+ # @example
46
+ # response = client.photos.upload("/path/to/photo.jpg")
47
+ # temp_url = response.body["url"]
48
+ # # => "https://ams3.digitaloceanspaces.com/temporaryphotos/..."
49
+ def upload(file_path)
50
+ uri = URI("#{client.configuration.api_url}#{resource_path}/upload")
51
+ http = Net::HTTP.new(uri.host, uri.port)
52
+ http.use_ssl = uri.scheme == "https"
53
+ http.read_timeout = client.configuration.timeout
54
+ http.open_timeout = client.configuration.open_timeout
55
+
56
+ request = Net::HTTP::Post.new(uri)
57
+ request["user-api-key"] = client.api_key
58
+
59
+ file = File.open(file_path, "rb")
60
+ filename = File.basename(file_path)
61
+ boundary = "----RubyMultipartPost#{rand(1000000)}"
62
+
63
+ request["Content-Type"] = "multipart/form-data; boundary=#{boundary}"
64
+ request.body = build_multipart_body(file, filename, boundary)
65
+ file.close
66
+
67
+ response = http.request(request)
68
+ Response.new(response)
69
+ end
70
+
71
+ # Reorder photos
72
+ #
73
+ # @param photos [Array<Hash>] Array of { id: "uuid", position: 0 }
74
+ # @return [Response]
75
+ def reorder(photos)
76
+ client.post("#{resource_path}/reorder", body: {photos: photos})
77
+ end
78
+
79
+ protected
80
+
81
+ def resource_path
82
+ "/photos"
83
+ end
84
+
85
+ def filter_keys
86
+ %i[property_id room_type_id]
87
+ end
88
+
89
+ private
90
+
91
+ def build_multipart_body(file, filename, boundary)
92
+ body = []
93
+ body << "--#{boundary}\r\n"
94
+ body << "Content-Disposition: form-data; name=\"photo\"; filename=\"#{filename}\"\r\n"
95
+ body << "Content-Type: #{mime_type_for(filename)}\r\n\r\n"
96
+ body << file.read
97
+ body << "\r\n--#{boundary}--\r\n"
98
+ body.join
99
+ end
100
+
101
+ def mime_type_for(filename)
102
+ case File.extname(filename).downcase
103
+ when ".jpg", ".jpeg"
104
+ "image/jpeg"
105
+ when ".png"
106
+ "image/png"
107
+ when ".gif"
108
+ "image/gif"
109
+ when ".webp"
110
+ "image/webp"
111
+ else
112
+ "application/octet-stream"
113
+ end
114
+ end
115
+ end
116
+ end
117
+ end
@@ -0,0 +1,42 @@
1
+ module Channex
2
+ module Resources
3
+ # Properties API resource
4
+ #
5
+ # A Property represents a physical premises - hotels, motels, lodges,
6
+ # cabins, chalets, luxury apartments, etc.
7
+ #
8
+ # @example List all properties
9
+ # client.properties.list
10
+ #
11
+ # @example Create a property
12
+ # client.properties.create(
13
+ # title: "My Hotel",
14
+ # currency: "USD",
15
+ # timezone: "America/New_York",
16
+ # property_type: "hotel",
17
+ # settings: { allow_availability_autoupdate: true }
18
+ # )
19
+ #
20
+ # @see https://docs.channex.io/api-v.1-documentation/hotels-collection
21
+ #
22
+ class Properties < Resource
23
+ # Retrieve property health status
24
+ #
25
+ # @param id [String] Property UUID
26
+ # @return [Response]
27
+ def health(id)
28
+ client.get("#{resource_path}/#{id}/health")
29
+ end
30
+
31
+ protected
32
+
33
+ def resource_path
34
+ "/properties"
35
+ end
36
+
37
+ def filter_keys
38
+ %i[group_id]
39
+ end
40
+ end
41
+ end
42
+ end
@@ -0,0 +1,32 @@
1
+ module Channex
2
+ module Resources
3
+ # Property Users API resource
4
+ #
5
+ # Property Users represent the association between users and properties,
6
+ # defining access permissions at the property level.
7
+ #
8
+ # @example List users for a property
9
+ # client.property_users.list(property_id: "property-uuid")
10
+ #
11
+ # @example Add a user to a property
12
+ # client.property_users.create(
13
+ # property_id: "property-uuid",
14
+ # user_id: "user-uuid",
15
+ # role: "manager"
16
+ # )
17
+ #
18
+ # @see https://docs.channex.io/api-v.1-documentation/property-users-collection
19
+ #
20
+ class PropertyUsers < Resource
21
+ protected
22
+
23
+ def resource_path
24
+ "/property_users"
25
+ end
26
+
27
+ def filter_keys
28
+ %i[property_id]
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,34 @@
1
+ module Channex
2
+ module Resources
3
+ # Rate Plans API resource
4
+ #
5
+ # A Rate Plan is a pricing plan for how to sell your Room Types,
6
+ # containing information about prices and restrictions.
7
+ #
8
+ # @example List rate plans for a property
9
+ # client.rate_plans.list(property_id: "property-uuid")
10
+ #
11
+ # @example Create a rate plan
12
+ # client.rate_plans.create(
13
+ # property_id: "property-uuid",
14
+ # room_type_id: "room-type-uuid",
15
+ # title: "Standard Rate",
16
+ # sell_mode: "per_room",
17
+ # rate_mode: "manual"
18
+ # )
19
+ #
20
+ # @see https://docs.channex.io/api-v.1-documentation/rate-plans-collection
21
+ #
22
+ class RatePlans < Resource
23
+ protected
24
+
25
+ def resource_path
26
+ "/rate_plans"
27
+ end
28
+
29
+ def filter_keys
30
+ %i[property_id room_type_id]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,98 @@
1
+ module Channex
2
+ module Resources
3
+ # Restrictions API resource
4
+ #
5
+ # Restrictions represent pricing and stay restrictions applied to rate plans.
6
+ # This includes rates, min/max stay, closed to arrival/departure, and stop sell.
7
+ #
8
+ # @example Get restrictions for a property
9
+ # client.restrictions.list(
10
+ # property_id: "property-uuid",
11
+ # date_from: "2024-01-01",
12
+ # date_to: "2024-01-31"
13
+ # )
14
+ #
15
+ # @example Update restrictions (rates and stay rules)
16
+ # client.restrictions.update([
17
+ # {
18
+ # property_id: "property-uuid",
19
+ # rate_plan_id: "rate-plan-uuid",
20
+ # date_from: "2024-01-15",
21
+ # date_to: "2024-01-20",
22
+ # rate: 15000,
23
+ # min_stay_arrival: 2,
24
+ # closed_to_arrival: false
25
+ # }
26
+ # ])
27
+ #
28
+ # @see https://docs.channex.io/api-v.1-documentation/ari
29
+ #
30
+ class Restrictions < Resource
31
+ # Get restrictions for a property within a date range
32
+ #
33
+ # @param property_id [String] Property UUID (required)
34
+ # @param date [String] Specific date (YYYY-MM-DD)
35
+ # @param date_from [String] Start date (YYYY-MM-DD)
36
+ # @param date_to [String] End date (YYYY-MM-DD)
37
+ # @param options [Hash] Additional options
38
+ # @return [Response]
39
+ def list(property_id:, date: nil, date_from: nil, date_to: nil, **options)
40
+ filter = options.delete(:filter) || {}
41
+ filter[:property_id] = property_id
42
+
43
+ if date
44
+ filter[:date] = date
45
+ else
46
+ filter[:date] = {}
47
+ filter[:date][:gte] = date_from if date_from
48
+ filter[:date][:lte] = date_to if date_to
49
+ end
50
+
51
+ client.get(resource_path, params: {filter: filter, **options})
52
+ end
53
+
54
+ # Update restrictions (rates and stay rules) for rate plans
55
+ #
56
+ # Each value in the array should contain all required fields including property_id.
57
+ # You can update multiple properties and rate plans in a single request.
58
+ #
59
+ # @param values [Array<Hash>] Array of restriction updates
60
+ # Each hash can contain:
61
+ # - property_id [String] Property UUID (required)
62
+ # - rate_plan_id [String] Rate Plan UUID (required)
63
+ # - date_from [String] Start date YYYY-MM-DD (required)
64
+ # - date_to [String] End date YYYY-MM-DD (required)
65
+ # - rate [Integer] Price (in smallest currency unit, e.g., cents)
66
+ # - min_stay_arrival [Integer] Minimum stay if arriving on this date
67
+ # - min_stay_through [Integer] Minimum stay passing through this date
68
+ # - max_stay [Integer] Maximum stay (0 = no limit)
69
+ # - closed_to_arrival [Boolean] Prevent arrivals on this date
70
+ # - closed_to_departure [Boolean] Prevent departures on this date
71
+ # - stop_sell [Boolean] Stop selling this rate plan
72
+ # @return [Response]
73
+ #
74
+ # @example Update rates and restrictions for a date range
75
+ # client.restrictions.update([
76
+ # {
77
+ # property_id: "716305c4-561a-4561-a187-7f5b8aeb5920",
78
+ # rate_plan_id: "bab451e7-9ab1-4cc4-aa16-107bf7bbabb2",
79
+ # date_from: "2024-01-15",
80
+ # date_to: "2024-01-20",
81
+ # rate: 30000,
82
+ # min_stay_arrival: 2,
83
+ # closed_to_arrival: false,
84
+ # stop_sell: false
85
+ # }
86
+ # ])
87
+ def update(values)
88
+ client.post(resource_path, body: {values: values})
89
+ end
90
+
91
+ protected
92
+
93
+ def resource_path
94
+ "/restrictions"
95
+ end
96
+ end
97
+ end
98
+ end
@@ -0,0 +1,41 @@
1
+ module Channex
2
+ module Resources
3
+ # Reviews API resource
4
+ #
5
+ # A review is what a guest wrote about a stay on Booking.com, Airbnb or
6
+ # Expedia, normalised by Channex: an overall score out of 10, per-category
7
+ # scores, the text and the property's reply.
8
+ #
9
+ # Requires the Messages & Reviews application to be installed on the
10
+ # property — without it every call in here answers 403 Forbidden.
11
+ #
12
+ # @example List a property's reviews
13
+ # client.reviews.list(property_id: "property-uuid")
14
+ #
15
+ # @example Reply to a review
16
+ # client.reviews.reply("review-uuid", reply: "Thank you for staying with us.")
17
+ #
18
+ # @see https://docs.channex.io/api-v.1-documentation/reviews-collection
19
+ #
20
+ class Reviews < Resource
21
+ # Reply to a review on the OTA
22
+ #
23
+ # @param id [String] Review UUID
24
+ # @param reply [String] Reply text
25
+ # @return [Response] The review, now replied
26
+ def reply(id, reply:)
27
+ client.post("#{resource_path}/#{id}/reply", body: {reply: {reply:}})
28
+ end
29
+
30
+ protected
31
+
32
+ def resource_path
33
+ "/reviews"
34
+ end
35
+
36
+ def filter_keys
37
+ %i[property_id booking_id]
38
+ end
39
+ end
40
+ end
41
+ end
@@ -0,0 +1,34 @@
1
+ module Channex
2
+ module Resources
3
+ # Room Types API resource
4
+ #
5
+ # Room Types represent the different types of rooms available at a property.
6
+ #
7
+ # @example List room types for a property
8
+ # client.room_types.list(property_id: "property-uuid")
9
+ #
10
+ # @example Create a room type
11
+ # client.room_types.create(
12
+ # property_id: "property-uuid",
13
+ # title: "Deluxe King Room",
14
+ # count_of_rooms: 10,
15
+ # occ_adults: 2,
16
+ # occ_children: 1,
17
+ # occ_infants: 1
18
+ # )
19
+ #
20
+ # @see https://docs.channex.io/api-v.1-documentation/room-types-collection
21
+ #
22
+ class RoomTypes < Resource
23
+ protected
24
+
25
+ def resource_path
26
+ "/room_types"
27
+ end
28
+
29
+ def filter_keys
30
+ %i[property_id]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,34 @@
1
+ module Channex
2
+ module Resources
3
+ # Tax Sets API resource
4
+ #
5
+ # A Tax Set represents a group of Taxes applicable to your Rate Plan or Property.
6
+ # Each property can have many Tax Sets, but only one can be selected as
7
+ # the Default Tax Set, which is applied to each new Rate Plan automatically.
8
+ #
9
+ # @example List tax sets for a property
10
+ # client.tax_sets.list(property_id: "property-uuid")
11
+ #
12
+ # @example Create a tax set
13
+ # client.tax_sets.create(
14
+ # property_id: "property-uuid",
15
+ # title: "Standard Taxes",
16
+ # taxes: ["tax-uuid-1", "tax-uuid-2"],
17
+ # is_default: true
18
+ # )
19
+ #
20
+ # @see https://docs.channex.io/api-v.1-documentation/taxes-and-tax-sets
21
+ #
22
+ class TaxSets < Resource
23
+ protected
24
+
25
+ def resource_path
26
+ "/tax_sets"
27
+ end
28
+
29
+ def filter_keys
30
+ %i[property_id]
31
+ end
32
+ end
33
+ end
34
+ end
@@ -0,0 +1,60 @@
1
+ module Channex
2
+ module Resources
3
+ # Taxes API resource
4
+ #
5
+ # A Tax represents a specific tax applicable to your Rate Plan or Property,
6
+ # such as VAT 20%, City Tax 1 EUR per Guest per Night, etc.
7
+ #
8
+ # @example List taxes for a property
9
+ # client.taxes.list(property_id: "property-uuid")
10
+ #
11
+ # @example Create a percentage-based tax (VAT)
12
+ # client.taxes.create(
13
+ # property_id: "property-uuid",
14
+ # title: "VAT",
15
+ # logic: "percent",
16
+ # type: "tax",
17
+ # rate: "20.00",
18
+ # is_inclusive: true
19
+ # )
20
+ #
21
+ # @example Create a fixed amount tax (City Tax per person per night)
22
+ # client.taxes.create(
23
+ # property_id: "property-uuid",
24
+ # title: "City Tax",
25
+ # logic: "per_person_per_night",
26
+ # type: "city tax",
27
+ # rate: "2.00",
28
+ # currency: "EUR",
29
+ # is_inclusive: false
30
+ # )
31
+ #
32
+ # @see https://docs.channex.io/api-v.1-documentation/taxes-and-tax-sets
33
+ #
34
+ class Taxes < Resource
35
+ # Logic types for tax calculation
36
+ LOGIC_TYPES = %w[
37
+ percent
38
+ per_room
39
+ per_room_per_night
40
+ per_person
41
+ per_person_per_night
42
+ per_night
43
+ per_booking
44
+ ].freeze
45
+
46
+ # Tax types
47
+ TAX_TYPES = ["tax", "fee", "city tax"].freeze
48
+
49
+ protected
50
+
51
+ def resource_path
52
+ "/taxes"
53
+ end
54
+
55
+ def filter_keys
56
+ %i[property_id]
57
+ end
58
+ end
59
+ end
60
+ end
@@ -0,0 +1,87 @@
1
+ module Channex
2
+ module Resources
3
+ # Webhooks API resource
4
+ #
5
+ # Webhooks allow you to receive push notifications about changes
6
+ # in your property's data, including ARI changes and booking events.
7
+ #
8
+ # Available webhook events:
9
+ # - *: All events
10
+ # - ari: ARI changes (availability, rates, restrictions)
11
+ # - booking: All booking changes (new, modified, cancelled)
12
+ # - booking_new: Only new bookings
13
+ # - booking_modification: Only modified bookings
14
+ # - booking_cancellation: Only cancelled bookings
15
+ # - reservation_request: Airbnb reservation requests
16
+ # - new_channel: Channel connected
17
+ # - updated_channel: Channel updated
18
+ # - disconnected_channel: Channel disconnected
19
+ # - message: New message received
20
+ #
21
+ # ARI events support filtering by restriction type, room type, and rate plan:
22
+ # Format: "ari:restrictions:room_type_ids:rate_plan_ids"
23
+ # Example: "ari:rate:*:96a44e07-2158-43e4-8baa-8f6f56922ba8"
24
+ #
25
+ # @example Create a webhook for all events
26
+ # client.webhooks.create(
27
+ # property_id: "property-uuid",
28
+ # callback_url: "https://your-domain.com/webhooks/channex",
29
+ # event_mask: "*",
30
+ # is_active: true,
31
+ # send_data: true
32
+ # )
33
+ #
34
+ # @example Create a webhook for specific rate plan changes
35
+ # client.webhooks.create(
36
+ # property_id: "property-uuid",
37
+ # callback_url: "https://your-domain.com/webhooks/ari",
38
+ # event_mask: "ari:rate:*:96a44e07-2158-43e4-8baa-8f6f56922ba8",
39
+ # is_active: true,
40
+ # send_data: true
41
+ # )
42
+ #
43
+ # @see https://docs.channex.io/api-v.1-documentation/webhook-collection
44
+ #
45
+ class Webhooks < Resource
46
+ # Standard event types
47
+ EVENTS = %w[
48
+ ari
49
+ booking
50
+ booking_new
51
+ booking_modification
52
+ booking_cancellation
53
+ reservation_request
54
+ new_channel
55
+ updated_channel
56
+ disconnected_channel
57
+ message
58
+ ].freeze
59
+
60
+ # Test a webhook endpoint without saving
61
+ #
62
+ # @param attributes [Hash] Webhook attributes to test
63
+ # @return [Response]
64
+ def test_new(attributes)
65
+ client.post("#{resource_path}/test", body: {webhook: attributes})
66
+ end
67
+
68
+ # Test an existing webhook endpoint
69
+ #
70
+ # @param id [String] Webhook UUID
71
+ # @return [Response]
72
+ def test(id)
73
+ client.post("#{resource_path}/#{id}/test", body: {})
74
+ end
75
+
76
+ protected
77
+
78
+ def resource_path
79
+ "/webhooks"
80
+ end
81
+
82
+ def filter_keys
83
+ %i[property_id]
84
+ end
85
+ end
86
+ end
87
+ end
@@ -0,0 +1,5 @@
1
+ module Channex
2
+ # Namespace for all Channex API resource classes
3
+ module Resources
4
+ end
5
+ end
@@ -0,0 +1,102 @@
1
+ module Channex
2
+ # Wraps API responses with convenient accessors
3
+ #
4
+ # Channex API responses follow this structure:
5
+ # - Success: { "data": {...} } or { "data": [...], "meta": {...} }
6
+ # - Error: { "error": {...} } or { "errors": {...} }
7
+ #
8
+ class Response
9
+ attr_reader :status, :headers, :raw_body, :body
10
+
11
+ def initialize(http_response)
12
+ @status = http_response.code.to_i
13
+ @headers = http_response.to_hash
14
+ @raw_body = http_response.body
15
+ @body = parse_body(http_response.body)
16
+ end
17
+
18
+ def success?
19
+ status >= 200 && status < 300
20
+ end
21
+
22
+ def data
23
+ body["data"]
24
+ end
25
+
26
+ def meta
27
+ body["meta"] || {}
28
+ end
29
+
30
+ # Returns error information from the response
31
+ #
32
+ # Channex API can return errors in two formats:
33
+ # - { "error": { "message": "...", "code": "..." } }
34
+ # - { "errors": { "code": "...", "title": "..." } }
35
+ #
36
+ # @return [Hash, nil] Error information or nil if no error
37
+ def error
38
+ body["error"] || body["errors"]
39
+ end
40
+
41
+ # Returns warnings from the response meta
42
+ #
43
+ # ARI updates can return partial success with warnings for invalid values.
44
+ #
45
+ # @return [Array] Array of warning objects
46
+ def warnings
47
+ meta["warnings"] || []
48
+ end
49
+
50
+ def pagination
51
+ @pagination ||= Pagination.new(meta)
52
+ end
53
+
54
+ private
55
+
56
+ def parse_body(body)
57
+ return {} if body.nil? || body.empty?
58
+
59
+ JSON.parse(body)
60
+ rescue JSON::ParserError
61
+ {"raw" => body}
62
+ end
63
+ end
64
+
65
+ # Pagination information from API responses
66
+ class Pagination
67
+ attr_reader :page, :limit, :total, :order
68
+
69
+ def initialize(meta)
70
+ @page = meta["page"]
71
+ @limit = meta["limit"]
72
+ @total = meta["total"]
73
+ @order = meta["order"]
74
+ end
75
+
76
+ def total_pages
77
+ return nil unless total && limit && limit > 0
78
+
79
+ (total.to_f / limit).ceil
80
+ end
81
+
82
+ def next_page
83
+ return nil unless page && total_pages
84
+
85
+ (page < total_pages) ? page + 1 : nil
86
+ end
87
+
88
+ def prev_page
89
+ return nil unless page
90
+
91
+ (page > 1) ? page - 1 : nil
92
+ end
93
+
94
+ def has_next?
95
+ !next_page.nil?
96
+ end
97
+
98
+ def has_prev?
99
+ !prev_page.nil?
100
+ end
101
+ end
102
+ end
@@ -0,0 +1,3 @@
1
+ module Channex
2
+ VERSION = "0.1.0"
3
+ end