doohly 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.
- checksums.yaml +7 -0
- data/.rspec +3 -0
- data/.rubocop.yml +88 -0
- data/CHANGELOG.md +25 -0
- data/LICENSE.txt +21 -0
- data/README.md +214 -0
- data/Rakefile +12 -0
- data/docs/example/GET_booking_by_id.json +1347 -0
- data/docs/example/GET_bookings.json +20366 -0
- data/docs/example/GET_creative_by_id.json +9 -0
- data/docs/example/GET_device_by_id.json +1268 -0
- data/docs/example/GET_devices.json +2576 -0
- data/docs/example/PUT_creatives.json +4 -0
- data/doohly.gemspec +48 -0
- data/examples/usage.rb +69 -0
- data/lib/doohly/client.rb +252 -0
- data/lib/doohly/configuration.rb +43 -0
- data/lib/doohly/error.rb +36 -0
- data/lib/doohly/version.rb +5 -0
- data/lib/doohly.rb +18 -0
- data/sig/doohly.rbs +4 -0
- data/test_api.rb +26 -0
- metadata +197 -0
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
{
|
|
2
|
+
"id": "b0324f10-14d8-41ca-8ae5-d03e6d6c37bf",
|
|
3
|
+
"uploadUrl": "https://doohly-production-file-uploads.s3.ap-southeast-2.amazonaws.com/creative/winboard-adverto/add04745-d007-4dfb-a856-0e53eca0a28b/b0324f10-14d8-41ca-8ae5-d03e6d6c37bf.png?X-Amz-Algorithm=AWS4-HMAC-SHA256&X-Amz-Content-Sha256=UNSIGNED-PAYLOAD&X-Amz-Credential=ASIAV6CZOKOV5MUX2UG4%2F20250820%2Fap-southeast-2%2Fs3%2Faws4_request&X-Amz-Date=20250820T044932Z&X-Amz-Expires=3600&X-Amz-Security-Token=IQoJb3JpZ2luX2VjEIX%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FwEaDmFwLXNvdXRoZWFzdC0yIkgwRgIhAItpbnNxukjLdf9gSFrQvfa3ZAzzWNO8I7APJlR2K0t4AiEAjVMz36tsWPr6ptaW3sJuqIQhncJXMUinSWQ89fFz%2FFQq0gUIzv%2F%2F%2F%2F%2F%2F%2F%2F%2F%2FARACGgw0MDgyMDk0Nzg1NzEiDNy0%2BI2Vi9xkv44dWCqmBTH%2BJ6av3wsnOPe9xPNLyPMqaSWAdIl4q3Mz4t%2BE9brbakVTGo6pHPyuNAgdybx%2FEzqVnJ0DDwiXOlaiXpe4GhFRXtknBwz4GK6paDl2IqZEYWFDkjZuhzGpnu2z2sAeaZu4%2F6NjoIVFypWtUbtecAHzGUpXZWbLBAPLTGV4MpkR5owho8zsbBcQBXUSok4n5WOSTI39%2FvyKAinzsay%2BbQeBgBZBPHaDUOQ%2FopOwfm6qe50DSFXgxeV%2F2qPwWjeUXs1TXxHradB6L9wk3ut%2FKJocnVC%2F7WSHTedLrbynn4q%2F48EW2EzHVdXUruU0WsoesrGSa%2FWcgwLCtqg1hhOrb4V2ByDagRQBmaQWwaGD%2FTsQ%2FnshMgwqmbm7X4ZO%2FY22yiaCFlMzrqwl1xKpoBA5YYRu1CN68DcDNPrD8mPxgn6GBIqgm6lnkwId5lsP7d086POzTi2zv4UIBhjOMazhSfD1SyxcT22m5ZtHywYtL27kGn3%2FhxnTkvQnpOay64LmhQXXkIifFvzC6OY2iMFn3u9cWtk2oaBU3sU3DPmFTp0bDbhUb5AWztHbtuaB2jQlJRNE2EaLopH%2FRrhqQlazLQ132yPXzhXijTd7y1pIegZ9hqHq%2FJFHceP8osWkAMOxssM%2BzFVLy7WdeAFYwi2X30hJXn%2Fh1o4jM7G6QRWTclGBa%2BVUkynL4oATjXAmht%2B4ukr2ZsQQG9vdpphSF53ZzW1kejeOt7JUTB8eNSWYvpG4PRdLioZREaldo8DtUx85fMiKYDo0lKVhYBVsOabjkLr0KtML9X0h2u7uwTv0P8OtIT8AgyM5May4F01UbnGMpqj8B8tyCpoBZ%2BHIhGWnAlfR9oEWcVbtG8n8%2FZdv5y5gxWLOmKpw4UbGEH%2B0QWHbI4YDn5Tr3DDJppXFBjqwAR2K8QDwQuGg10sfj6%2FUE0eDnA3lrWNYFn6%2BW%2FDmTc6N3bUJAJJQ%2FH3OWPMgEN6mkBN8qgJ7OzQ6%2B5ueo%2FFTm%2FbauiO1EEEjBLD8P6KuLNI4SNL0V1QhlbCoBq8yvDrbtGXA9VyE%2FGi2sV197my6UM0gYXdWVnwvuyFTHyXOo%2Fzjr7F5WJJE6Ou7RzbIIOKtdfMmZmLXS6Fw8xcd9tdvPsjqlcIu2KGCbd5vre1ZJ3C9&X-Amz-Signature=a92086f6b872d1ec77fa2d8a0e12750d3749fca1d06dedfdfa3df39f22fe98a7&X-Amz-SignedHeaders=content-length%3Bhost&x-id=PutObject"
|
|
4
|
+
}
|
data/doohly.gemspec
ADDED
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "lib/doohly/version"
|
|
4
|
+
|
|
5
|
+
Gem::Specification.new do |spec|
|
|
6
|
+
spec.name = "doohly"
|
|
7
|
+
spec.version = Doohly::VERSION
|
|
8
|
+
spec.authors = ["Sentia"]
|
|
9
|
+
spec.email = ["developer@sentia.com.au"]
|
|
10
|
+
|
|
11
|
+
spec.summary = "Ruby client for the Doohly DOOH advertising platform API"
|
|
12
|
+
spec.description = "A Ruby client library for interacting with the Doohly (Digital Out-of-Home) " \
|
|
13
|
+
"advertising platform API. Supports managing bookings, devices, creatives, and more."
|
|
14
|
+
spec.homepage = "https://github.com/Sentia/doohly"
|
|
15
|
+
spec.license = "MIT"
|
|
16
|
+
spec.required_ruby_version = ">= 3.0.0"
|
|
17
|
+
|
|
18
|
+
spec.metadata["homepage_uri"] = spec.homepage
|
|
19
|
+
spec.metadata["source_code_uri"] = "https://github.com/Sentia/doohly"
|
|
20
|
+
spec.metadata["changelog_uri"] = "https://github.com/Sentia/doohly/blob/main/CHANGELOG.md"
|
|
21
|
+
spec.metadata["bug_tracker_uri"] = "https://github.com/Sentia/doohly/issues"
|
|
22
|
+
spec.metadata["documentation_uri"] = "https://rubydoc.info/gems/doohly"
|
|
23
|
+
spec.metadata["rubygems_mfa_required"] = "true"
|
|
24
|
+
|
|
25
|
+
# Specify which files should be added to the gem when it is released.
|
|
26
|
+
spec.files = Dir.chdir(__dir__) do
|
|
27
|
+
`git ls-files -z`.split("\x0").reject do |f|
|
|
28
|
+
(File.expand_path(f) == __FILE__) ||
|
|
29
|
+
f.start_with?(*%w[bin/ test/ spec/ features/ .git .github appveyor Gemfile])
|
|
30
|
+
end
|
|
31
|
+
end
|
|
32
|
+
spec.bindir = "exe"
|
|
33
|
+
spec.executables = spec.files.grep(%r{\Aexe/}) { |f| File.basename(f) }
|
|
34
|
+
spec.require_paths = ["lib"]
|
|
35
|
+
|
|
36
|
+
# Runtime dependencies
|
|
37
|
+
spec.add_dependency "faraday", "~> 2.0"
|
|
38
|
+
spec.add_dependency "faraday-multipart", "~> 1.0"
|
|
39
|
+
|
|
40
|
+
# Development dependencies
|
|
41
|
+
spec.add_development_dependency "rake", "~> 13.0"
|
|
42
|
+
spec.add_development_dependency "rspec", "~> 3.12"
|
|
43
|
+
spec.add_development_dependency "rubocop", "~> 1.50"
|
|
44
|
+
spec.add_development_dependency "rubocop-rspec", "~> 2.20"
|
|
45
|
+
spec.add_development_dependency "simplecov", "~> 0.22"
|
|
46
|
+
spec.add_development_dependency "vcr", "~> 6.1"
|
|
47
|
+
spec.add_development_dependency "webmock", "~> 3.18"
|
|
48
|
+
end
|
data/examples/usage.rb
ADDED
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "doohly"
|
|
6
|
+
|
|
7
|
+
# Configure with your API token
|
|
8
|
+
api_token = ENV.fetch("DOOHLY_API_TOKEN", "your_api_token_here")
|
|
9
|
+
|
|
10
|
+
# Option 1: Global configuration
|
|
11
|
+
Doohly.configure do |config|
|
|
12
|
+
config.api_token = api_token
|
|
13
|
+
config.timeout = 30
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
client = Doohly::Client.new
|
|
17
|
+
|
|
18
|
+
# Option 2: Direct client initialization
|
|
19
|
+
# client = Doohly::Client.new(api_token: api_token)
|
|
20
|
+
|
|
21
|
+
puts "=" * 80
|
|
22
|
+
puts "Doohly Ruby Client - Usage Examples"
|
|
23
|
+
puts "=" * 80
|
|
24
|
+
|
|
25
|
+
# List devices
|
|
26
|
+
puts "\nš± Fetching devices..."
|
|
27
|
+
begin
|
|
28
|
+
devices = client.devices
|
|
29
|
+
device_count = devices.is_a?(Array) ? devices.count : 0
|
|
30
|
+
puts "Found #{device_count} devices"
|
|
31
|
+
if devices.is_a?(Array) && devices.any?
|
|
32
|
+
first_device = devices.first
|
|
33
|
+
puts " First device: #{first_device["name"]} (#{first_device["id"]})"
|
|
34
|
+
end
|
|
35
|
+
rescue Doohly::APIError => e
|
|
36
|
+
puts "Error fetching devices: #{e.message}"
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# List bookings
|
|
40
|
+
puts "\nš
Fetching bookings..."
|
|
41
|
+
begin
|
|
42
|
+
bookings = client.bookings
|
|
43
|
+
booking_list = bookings.is_a?(Hash) ? bookings["bookings"] : bookings
|
|
44
|
+
booking_count = booking_list.is_a?(Array) ? booking_list.count : 0
|
|
45
|
+
puts "Found #{booking_count} bookings"
|
|
46
|
+
if booking_list.is_a?(Array) && booking_list.any?
|
|
47
|
+
first_booking = booking_list.first
|
|
48
|
+
puts " First booking: #{first_booking["name"]} (#{first_booking["status"]})"
|
|
49
|
+
end
|
|
50
|
+
rescue Doohly::APIError => e
|
|
51
|
+
puts "Error fetching bookings: #{e.message}"
|
|
52
|
+
end
|
|
53
|
+
|
|
54
|
+
# Get signed upload URL
|
|
55
|
+
puts "\nšØ Getting signed upload URL for creative..."
|
|
56
|
+
begin
|
|
57
|
+
upload_info = client.get_signed_upload_url(
|
|
58
|
+
name: "example-creative.png",
|
|
59
|
+
mime_type: "image/png",
|
|
60
|
+
file_size: 100_000,
|
|
61
|
+
playback_scaling: "contain"
|
|
62
|
+
)
|
|
63
|
+
puts "Upload ID: #{upload_info["id"]}"
|
|
64
|
+
puts "Upload URL: #{upload_info["uploadUrl"][0..50]}..."
|
|
65
|
+
rescue Doohly::APIError => e
|
|
66
|
+
puts "Error getting upload URL: #{e.message}"
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
puts "\nā
Example completed!"
|
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "faraday"
|
|
4
|
+
require "faraday/multipart"
|
|
5
|
+
require "json"
|
|
6
|
+
|
|
7
|
+
module Doohly
|
|
8
|
+
# Main API client for Doohly
|
|
9
|
+
class Client
|
|
10
|
+
attr_reader :api_token, :api_base_url
|
|
11
|
+
|
|
12
|
+
def initialize(api_token: nil, api_base_url: nil)
|
|
13
|
+
@api_token = api_token || Doohly.configuration.api_token
|
|
14
|
+
@api_base_url = api_base_url || Doohly.configuration.api_base_url
|
|
15
|
+
|
|
16
|
+
raise ConfigurationError, "API token is required" if @api_token.nil? || @api_token.empty?
|
|
17
|
+
|
|
18
|
+
@connection = build_connection
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
# Bookings API
|
|
22
|
+
|
|
23
|
+
# GET /v2/bookings
|
|
24
|
+
# @param status [String, nil] Filter by status (e.g., 'booked', 'paused', 'completed')
|
|
25
|
+
# @return [Hash] List of bookings
|
|
26
|
+
def bookings(status: nil)
|
|
27
|
+
params = {}
|
|
28
|
+
params[:status] = status if status
|
|
29
|
+
get("v2/bookings", params)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# GET /v2/bookings/:id
|
|
33
|
+
# @param id [String] Booking ID
|
|
34
|
+
# @return [Hash] Booking details
|
|
35
|
+
def booking(id)
|
|
36
|
+
get("v2/bookings/#{id}")
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# POST /v2/bookings - Create a new booking
|
|
40
|
+
# @param name [String] Booking name
|
|
41
|
+
# @param external_id [String, nil] External reference ID
|
|
42
|
+
# @param plays_per_loop [Integer, nil] Number of plays per loop
|
|
43
|
+
# @param loops_per_play [Integer, nil] Number of loops per play
|
|
44
|
+
# @param play_consecutively [Boolean, nil] Whether to play consecutively
|
|
45
|
+
# @param purchase_type [String, nil] Purchase type (e.g., 'Sold', 'Bonus')
|
|
46
|
+
# @param campaign [Hash, nil] Campaign details
|
|
47
|
+
# @param schedule [Hash, nil] Schedule configuration
|
|
48
|
+
# @param assigned_creatives [Array<Hash>, nil] Array of creative assignments
|
|
49
|
+
# @param assigned_frames [Array<Hash>, nil] Array of frame assignments
|
|
50
|
+
# @param tags [Array<String>, nil] Tags for the booking
|
|
51
|
+
# @param seedooh [Hash, nil] SeeDooh configuration
|
|
52
|
+
# @param status [String, nil] Booking status
|
|
53
|
+
# @return [Hash] Created booking details
|
|
54
|
+
def create_booking(name:, external_id: nil, plays_per_loop: nil, loops_per_play: nil,
|
|
55
|
+
play_consecutively: nil, purchase_type: nil, campaign: nil,
|
|
56
|
+
schedule: nil, assigned_creatives: nil, assigned_frames: nil,
|
|
57
|
+
tags: nil, seedooh: nil, status: nil)
|
|
58
|
+
body = { name: name }
|
|
59
|
+
body[:externalId] = external_id if external_id
|
|
60
|
+
body[:playsPerLoop] = plays_per_loop if plays_per_loop
|
|
61
|
+
body[:loopsPerPlay] = loops_per_play if loops_per_play
|
|
62
|
+
body[:playConsecutively] = play_consecutively unless play_consecutively.nil?
|
|
63
|
+
body[:purchaseType] = purchase_type if purchase_type
|
|
64
|
+
body[:campaign] = campaign if campaign
|
|
65
|
+
body[:schedule] = schedule if schedule
|
|
66
|
+
body[:assignedCreatives] = assigned_creatives if assigned_creatives
|
|
67
|
+
body[:assignedFrames] = assigned_frames if assigned_frames
|
|
68
|
+
body[:tags] = tags if tags
|
|
69
|
+
body[:seedooh] = seedooh if seedooh
|
|
70
|
+
body[:status] = status if status
|
|
71
|
+
|
|
72
|
+
post("v2/bookings", body)
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
# PATCH /v2/bookings/:id - Update an existing booking
|
|
76
|
+
# @param id [String] Booking ID
|
|
77
|
+
# @param name [String, nil] Booking name
|
|
78
|
+
# @param external_id [String, nil] External reference ID
|
|
79
|
+
# @param plays_per_loop [Integer, nil] Number of plays per loop
|
|
80
|
+
# @param loops_per_play [Integer, nil] Number of loops per play
|
|
81
|
+
# @param play_consecutively [Boolean, nil] Whether to play consecutively
|
|
82
|
+
# @param purchase_type [String, nil] Purchase type
|
|
83
|
+
# @param campaign [Hash, nil] Campaign details
|
|
84
|
+
# @param schedule [Hash, nil] Schedule configuration
|
|
85
|
+
# @param assigned_creatives [Array<Hash>, nil] Array of creative assignments
|
|
86
|
+
# @param assigned_frames [Array<Hash>, nil] Array of frame assignments
|
|
87
|
+
# @param tags [Array<String>, nil] Tags for the booking
|
|
88
|
+
# @param seedooh [Hash, nil] SeeDooh configuration
|
|
89
|
+
# @param status [String, nil] Booking status
|
|
90
|
+
# @return [Hash] Updated booking details
|
|
91
|
+
def update_booking(id, name: nil, external_id: nil, plays_per_loop: nil, loops_per_play: nil,
|
|
92
|
+
play_consecutively: nil, purchase_type: nil, campaign: nil,
|
|
93
|
+
schedule: nil, assigned_creatives: nil, assigned_frames: nil,
|
|
94
|
+
tags: nil, seedooh: nil, status: nil)
|
|
95
|
+
body = {}
|
|
96
|
+
body[:name] = name if name
|
|
97
|
+
body[:externalId] = external_id unless external_id.nil?
|
|
98
|
+
body[:playsPerLoop] = plays_per_loop if plays_per_loop
|
|
99
|
+
body[:loopsPerPlay] = loops_per_play if loops_per_play
|
|
100
|
+
body[:playConsecutively] = play_consecutively unless play_consecutively.nil?
|
|
101
|
+
body[:purchaseType] = purchase_type if purchase_type
|
|
102
|
+
body[:campaign] = campaign unless campaign.nil?
|
|
103
|
+
body[:schedule] = schedule if schedule
|
|
104
|
+
body[:assignedCreatives] = assigned_creatives if assigned_creatives
|
|
105
|
+
body[:assignedFrames] = assigned_frames if assigned_frames
|
|
106
|
+
body[:tags] = tags if tags
|
|
107
|
+
body[:seedooh] = seedooh if seedooh
|
|
108
|
+
body[:status] = status if status
|
|
109
|
+
|
|
110
|
+
patch("v2/bookings/#{id}", body)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# DELETE /v2/bookings/:id - Delete an existing booking
|
|
114
|
+
# @param id [String] Booking ID
|
|
115
|
+
# @return [Hash] Deletion result
|
|
116
|
+
def delete_booking(id)
|
|
117
|
+
delete("v2/bookings/#{id}")
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
# Devices API
|
|
121
|
+
|
|
122
|
+
# GET /v1/devices
|
|
123
|
+
# @return [Hash] List of devices
|
|
124
|
+
def devices
|
|
125
|
+
get("v1/devices")
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# GET /v2/devices/:id
|
|
129
|
+
# @param id [String] Device ID
|
|
130
|
+
# @return [Hash] Device details
|
|
131
|
+
def device(id)
|
|
132
|
+
get("v2/devices/#{id}")
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# Creatives API
|
|
136
|
+
|
|
137
|
+
# GET /v1/library/creatives/upload/:id - Get creative upload status
|
|
138
|
+
# @param id [String] Upload ID
|
|
139
|
+
# @return [Hash] Upload status
|
|
140
|
+
def creative_upload_status(id)
|
|
141
|
+
get("v1/library/creatives/upload/#{id}")
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
# POST /v1/library/creatives/upload - Get signed upload URL
|
|
145
|
+
# @param name [String] Creative name
|
|
146
|
+
# @param mime_type [String] MIME type (e.g., 'image/png', 'video/mp4')
|
|
147
|
+
# @param file_size [Integer] File size in bytes
|
|
148
|
+
# @param playback_scaling [String, nil] Playback scaling mode (e.g., 'contain', 'cover')
|
|
149
|
+
# @param path [Array<String>, nil] Folder path for organization
|
|
150
|
+
# @return [Hash] Upload information including signed URL
|
|
151
|
+
def get_signed_upload_url(name:, mime_type:, file_size:, playback_scaling: nil, path: nil)
|
|
152
|
+
body = {
|
|
153
|
+
name: name,
|
|
154
|
+
mimeType: mime_type,
|
|
155
|
+
fileSize: file_size
|
|
156
|
+
}
|
|
157
|
+
body[:playbackScaling] = playback_scaling if playback_scaling
|
|
158
|
+
body[:path] = path if path
|
|
159
|
+
|
|
160
|
+
post("v1/library/creatives/upload", body)
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
private
|
|
164
|
+
|
|
165
|
+
def build_connection
|
|
166
|
+
Faraday.new(url: @api_base_url) do |conn|
|
|
167
|
+
conn.request :authorization, "Bearer", @api_token
|
|
168
|
+
conn.request :json
|
|
169
|
+
conn.response :json, content_type: /json$/
|
|
170
|
+
conn.response :logger, Doohly.configuration.logger if Doohly.configuration.logger
|
|
171
|
+
conn.options.timeout = Doohly.configuration.timeout
|
|
172
|
+
conn.options.open_timeout = Doohly.configuration.open_timeout
|
|
173
|
+
conn.adapter Faraday.default_adapter
|
|
174
|
+
end
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
def get(path, params = {})
|
|
178
|
+
response = @connection.get(path, params)
|
|
179
|
+
handle_response(response)
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def post(path, body)
|
|
183
|
+
response = @connection.post(path) do |req|
|
|
184
|
+
req.headers["Content-Type"] = "application/json"
|
|
185
|
+
req.body = body.to_json
|
|
186
|
+
end
|
|
187
|
+
handle_response(response)
|
|
188
|
+
end
|
|
189
|
+
|
|
190
|
+
def patch(path, body)
|
|
191
|
+
response = @connection.patch(path) do |req|
|
|
192
|
+
req.headers["Content-Type"] = "application/json"
|
|
193
|
+
req.body = body.to_json
|
|
194
|
+
end
|
|
195
|
+
handle_response(response)
|
|
196
|
+
end
|
|
197
|
+
|
|
198
|
+
def delete(path)
|
|
199
|
+
response = @connection.delete(path)
|
|
200
|
+
handle_response(response)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def handle_response(response)
|
|
204
|
+
case response.status
|
|
205
|
+
when 200..299
|
|
206
|
+
response.body
|
|
207
|
+
when 400
|
|
208
|
+
raise BadRequestError.new(
|
|
209
|
+
"Bad Request: #{response.body}",
|
|
210
|
+
status: response.status,
|
|
211
|
+
body: response.body,
|
|
212
|
+
response: response
|
|
213
|
+
)
|
|
214
|
+
when 401
|
|
215
|
+
raise AuthenticationError.new(
|
|
216
|
+
"Authentication failed: #{response.body}",
|
|
217
|
+
status: response.status,
|
|
218
|
+
body: response.body,
|
|
219
|
+
response: response
|
|
220
|
+
)
|
|
221
|
+
when 404
|
|
222
|
+
raise NotFoundError.new(
|
|
223
|
+
"Resource not found: #{response.body}",
|
|
224
|
+
status: response.status,
|
|
225
|
+
body: response.body,
|
|
226
|
+
response: response
|
|
227
|
+
)
|
|
228
|
+
when 429
|
|
229
|
+
raise RateLimitError.new(
|
|
230
|
+
"Rate limit exceeded: #{response.body}",
|
|
231
|
+
status: response.status,
|
|
232
|
+
body: response.body,
|
|
233
|
+
response: response
|
|
234
|
+
)
|
|
235
|
+
when 500..599
|
|
236
|
+
raise ServerError.new(
|
|
237
|
+
"Server error: #{response.body}",
|
|
238
|
+
status: response.status,
|
|
239
|
+
body: response.body,
|
|
240
|
+
response: response
|
|
241
|
+
)
|
|
242
|
+
else
|
|
243
|
+
raise APIError.new(
|
|
244
|
+
"API Error: #{response.status} - #{response.body}",
|
|
245
|
+
status: response.status,
|
|
246
|
+
body: response.body,
|
|
247
|
+
response: response
|
|
248
|
+
)
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
252
|
+
end
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doohly
|
|
4
|
+
# Configuration management for Doohly client
|
|
5
|
+
class Configuration
|
|
6
|
+
attr_accessor :api_token, :api_base_url, :timeout, :open_timeout, :logger
|
|
7
|
+
|
|
8
|
+
DEFAULT_API_BASE_URL = "https://api.dooh.ly/api/public"
|
|
9
|
+
DEFAULT_TIMEOUT = 30
|
|
10
|
+
DEFAULT_OPEN_TIMEOUT = 10
|
|
11
|
+
|
|
12
|
+
def initialize
|
|
13
|
+
@api_token = nil
|
|
14
|
+
@api_base_url = DEFAULT_API_BASE_URL
|
|
15
|
+
@timeout = DEFAULT_TIMEOUT
|
|
16
|
+
@open_timeout = DEFAULT_OPEN_TIMEOUT
|
|
17
|
+
@logger = nil
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def validate!
|
|
21
|
+
raise ConfigurationError, "API token is required" if api_token.nil? || api_token.empty?
|
|
22
|
+
|
|
23
|
+
true
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
class << self
|
|
28
|
+
attr_writer :configuration
|
|
29
|
+
|
|
30
|
+
def configuration
|
|
31
|
+
@configuration ||= Configuration.new
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def configure
|
|
35
|
+
yield(configuration)
|
|
36
|
+
configuration.validate!
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
def reset_configuration!
|
|
40
|
+
@configuration = Configuration.new
|
|
41
|
+
end
|
|
42
|
+
end
|
|
43
|
+
end
|
data/lib/doohly/error.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Doohly
|
|
4
|
+
# Base error class for all Doohly errors
|
|
5
|
+
class Error < StandardError; end
|
|
6
|
+
|
|
7
|
+
# Raised when API request fails
|
|
8
|
+
class APIError < Error
|
|
9
|
+
attr_reader :status, :body, :response
|
|
10
|
+
|
|
11
|
+
def initialize(message, status: nil, body: nil, response: nil)
|
|
12
|
+
@status = status
|
|
13
|
+
@body = body
|
|
14
|
+
@response = response
|
|
15
|
+
super(message)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
# Raised when configuration is invalid
|
|
20
|
+
class ConfigurationError < Error; end
|
|
21
|
+
|
|
22
|
+
# Raised when authentication fails
|
|
23
|
+
class AuthenticationError < APIError; end
|
|
24
|
+
|
|
25
|
+
# Raised when resource is not found
|
|
26
|
+
class NotFoundError < APIError; end
|
|
27
|
+
|
|
28
|
+
# Raised when request is invalid
|
|
29
|
+
class BadRequestError < APIError; end
|
|
30
|
+
|
|
31
|
+
# Raised when rate limit is exceeded
|
|
32
|
+
class RateLimitError < APIError; end
|
|
33
|
+
|
|
34
|
+
# Raised when server error occurs
|
|
35
|
+
class ServerError < APIError; end
|
|
36
|
+
end
|
data/lib/doohly.rb
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "doohly/version"
|
|
4
|
+
require_relative "doohly/error"
|
|
5
|
+
require_relative "doohly/configuration"
|
|
6
|
+
require_relative "doohly/client"
|
|
7
|
+
|
|
8
|
+
# Main module for Doohly Ruby client
|
|
9
|
+
module Doohly
|
|
10
|
+
class << self
|
|
11
|
+
# Quick client initialization
|
|
12
|
+
# @param api_token [String] Doohly API token
|
|
13
|
+
# @return [Doohly::Client] Configured client instance
|
|
14
|
+
def client(api_token: nil)
|
|
15
|
+
Client.new(api_token: api_token)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
data/sig/doohly.rbs
ADDED
data/test_api.rb
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
#!/usr/bin/env ruby
|
|
2
|
+
# frozen_string_literal: true
|
|
3
|
+
|
|
4
|
+
require "bundler/setup"
|
|
5
|
+
require "doohly"
|
|
6
|
+
|
|
7
|
+
api_token = ENV.fetch("DOOHLY_API_TOKEN")
|
|
8
|
+
|
|
9
|
+
client = Doohly::Client.new(api_token: api_token)
|
|
10
|
+
|
|
11
|
+
puts "Testing API connection..."
|
|
12
|
+
puts
|
|
13
|
+
|
|
14
|
+
begin
|
|
15
|
+
devices = client.devices
|
|
16
|
+
puts "ā
API connection successful!"
|
|
17
|
+
puts "Response type: #{devices.class}"
|
|
18
|
+
puts "Response: #{devices.inspect}"
|
|
19
|
+
rescue Doohly::AuthenticationError => e
|
|
20
|
+
puts "ā Authentication failed: #{e.message}"
|
|
21
|
+
rescue Doohly::APIError => e
|
|
22
|
+
puts "ā API error: #{e.message} (Status: #{e.status})"
|
|
23
|
+
rescue StandardError => e
|
|
24
|
+
puts "ā Error: #{e.class} - #{e.message}"
|
|
25
|
+
puts e.backtrace.first(5)
|
|
26
|
+
end
|