poodle-ruby 1.0.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 +1 -0
- data/.rubocop.yml +65 -0
- data/.yardopts +12 -0
- data/CODE_OF_CONDUCT.md +78 -0
- data/CONTRIBUTING.md +151 -0
- data/LICENSE +21 -0
- data/README.md +442 -0
- data/Rakefile +4 -0
- data/examples/advanced_usage.rb +255 -0
- data/examples/basic_usage.rb +64 -0
- data/lib/poodle/client.rb +222 -0
- data/lib/poodle/configuration.rb +145 -0
- data/lib/poodle/email.rb +190 -0
- data/lib/poodle/email_response.rb +101 -0
- data/lib/poodle/errors/authentication_error.rb +54 -0
- data/lib/poodle/errors/base_error.rb +49 -0
- data/lib/poodle/errors/forbidden_error.rb +56 -0
- data/lib/poodle/errors/network_error.rb +104 -0
- data/lib/poodle/errors/payment_error.rb +73 -0
- data/lib/poodle/errors/rate_limit_error.rb +146 -0
- data/lib/poodle/errors/server_error.rb +57 -0
- data/lib/poodle/errors/validation_error.rb +93 -0
- data/lib/poodle/http_client.rb +327 -0
- data/lib/poodle/rails/railtie.rb +50 -0
- data/lib/poodle/rails/tasks.rake +113 -0
- data/lib/poodle/rails.rb +158 -0
- data/lib/poodle/test_helpers.rb +244 -0
- data/lib/poodle/version.rb +6 -0
- data/lib/poodle.rb +80 -0
- data/sig/poodle.rbs +4 -0
- metadata +107 -0
@@ -0,0 +1,93 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "base_error"
|
4
|
+
|
5
|
+
module Poodle
|
6
|
+
# Exception raised when request validation fails (400 Bad Request, 422 Unprocessable Entity)
|
7
|
+
#
|
8
|
+
# @example Handling validation errors
|
9
|
+
# begin
|
10
|
+
# client.send_email(invalid_email)
|
11
|
+
# rescue Poodle::ValidationError => e
|
12
|
+
# puts "Validation failed: #{e.message}"
|
13
|
+
# e.errors.each do |field, messages|
|
14
|
+
# puts "#{field}: #{messages.join(', ')}"
|
15
|
+
# end
|
16
|
+
# end
|
17
|
+
class ValidationError < Error
|
18
|
+
# @return [Hash] field-specific validation errors
|
19
|
+
attr_reader :errors
|
20
|
+
|
21
|
+
# Initialize a new ValidationError
|
22
|
+
#
|
23
|
+
# @param message [String] the error message
|
24
|
+
# @param errors [Hash] field-specific validation errors
|
25
|
+
# @param context [Hash] additional context information
|
26
|
+
# @param status_code [Integer] HTTP status code (400 or 422)
|
27
|
+
def initialize(message = "Validation failed", errors: {}, context: {}, status_code: 400)
|
28
|
+
@errors = errors
|
29
|
+
super(message, context: context.merge(errors: errors), status_code: status_code)
|
30
|
+
end
|
31
|
+
|
32
|
+
# Create a ValidationError for invalid email address
|
33
|
+
#
|
34
|
+
# @param email [String] the invalid email address
|
35
|
+
# @param field [String] the field name (default: "email")
|
36
|
+
# @return [ValidationError] the validation error
|
37
|
+
def self.invalid_email(email, field: "email")
|
38
|
+
new(
|
39
|
+
"Invalid email address provided",
|
40
|
+
errors: { field => ["'#{email}' is not a valid email address"] }
|
41
|
+
)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Create a ValidationError for missing required field
|
45
|
+
#
|
46
|
+
# @param field [String] the missing field name
|
47
|
+
# @return [ValidationError] the validation error
|
48
|
+
def self.missing_field(field)
|
49
|
+
new(
|
50
|
+
"Missing required field: #{field}",
|
51
|
+
errors: { field => ["The #{field} field is required"] }
|
52
|
+
)
|
53
|
+
end
|
54
|
+
|
55
|
+
# Create a ValidationError for invalid content
|
56
|
+
#
|
57
|
+
# @return [ValidationError] the validation error
|
58
|
+
def self.invalid_content
|
59
|
+
new(
|
60
|
+
"Email must contain either HTML content, text content, or both",
|
61
|
+
errors: { content: ["At least one content type (html or text) is required"] }
|
62
|
+
)
|
63
|
+
end
|
64
|
+
|
65
|
+
# Create a ValidationError for content too large
|
66
|
+
#
|
67
|
+
# @param field [String] the field name
|
68
|
+
# @param max_size [Integer] the maximum allowed size
|
69
|
+
# @return [ValidationError] the validation error
|
70
|
+
def self.content_too_large(field, max_size)
|
71
|
+
new(
|
72
|
+
"Content size exceeds maximum allowed size of #{max_size} bytes",
|
73
|
+
errors: { field => ["Content size exceeds maximum allowed size of #{max_size} bytes"] }
|
74
|
+
)
|
75
|
+
end
|
76
|
+
|
77
|
+
# Create a ValidationError for invalid field value
|
78
|
+
#
|
79
|
+
# @param field [String] the field name
|
80
|
+
# @param value [String] the invalid value
|
81
|
+
# @param reason [String] the reason why it's invalid
|
82
|
+
# @return [ValidationError] the validation error
|
83
|
+
def self.invalid_field_value(field, value, reason = "")
|
84
|
+
message = "Invalid value for field '#{field}': #{value}"
|
85
|
+
message += ". #{reason}" unless reason.empty?
|
86
|
+
|
87
|
+
new(
|
88
|
+
message,
|
89
|
+
errors: { field => [message] }
|
90
|
+
)
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
@@ -0,0 +1,327 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "faraday"
|
4
|
+
require "json"
|
5
|
+
require_relative "configuration"
|
6
|
+
require_relative "errors/authentication_error"
|
7
|
+
require_relative "errors/payment_error"
|
8
|
+
require_relative "errors/forbidden_error"
|
9
|
+
require_relative "errors/rate_limit_error"
|
10
|
+
require_relative "errors/validation_error"
|
11
|
+
require_relative "errors/network_error"
|
12
|
+
require_relative "errors/server_error"
|
13
|
+
|
14
|
+
module Poodle
|
15
|
+
# HTTP client wrapper for Poodle API communication
|
16
|
+
#
|
17
|
+
# @example Basic usage
|
18
|
+
# config = Poodle::Configuration.new(api_key: "your_api_key")
|
19
|
+
# client = Poodle::HttpClient.new(config)
|
20
|
+
# response = client.post("api/v1/send-email", email_data)
|
21
|
+
class HttpClient
|
22
|
+
# @return [Configuration] the configuration object
|
23
|
+
attr_reader :config
|
24
|
+
|
25
|
+
# Initialize a new HttpClient
|
26
|
+
#
|
27
|
+
# @param config [Configuration] the configuration object
|
28
|
+
def initialize(config)
|
29
|
+
@config = config
|
30
|
+
@connection = build_connection
|
31
|
+
end
|
32
|
+
|
33
|
+
# Send a POST request
|
34
|
+
#
|
35
|
+
# @param endpoint [String] the API endpoint
|
36
|
+
# @param data [Hash] the request data
|
37
|
+
# @param headers [Hash] additional headers
|
38
|
+
# @return [Hash] the parsed response data
|
39
|
+
# @raise [Poodle::Error] if the request fails
|
40
|
+
def post(endpoint, data = {}, headers = {})
|
41
|
+
request(:post, endpoint, data, headers)
|
42
|
+
end
|
43
|
+
|
44
|
+
# Send a GET request
|
45
|
+
#
|
46
|
+
# @param endpoint [String] the API endpoint
|
47
|
+
# @param params [Hash] query parameters
|
48
|
+
# @param headers [Hash] additional headers
|
49
|
+
# @return [Hash] the parsed response data
|
50
|
+
# @raise [Poodle::Error] if the request fails
|
51
|
+
def get(endpoint, params = {}, headers = {})
|
52
|
+
request(:get, endpoint, params, headers)
|
53
|
+
end
|
54
|
+
|
55
|
+
private
|
56
|
+
|
57
|
+
# Send an HTTP request
|
58
|
+
#
|
59
|
+
# @param method [Symbol] the HTTP method
|
60
|
+
# @param endpoint [String] the API endpoint
|
61
|
+
# @param data [Hash] the request data or query parameters
|
62
|
+
# @param headers [Hash] additional headers
|
63
|
+
# @return [Hash] the parsed response data
|
64
|
+
# @raise [Poodle::Error] if the request fails
|
65
|
+
def request(method, endpoint, data = {}, headers = {})
|
66
|
+
url = @config.url_for(endpoint)
|
67
|
+
|
68
|
+
log_request(method, url, data) if @config.debug?
|
69
|
+
|
70
|
+
response = perform_request(method, url, data, headers)
|
71
|
+
|
72
|
+
log_response(response) if @config.debug?
|
73
|
+
|
74
|
+
handle_response(response)
|
75
|
+
rescue Faraday::TimeoutError => e
|
76
|
+
puts "TimeoutError: #{e.message}"
|
77
|
+
raise NetworkError.connection_timeout(@config.timeout)
|
78
|
+
rescue Faraday::ConnectionFailed => e
|
79
|
+
handle_connection_failed_error(e)
|
80
|
+
rescue Faraday::Error => e
|
81
|
+
raise NetworkError.connection_failed(@config.base_url, original_error: e)
|
82
|
+
end
|
83
|
+
|
84
|
+
# Perform the actual HTTP request
|
85
|
+
#
|
86
|
+
# @param method [Symbol] the HTTP method
|
87
|
+
# @param url [String] the request URL
|
88
|
+
# @param data [Hash] the request data
|
89
|
+
# @param headers [Hash] additional headers
|
90
|
+
# @return [Faraday::Response] the HTTP response
|
91
|
+
def perform_request(method, url, data, headers)
|
92
|
+
case method
|
93
|
+
when :post
|
94
|
+
@connection.post(url) do |req|
|
95
|
+
req.headers.update(headers)
|
96
|
+
req.body = data.to_json
|
97
|
+
end
|
98
|
+
when :get
|
99
|
+
@connection.get(url) do |req|
|
100
|
+
req.headers.update(headers)
|
101
|
+
req.params = data
|
102
|
+
end
|
103
|
+
else
|
104
|
+
raise ArgumentError, "Unsupported HTTP method: #{method}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
|
108
|
+
# Handle connection failed errors
|
109
|
+
#
|
110
|
+
# @param error [Faraday::ConnectionFailed] the connection error
|
111
|
+
# @raise [NetworkError] the appropriate network error
|
112
|
+
def handle_connection_failed_error(error)
|
113
|
+
if error.message.include?("SSL") || error.message.include?("certificate")
|
114
|
+
raise NetworkError.ssl_error(error.message)
|
115
|
+
elsif error.message.include?("resolve") || error.message.include?("DNS")
|
116
|
+
host = URI.parse(@config.base_url).host
|
117
|
+
raise NetworkError.dns_resolution_failed(host)
|
118
|
+
else
|
119
|
+
raise NetworkError.connection_failed(@config.base_url, original_error: error)
|
120
|
+
end
|
121
|
+
end
|
122
|
+
|
123
|
+
# Build the Faraday connection
|
124
|
+
#
|
125
|
+
# @return [Faraday::Connection] the configured connection
|
126
|
+
def build_connection
|
127
|
+
Faraday.new do |conn|
|
128
|
+
configure_connection_middleware(conn)
|
129
|
+
configure_connection_timeouts(conn)
|
130
|
+
configure_connection_headers(conn)
|
131
|
+
configure_custom_options(conn)
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Configure connection middleware
|
136
|
+
#
|
137
|
+
# @param conn [Faraday::Connection] the connection
|
138
|
+
def configure_connection_middleware(conn)
|
139
|
+
conn.request :json
|
140
|
+
conn.response :json, content_type: /\bjson$/
|
141
|
+
conn.adapter Faraday.default_adapter
|
142
|
+
end
|
143
|
+
|
144
|
+
# Configure connection timeouts
|
145
|
+
#
|
146
|
+
# @param conn [Faraday::Connection] the connection
|
147
|
+
def configure_connection_timeouts(conn)
|
148
|
+
conn.options.timeout = @config.timeout
|
149
|
+
conn.options.open_timeout = @config.connect_timeout
|
150
|
+
end
|
151
|
+
|
152
|
+
# Configure connection headers
|
153
|
+
#
|
154
|
+
# @param conn [Faraday::Connection] the connection
|
155
|
+
def configure_connection_headers(conn)
|
156
|
+
conn.headers["Authorization"] = "Bearer #{@config.api_key}"
|
157
|
+
conn.headers["Content-Type"] = "application/json"
|
158
|
+
conn.headers["Accept"] = "application/json"
|
159
|
+
conn.headers["User-Agent"] = @config.user_agent
|
160
|
+
end
|
161
|
+
|
162
|
+
# Configure custom HTTP options
|
163
|
+
#
|
164
|
+
# @param conn [Faraday::Connection] the connection
|
165
|
+
def configure_custom_options(conn)
|
166
|
+
@config.http_options.each do |key, value|
|
167
|
+
conn.options[key] = value
|
168
|
+
end
|
169
|
+
end
|
170
|
+
|
171
|
+
# Handle HTTP response
|
172
|
+
#
|
173
|
+
# @param response [Faraday::Response] the HTTP response
|
174
|
+
# @return [Hash] the parsed response data
|
175
|
+
# @raise [Poodle::Error] if the response indicates an error
|
176
|
+
def handle_response(response)
|
177
|
+
return response.body || {} if success_response?(response)
|
178
|
+
|
179
|
+
handle_error_response(response)
|
180
|
+
end
|
181
|
+
|
182
|
+
# Check if response is successful
|
183
|
+
#
|
184
|
+
# @param response [Faraday::Response] the HTTP response
|
185
|
+
# @return [Boolean] true if successful
|
186
|
+
def success_response?(response)
|
187
|
+
(200..299).cover?(response.status)
|
188
|
+
end
|
189
|
+
|
190
|
+
# Handle error responses
|
191
|
+
#
|
192
|
+
# @param response [Faraday::Response] the HTTP response
|
193
|
+
# @raise [Poodle::Error] the appropriate error
|
194
|
+
def handle_error_response(response)
|
195
|
+
case response.status
|
196
|
+
when 400
|
197
|
+
handle_validation_error(response)
|
198
|
+
when 401
|
199
|
+
raise AuthenticationError.invalid_api_key
|
200
|
+
when 402
|
201
|
+
handle_payment_error(response)
|
202
|
+
when 403
|
203
|
+
handle_forbidden_error(response)
|
204
|
+
when 422
|
205
|
+
handle_validation_error(response, status_code: 422)
|
206
|
+
when 429
|
207
|
+
raise RateLimitError.from_headers(response.headers)
|
208
|
+
when 500..599
|
209
|
+
handle_server_error(response)
|
210
|
+
else
|
211
|
+
raise NetworkError.http_error(response.status, extract_error_message(response))
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Handle validation errors (400, 422)
|
216
|
+
#
|
217
|
+
# @param response [Faraday::Response] the HTTP response
|
218
|
+
# @param status_code [Integer] the HTTP status code
|
219
|
+
# @raise [ValidationError] the validation error
|
220
|
+
def handle_validation_error(response, status_code: 400)
|
221
|
+
body = response.body || {}
|
222
|
+
message = extract_error_message(response)
|
223
|
+
errors = extract_validation_errors(body)
|
224
|
+
|
225
|
+
raise ValidationError.new(message, errors: errors, status_code: status_code)
|
226
|
+
end
|
227
|
+
|
228
|
+
# Handle payment errors (402)
|
229
|
+
#
|
230
|
+
# @param response [Faraday::Response] the HTTP response
|
231
|
+
# @raise [PaymentError] the payment error
|
232
|
+
def handle_payment_error(response)
|
233
|
+
message = extract_error_message(response)
|
234
|
+
|
235
|
+
case message
|
236
|
+
when /subscription.*expired/i
|
237
|
+
raise PaymentError.subscription_expired
|
238
|
+
when /trial.*limit/i
|
239
|
+
raise PaymentError.trial_limit_reached
|
240
|
+
when /monthly.*limit/i
|
241
|
+
raise PaymentError.monthly_limit_reached
|
242
|
+
else
|
243
|
+
raise PaymentError, message
|
244
|
+
end
|
245
|
+
end
|
246
|
+
|
247
|
+
# Handle forbidden errors (403)
|
248
|
+
#
|
249
|
+
# @param response [Faraday::Response] the HTTP response
|
250
|
+
# @raise [ForbiddenError] the forbidden error
|
251
|
+
def handle_forbidden_error(response)
|
252
|
+
body = response.body || {}
|
253
|
+
message = extract_error_message(response)
|
254
|
+
|
255
|
+
raise ForbiddenError.insufficient_permissions unless message.include?("suspended")
|
256
|
+
|
257
|
+
# Extract suspension details if available
|
258
|
+
reason = body["reason"] || "unknown"
|
259
|
+
rate = body["rate"]
|
260
|
+
raise ForbiddenError.account_suspended(reason, rate)
|
261
|
+
end
|
262
|
+
|
263
|
+
# Handle server errors (5xx)
|
264
|
+
#
|
265
|
+
# @param response [Faraday::Response] the HTTP response
|
266
|
+
# @raise [ServerError] the server error
|
267
|
+
def handle_server_error(response)
|
268
|
+
message = extract_error_message(response)
|
269
|
+
|
270
|
+
case response.status
|
271
|
+
when 500
|
272
|
+
raise ServerError.internal_server_error(message)
|
273
|
+
when 502
|
274
|
+
raise ServerError.bad_gateway(message)
|
275
|
+
when 503
|
276
|
+
raise ServerError.service_unavailable(message)
|
277
|
+
when 504
|
278
|
+
raise ServerError.gateway_timeout(message)
|
279
|
+
else
|
280
|
+
raise ServerError.new(message, status_code: response.status)
|
281
|
+
end
|
282
|
+
end
|
283
|
+
|
284
|
+
# Extract error message from response
|
285
|
+
#
|
286
|
+
# @param response [Faraday::Response] the HTTP response
|
287
|
+
# @return [String] the error message
|
288
|
+
def extract_error_message(response)
|
289
|
+
body = response.body
|
290
|
+
return "HTTP #{response.status} error" unless body.is_a?(Hash)
|
291
|
+
|
292
|
+
body["message"] || body["error"] || "HTTP #{response.status} error"
|
293
|
+
end
|
294
|
+
|
295
|
+
# Extract validation errors from response body
|
296
|
+
#
|
297
|
+
# @param body [Hash] the response body
|
298
|
+
# @return [Hash] field-specific validation errors
|
299
|
+
def extract_validation_errors(body)
|
300
|
+
return {} unless body.is_a?(Hash)
|
301
|
+
|
302
|
+
errors = body["errors"] || body["validation_errors"] || {}
|
303
|
+
return {} unless errors.is_a?(Hash)
|
304
|
+
|
305
|
+
# Convert string values to arrays for consistency
|
306
|
+
errors.transform_values { |v| Array(v) }
|
307
|
+
end
|
308
|
+
|
309
|
+
# Log HTTP request for debugging
|
310
|
+
#
|
311
|
+
# @param method [Symbol] the HTTP method
|
312
|
+
# @param url [String] the request URL
|
313
|
+
# @param data [Hash] the request data
|
314
|
+
def log_request(method, url, data)
|
315
|
+
puts "[Poodle] #{method.upcase} #{url}"
|
316
|
+
puts "[Poodle] Request: #{data.to_json}" unless data.empty?
|
317
|
+
end
|
318
|
+
|
319
|
+
# Log HTTP response for debugging
|
320
|
+
#
|
321
|
+
# @param response [Faraday::Response] the HTTP response
|
322
|
+
def log_response(response)
|
323
|
+
puts "[Poodle] Response: #{response.status}"
|
324
|
+
puts "[Poodle] Body: #{response.body}" if response.body
|
325
|
+
end
|
326
|
+
end
|
327
|
+
end
|
@@ -0,0 +1,50 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "rails/railtie"
|
4
|
+
|
5
|
+
module Poodle
|
6
|
+
module Rails
|
7
|
+
# Rails Railtie for automatic Poodle integration
|
8
|
+
class Railtie < ::Rails::Railtie
|
9
|
+
# Add Poodle configuration to Rails generators
|
10
|
+
config.generators do |g|
|
11
|
+
g.test_framework :rspec
|
12
|
+
end
|
13
|
+
|
14
|
+
# Initialize Poodle after Rails application is initialized
|
15
|
+
initializer "poodle.configure" do |app|
|
16
|
+
# Auto-configure Poodle if credentials or environment variables are present
|
17
|
+
if ENV["POODLE_API_KEY"] ||
|
18
|
+
(app.credentials.respond_to?(:poodle_api_key) && app.credentials.poodle_api_key)
|
19
|
+
Poodle::Rails.auto_configure!
|
20
|
+
end
|
21
|
+
end
|
22
|
+
|
23
|
+
# Add Poodle logger integration
|
24
|
+
initializer "poodle.logger" do |_app|
|
25
|
+
# Integrate with Rails logger if debug mode is enabled
|
26
|
+
if Poodle::Rails.configured? && Poodle::Rails.configuration.debug?
|
27
|
+
# Override the HTTP client logging to use Rails logger
|
28
|
+
Poodle::HttpClient.class_eval do
|
29
|
+
private
|
30
|
+
|
31
|
+
def log_request(method, url, data)
|
32
|
+
::Rails.logger.debug "[Poodle] #{method.upcase} #{url}"
|
33
|
+
::Rails.logger.debug "[Poodle] Request: #{data.to_json}" unless data.empty?
|
34
|
+
end
|
35
|
+
|
36
|
+
def log_response(response)
|
37
|
+
::Rails.logger.debug "[Poodle] Response: #{response.status}"
|
38
|
+
::Rails.logger.debug "[Poodle] Body: #{response.body}" if response.body
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
# Add rake tasks
|
45
|
+
rake_tasks do
|
46
|
+
load "poodle/rails/tasks.rake"
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
50
|
+
end
|
@@ -0,0 +1,113 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
namespace :poodle do
|
4
|
+
desc "Check Poodle configuration"
|
5
|
+
task config: :environment do
|
6
|
+
if Poodle::Rails.configured?
|
7
|
+
config = Poodle::Rails.configuration
|
8
|
+
puts "✅ Poodle is configured"
|
9
|
+
puts " API Key: #{config.api_key ? "***#{config.api_key[-4..]}" : 'Not set'}"
|
10
|
+
puts " Base URL: #{config.base_url}"
|
11
|
+
puts " Timeout: #{config.timeout}s"
|
12
|
+
puts " Debug: #{config.debug?}"
|
13
|
+
else
|
14
|
+
puts "❌ Poodle is not configured"
|
15
|
+
puts " Set POODLE_API_KEY environment variable or configure in Rails credentials"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
desc "Test Poodle connection"
|
20
|
+
task test: :environment do
|
21
|
+
unless Poodle::Rails.configured?
|
22
|
+
puts "❌ Poodle is not configured. Run 'rake poodle:config' to check configuration."
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
|
26
|
+
begin
|
27
|
+
client = Poodle::Rails.client
|
28
|
+
puts "✅ Poodle client created successfully"
|
29
|
+
puts " SDK Version: #{Poodle::VERSION}"
|
30
|
+
puts " User Agent: #{client.config.user_agent}"
|
31
|
+
rescue StandardError => e
|
32
|
+
puts "❌ Failed to create Poodle client: #{e.message}"
|
33
|
+
exit 1
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
desc "Send a test email"
|
38
|
+
task :send_test, %i[to from] => :environment do |_t, args|
|
39
|
+
unless Poodle::Rails.configured?
|
40
|
+
puts "❌ Poodle is not configured. Run 'rake poodle:config' to check configuration."
|
41
|
+
exit 1
|
42
|
+
end
|
43
|
+
|
44
|
+
to_email = args[:to] || ENV.fetch("POODLE_TEST_TO", nil)
|
45
|
+
from_email = args[:from] || ENV["POODLE_TEST_FROM"] || "test@example.com"
|
46
|
+
|
47
|
+
unless to_email
|
48
|
+
puts "❌ Please provide a recipient email address:"
|
49
|
+
puts " rake poodle:send_test[recipient@example.com]"
|
50
|
+
puts " or set POODLE_TEST_TO environment variable"
|
51
|
+
exit 1
|
52
|
+
end
|
53
|
+
|
54
|
+
begin
|
55
|
+
client = Poodle::Rails.client
|
56
|
+
response = client.send(
|
57
|
+
from: from_email,
|
58
|
+
to: to_email,
|
59
|
+
subject: "Test Email from Poodle Ruby SDK",
|
60
|
+
html: "<h1>Test Email</h1><p>This is a test email sent from the Poodle Ruby SDK in a Rails application.</p>",
|
61
|
+
text: "Test Email\n\nThis is a test email sent from the Poodle Ruby SDK in a Rails application."
|
62
|
+
)
|
63
|
+
|
64
|
+
if response.success?
|
65
|
+
puts "✅ Test email sent successfully!"
|
66
|
+
puts " From: #{from_email}"
|
67
|
+
puts " To: #{to_email}"
|
68
|
+
puts " Message: #{response.message}"
|
69
|
+
else
|
70
|
+
puts "❌ Failed to send test email: #{response.message}"
|
71
|
+
exit 1
|
72
|
+
end
|
73
|
+
rescue StandardError => e
|
74
|
+
puts "❌ Error sending test email: #{e.message}"
|
75
|
+
puts " #{e.class.name}"
|
76
|
+
exit 1
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
desc "Generate Poodle initializer"
|
81
|
+
task install: :environment do
|
82
|
+
initializer_path = Rails.root.join("config", "initializers", "poodle.rb")
|
83
|
+
|
84
|
+
if File.exist?(initializer_path)
|
85
|
+
puts "⚠️ Poodle initializer already exists at #{initializer_path}"
|
86
|
+
puts " Remove it first if you want to regenerate it."
|
87
|
+
exit 1
|
88
|
+
end
|
89
|
+
|
90
|
+
initializer_content = <<~RUBY
|
91
|
+
# frozen_string_literal: true
|
92
|
+
|
93
|
+
# Poodle email sending configuration
|
94
|
+
Poodle::Rails.configure do |config|
|
95
|
+
# API key from Rails credentials or environment variable
|
96
|
+
config.api_key = Rails.application.credentials.poodle_api_key || ENV['POODLE_API_KEY']
|
97
|
+
|
98
|
+
# Optional: Override base URL (defaults to https://api.usepoodle.com)
|
99
|
+
# config.base_url = ENV['POODLE_BASE_URL']
|
100
|
+
|
101
|
+
# Optional: Set timeout (defaults to 30 seconds)
|
102
|
+
# config.timeout = 30
|
103
|
+
|
104
|
+
# Optional: Enable debug mode in development
|
105
|
+
config.debug = Rails.env.development?
|
106
|
+
end
|
107
|
+
RUBY
|
108
|
+
|
109
|
+
File.write(initializer_path, initializer_content)
|
110
|
+
puts "✅ Created Poodle initializer at #{initializer_path}"
|
111
|
+
puts " Don't forget to set your API key in Rails credentials or environment variables!"
|
112
|
+
end
|
113
|
+
end
|